You already have a LiveKit agent that works. You want callers to reach it over the PSTN, or you want its media to use ClutchCall’s QUIC transport instead of a LiveKit SFU. You do not want to rewrite the agent. There are three ways to do that. The recommended way is a package that you install into your agent process.

Transport plugin

Recommended. pip install / npm i a plugin into your existing livekit-agents worker. Your agent code, plugins, and provider keys do not change. Only the transport changes. There is no LiveKit server at all.

Front-proxy the media

The voice gateway terminates the caller. It bridges audio into your existing LiveKit room through a sidecar. Use this path when you must keep your LiveKit server in operation.

Browser compat shim

Preview. Change one import. A livekit-client browser app then publishes and subscribes over MoQT/QUIC instead of the SFU.
These three paths solve different problems. The plugin replaces LiveKit’s transport for a server-side agent. The front-proxy keeps your LiveKit server and puts ClutchCall in front of it. The compat shim is for a browser client. The paths combine. You can front-proxy a phone leg and move a browser client onto the transport at the same time.
Install the plugin next to your agent. The plugin wraps AgentSession.start. It binds the session’s audio input and output to a QUIC/WebTransport connection to ClutchCall instead of a LiveKit room. The LiveKit Agents SDK skips its own RoomIO when a session already has custom audio IO set. Thus your entrypoint runs verbatim: the same AgentSession, the same STT/LLM/TTS plugins, the same provider keys, and the same tool calls. There is no room, no SFU, and no WebRTC.
This is the same seam that LiveKit itself uses for its TCP console mode (TcpAudioInput / TcpAudioOutput). The plugin points that seam at our QUIC edge.
Your agent is always the QUIC client. It dials out to :443 and opens no inbound ports. Thus it works behind NAT and needs no public address. One session carries three lanes:
  • presence/registry (liveness)
  • audio over QUIC datagrams
  • per-call control (job assign, flush, barge-in, playback finished) over a WebTransport stream

Install

The quic extra installs aioquic (the WebTransport client). Without it, the package still imports, but it cannot connect.

Run your agent over the transport

Replace your worker’s entrypoint runner (cli.run_app(...) / cli.runApp(...)) with ours. The code it calls, your entrypoint, does not change.
worker.py
run_agent_worker does these tasks:
  1. It authenticates one connection.
  2. It registers the worker under agent_name.
  3. It serves the calls that the engine assigns to it. For each call, it starts your entrypoint with a JobContext bound to that call’s media.
If the connection drops, the worker reconnects with exponential backoff. Thus an engine restart does not leave the worker on a dead socket.
Set sample_rate to match the call leg. The pcm16 codec is a raw passthrough. A wideband TTS voice (24 kHz or 48 kHz) sent onto an 8 kHz telephony leg plays at the wrong speed. The plugin declares the wire rate on the session output, and the SDK resamples for you. But this works only if you set the correct rate.

Point a ClutchCall agent at the worker

The engine side models a remote agent as a VENDOR_BRIDGE node with vendor_provider set to clutchcall. This means external agent orchestration, and ClutchCall stays the transport. vendor_room is the agent_name that your worker registered.
agent-config.json
With this config, the runtime runs no turn detection and no model in the engine. It sends the caller’s audio to your worker. It streams your agent’s audio back onto the call. Attach the agent to a number or trunk in the same way as a native agent. See SIP Trunking and Number Provisioning.

What your agent sees

Your entrypoint receives a JobContext. Its ctx.room is a veneer, not a LiveKit room. The veneer carries the fields that agents read: These fields mirror how LiveKit shows SIP participant attributes. Thus an agent that reads the caller’s number continues to work without changes. Room APIs beyond these fields are not backed: track publication, participant enumeration, and room data messages. There is no room behind the veneer. The plugin forwards finalized transcript turns to the engine automatically over the control stream. Thus a remote agent’s conversation history lands in the same store as a native agent’s history. It shows in Observability together with CDR, MOS, and recording.

Current limits

livekit-plugins-clutchcall (Python) and @clutchcall/livekit-transport (TypeScript) track the two LiveKit Agents SDKs. There is no Go or Rust plugin.
The wire codec is raw little-endian pcm16. Opus fits into the same codec seam without changes to the session or IO classes, but it is not connected yet. Thus budget bandwidth for uncompressed audio between your worker and the edge.
The worker holds a connection open, and the engine dispatches calls to it. This mode is correct for always-on workers on a VM or in Kubernetes, and it is warm on the first call. A per-call HTTP trigger is not yet available. That trigger would serve scale-to-zero serverless workers that dial back on demand.
The worker needs outbound UDP/443 to the edge. The WebSocket fallback covers presence only. Media over TCP reintroduces head-of-line blocking and is a degraded last resort. Agents run on server infrastructure, where QUIC egress is the norm.

Path B — front-proxy the media

Use this path when you must keep your LiveKit server in operation. For example, other participants join the room, or another part of your stack depends on it. In this path, the gateway owns the caller edge: a PSTN SIP trunk, or a browser leg over QUIC on the single :443 plane. The gateway decodes the edge to the runtime’s 8 kHz PCM audio bus. It relays that audio over a WebSocket media bridge to a bridge sidecar that you run next to your LiveKit deployment. That sidecar joins your room as an ordinary participant.
What is shipped and what you deploy. The gateway-side bridge is in-tree and shipped. It consists of a vendor-bridge pipeline node plus the WebSocket media handler. The bridge sidecar that joins your LiveKit room is a component that you run next to your LiveKit deployment. It uses your LiveKit URL, API key, and secret.
The agent config is the same VENDOR_BRIDGE shape. Here vendor_provider names the vendor instead of ClutchCall:
agent-config.json
The full runnable version is in Bridge a LiveKit Agent. It covers how to build the sidecar, store credentials, route a number, and place a call.

Path C — the browser compat shim (preview)

If your LiveKit surface is a browser app (livekit-client), you can run the client’s audio over our transport directly. The livekit-compat shim re-implements the livekit-client API (Room, RoomEvent, tracks, participants) on top of MoQT/QUIC. The common voice path is a one-line import change. That path covers these actions: publish the mic, subscribe to remote audio, and exchange data. There is no SFU, and the shim never uses WebRTC’s transport.
Preview / experimental. The livekit-compat shim is an early-access browser package, not a shipped product. It covers the core voice path: publish/subscribe audio and data messages. Video, screenshare, and per-participant E2EE key rotation are deferred. A session-setup race can sometimes deliver zero frames on connect. If that occurs, retry the connect. Pin a version and test your flows.
The mic audio still goes through the browser’s Opus encoder. But the shim extracts only the encoded frames and sends them over MoQT. The shim requires encoded media transforms (RTCRtpScriptTransform). It refuses to connect on a browser that would silently fall back to plaintext WebRTC. Today that means Chromium-family browsers. The full deep-dive is in Keep Your Existing Runtime and WebRTC Diversion.

Which path?

Run a LiveKit Agent on the Transport

The end-to-end recipe: install the plugin, register a worker, and take a call.

Keep Your Existing Runtime

The full transport-only spectrum: the plugin, the shim, and raw MoQT.

From Self-Hosted LiveKit

The end-to-end migration path: trunks, tokens, and rollout.

Custom Agent Runtime

The same patterns for any non-LiveKit runtime.