You have a working LiveKit agent and you want to put it on the phone — or on TeleQuick’s QUIC transport — without rewriting it. This recipe builds the whole thing end to end: you deploy a small bridge sidecar next to your LiveKit deployment, point a TeleQuick agent at the vendor bridge, route an inbound number to it, and place a real call. The caller lands on the voice gateway; the gateway carries the telephony/QUIC edge and relays audio to your LiveKit room; your agent answers unchanged.
There is no packaged LiveKit-Agents adapter, and no plugin lives inside your agent. Integration is at the edge — the gateway front-proxies audio to your existing room over a WebSocket media bridge. Your agent sees a normal room participant. The alternative browser path (the livekit-compat shim) is preview — see the integration reference for that. This recipe is the shipped front-proxy path.

What you’ll build

 PSTN / SIP trunk ─▶ ┌──────────────────┐  WebSocket   ┌──────────────┐   ┌──────────────┐
                     │  voice gateway   │   media      │  bridge      │   │  your LiveKit│
 browser (QUIC/MoQT) │  (terminates the │──── bridge ──▶  sidecar     │──▶│  room + agent│
 on :443 ──────────▶ │  caller edge)    │              │ (joins room) │   │              │
                     └──────────────────┘              └──────────────┘   └──────────────┘
        caller audio ──▶ 8 kHz PCM bus ──▶ Opus ──▶ published as a mic track ──▶ agent
        agent audio  ◀── back onto the call ◀────── subscribed Opus ◀───────────┘
The gateway owns the hard edge — a SIP trunk or a browser leg over QUIC on the single :443 plane — decodes it to the runtime’s 8 kHz PCM audio bus, and relays that audio to the bridge over a WebSocket media bridge. The bridge joins your LiveKit room as an ordinary participant, publishes the caller as a mic track, and forwards your agent’s audio back the other way.
Shipped edge + a sidecar you run. The gateway-side vendor-bridge node and WebSocket media handler are in-tree. The bridge sidecar that joins your LiveKit room is an external component you deploy next to LiveKit with your own LIVEKIT_URL / API key / secret. The edge is shipped; the bridge is a thin component you run.

Prerequisites

LiveKit deployment
required
A LiveKit server (cloud or self-hosted) plus a running agent, and its LIVEKIT_URL, LIVEKIT_API_KEY, and LIVEKIT_API_SECRET.
TeleQuick workspace
required
API credentials in TELEQUICK_CREDENTIALS and an inbound SIP trunk or DID (see SIP Trunking / Number Provisioning).
Go toolchain
required
Go 1.25+ to build the bridge sidecar (a prebuilt livekit-bridge binary is also published with the sidecar).

Build the mini-app

1

Have a LiveKit agent + room

Any LiveKit agent works — it needs no TeleQuick awareness. A minimal LiveKit Agents (Python) worker that a room participant can talk to:
agent.py
from livekit.agents import AgentSession, Agent, JobContext, cli, WorkerOptions
from livekit.plugins import openai, deepgram, elevenlabs, silero

async def entrypoint(ctx: JobContext):
    await ctx.connect()
    session = AgentSession(
        vad=silero.VAD.load(),
        stt=deepgram.STT(),
        llm=openai.LLM(model="gpt-4o-mini"),
        tts=elevenlabs.TTS(),
    )
    await session.start(
        room=ctx.room,
        agent=Agent(instructions="You are a helpful phone assistant."),
    )

if __name__ == "__main__":
    cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
Run it as you already do (python agent.py dev). The bridge will drop a participant into whichever room you name below, and this worker dispatches onto it — exactly as if a human joined from a browser.
2

Deploy the bridge sidecar

The sidecar is a single Go binary. Build it and run it where it can reach both the gateway’s WebSocket media bridge and your LiveKit server.
# build (or use the prebuilt `livekit-bridge` binary shipped with the sidecar)
go build -o livekit-bridge .

# prod: serve wss — the gateway's media client is TLS-only
./livekit-bridge \
  --addr :7071 \
  --tls-cert /etc/tls/bridge.pem \
  --tls-key  /etc/tls/bridge.key
It exposes /bridge (the media endpoint the gateway connects to) and /healthz. Confirm it’s up:
curl -fsS https://bridge.internal:7071/healthz && echo ok
Plaintext (--addr with no cert) is dev only — the gateway’s media client dials wss://. Terminate TLS on the bridge or a sidecar proxy in production, and keep the endpoint on a private network.
The sidecar is credential-free at rest: it receives the LiveKit URL, key, secret, room, and participant identity per session in the handshake the gateway sends — you don’t bake LiveKit secrets into the binary. It’s also codec-agnostic; the gateway sends LiveKit-appropriate Opus and converts on the return, so the bridge just forwards frames.
3

Store your LiveKit credentials

Save your LiveKit URL / key / secret as a tenant vendor credential so the gateway can hand them to the bridge at call time. Reference it by a stable key (here, livekit-prod) from the agent config in the next step. Manage vendor credentials from the console (agent.telequick.dev) or the control-plane API.
4

Point an agent at the vendor bridge

Instead of a cascade (ASR→LLM→TTS) or a duplex realtime model, the agent’s entry node is a VENDOR_BRIDGE — it routes the call’s media to your vendor instead of running a model in-engine:
agent-config.json
{
  "entry_node": "bridge",
  "pipeline": {
    "bridge": {
      "node_type": "VENDOR_BRIDGE",
      "vendor_provider": "livekit",
      "vendor_room": "support-room",
      "vendor_creds_ref": "livekit-prod"
    }
  }
}
FieldMeaning
vendor_providerlivekit | vapi | twilio (swap targets, same mechanism)
vendor_roomThe LiveKit room the bridge joins (your agent dispatches onto it)
vendor_creds_refKey into the tenant vendor credentials from the previous step
With this config the runtime does not run turn detection or a model — it hands the caller’s audio to the bridge and streams the bridge’s audio back onto the call. Your LiveKit agent owns the conversation. The gateway-side bridge endpoint (where the sidecar is reachable) is a deployment-level setting on the gateway; see Runtime Configuration.
5

Route a number to the agent

Attach an inbound DID or SIP trunk to this agent so real calls reach it — the same routing you’d use for any TeleQuick agent. Details in SIP Trunking and Number Provisioning.
6

Connect a caller

Anyone dialing that number now reaches your LiveKit agent through the gateway. To drive it programmatically, place an outbound call from the SDK — the engine bridges to your room on answer just like an inbound call:
import { Voice } from "@telequick/sdk/voice";

const v = new Voice({
  baseUrl: "https://engine.telequick.dev",
  apiKey:  process.env.TELEQUICK_CREDENTIALS!,
  orgId:   "org_abc",
});

// The agent's VENDOR_BRIDGE node bridges this call into your LiveKit room.
const call = await v.calls.originate({
  to:      "+15551234567",
  from:    "+15558675309",
  trunkId: "trunk_main",
  agent:   "livekit-front-proxy",   // the VENDOR_BRIDGE agent above
  ringTimeoutSec: 25,
});

console.log("dialing", call.sid, "→ LiveKit room support-room");

What happens on the wire

When the call answers, the gateway opens one WebSocket session to the sidecar’s /bridge and sends a JSON handshake naming the vendor plus your LiveKit URL, key, secret, room, and a participant identity — pulled from the vendor credentials you stored. Every message after that is one audio frame, in both directions:
  1. The gateway decodes the caller (G.711 off the PSTN, or Opus off the QUIC leg) to the runtime’s PCM bus, re-encodes to Opus, and streams frames to the bridge.
  2. The bridge publishes those frames as a LiveKit MICROPHONE track named customer. Your agent subscribes to it like any participant.
  3. The agent’s reply track is subscribed by the bridge, whose Opus payloads flow back over the same WebSocket and onto the call.
Because the bridge only forwards bytes, the same sidecar fronts Vapi and Twilio too — flip vendor_provider. Vapi and Twilio Media Streams ride a PCM/mulaw WebSocket instead of a LiveKit room, but the loopback shape is identical.

Verify it worked

Everything the gateway gives a native agent, it gives this one — because the edge is unchanged:
  • Call events & CDR — the call shows up in reports with an initiated → established → cleared lifecycle and a Q.850 clear cause.
  • Media quality — per-call MOS / jitter / loss scoring lands on the CDR.
  • Recording — both legs are captured if recording is enabled for the trunk.
See Observability for where these surface.
The livekit-compat browser shim (a one-import swap that runs a livekit-client app over MoQT/QUIC with no SFU) is a separate, preview path — good when your LiveKit surface is a browser app rather than a phone agent. It’s early-access, Chromium-only, and covers the core audio/data path; don’t ship it on the critical path yet. Details in Keep Your Existing Runtime.

LiveKit integration reference

Both paths side by side — front-proxy vs. the browser compat shim.

From Self-Hosted LiveKit

The full migration plan — trunks, tokens, rollout.

Keep Your Existing Runtime

The transport-only spectrum for any runtime, not just LiveKit.

Runtime Configuration

Every pipeline node field, including VENDOR_BRIDGE.