# Bridge a Pipecat Agent

> Connect a Pipecat pipeline to the audio bridge over raw MoQT. This page shows the real seam today and the illustrative shape of a dedicated transport.

You have a Pipecat bot: a frame pipeline connected to ASR, an LLM, and TTS. You
want it to answer real calls over FirstByte's QUIC/MoQT transport. Pipecat's
transport seam maps cleanly onto the audio bridge. Caller audio arrives on an
**uplink** track. Agent audio goes back on a **downlink** track. The bridge is
real and shipping. This cookbook connects it to Pipecat by hand.

> **NOTE:**
> **A packaged adapter now ships. Most readers want that instead.**
>   `pipecat-clutchcall` provides a first-class Pipecat transport
>   (`ClutchCallTransport`) over the external-agent QUIC plane, with one pipeline
>   per routed call. See
>   [Pipecat + FirstByte Transport](/modalities/voice/integrations/pipecat).
>   This cookbook remains the raw MoQT audio-bridge path. Build the glue yourself
>   when you need custom framing, custom mixing, or a runtime that the adapter
>   does not cover.

## The real seam: subscribe uplink, publish downlink

Skip `agents.attach` and run the bridge yourself. Ask for `pcm16` so that
Pipecat gets raw audio. The bridge transcodes the PSTN (G.711) leg for you.
This is stock SDK.

  
```ts
import { Voice } from "clutchcall_connect/voice";

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

const call = await v.calls.originate({
  to: "+15551234567",
  from: "+15558675309",
  trunkId: "trunk_main",
});

// pcm16 @ 16 kHz — the shape most frame pipelines expect.
const bridge = await v.audioBridge.attach(call.sid, {
  codec: "pcm16",
  sampleRate: 16000,
  onUplink: (pcm, tsUs) => pipeline.pushAudio(pcm),   // caller -> your bot
});

pipeline.onAudioOut((pcm) => bridge.publishDownlink(pcm)); // your bot -> caller
```
  
  
```python
import os
from clutchcall.voice import Voice

v = Voice(
    base_url="https://engine.firstbyte.fast",
    api_key=os.environ["CLUTCHCALL_CREDENTIALS"],
    org_id="org_abc",
)

call = v.calls.originate(to="+15551234567", from_="+15558675309", trunk_id="trunk_main")

# pcm16 @ 16 kHz — the shape most frame pipelines expect.
bridge = v.audio_bridge.attach(
    call.sid,
    codec="pcm16",
    sample_rate=16000,
    on_uplink=lambda pcm, ts_us: pipeline.push_audio(pcm),  # caller -> your bot
)

pipeline.on_audio_out(lambda pcm: bridge.publish_downlink(pcm))  # your bot -> caller
```
  

> **WARNING:**
> Keep the `bridge` reference alive for the full duration of the call. If the
>   garbage collector removes it, both tracks drop. Always call `bridge.close()`
>   before `call.hangup()` so that the tracks close cleanly.

## Wrapping the bridge as a Pipecat transport

The bridge above already gives you the two hooks that a Pipecat `Transport`
needs. `onUplink` produces input frames. `publishDownlink` consumes output
frames. A dedicated transport would wrap those into Pipecat's `input()` /
`output()` processors so that the pipeline runs unchanged.

> **NOTE:**
> **Illustrative — this transport is not shipped.** `MoqtBridgeTransport` below
>   does not exist as a package. It shows the design shape only: what a
>   first-class adapter would wrap around the real audio bridge. Do not
>   `pip install` it.

```python
# ILLUSTRATIVE — design sketch, not an installable package.
from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineTask

# A transport that would wrap FirstByte's audio bridge:
#   input():  bridge.on_uplink   -> AudioRawFrame (PCM16 @ 16 kHz)
#   output(): AudioRawFrame      -> bridge.publish_downlink
transport = MoqtBridgeTransport(bridge, sample_rate=16000)

pipeline = Pipeline([
    transport.input(),   # caller audio in
    stt,                 # your ASR frame processor
    llm,                 # your LLM context
    tts,                 # your TTS frame processor
    transport.output(),  # agent audio back to the caller
])

await PipelineRunner().run(PipelineTask(pipeline))
```

The adapter's only real work is the seam that our own runtime already handles:

- Resample between Pipecat's frame rate and the runtime's **8 kHz PCM16** audio bus.
- Keep the room-scoped MoQT namespaces valid.
- Carry **Opus** rather than PCM on browser legs.

None of this work is exotic. It is only not packaged. Today you own the
`onUplink`/`publishDownlink` glue directly.

## What ships today instead of an adapter

  
    Pipecat already speaks WebSocket audio, and FirstByte accepts a bespoke
    WebSocket audio sender as a media leg. To use this path:

    1. Point Pipecat's WebSocket server transport at that on-ramp.
    2. Route the trunk's inbound rule to your endpoint.
    3. Let the SIP gateway terminate the carrier while Pipecat drives the conversation.

    The bot needs no FirstByte code. This is the same path as
    [migrating a custom WebSocket audio sender](/modalities/voice/migration/from-websocket-audio).
  
  
    To use QUIC/MoQT directly rather than a WebSocket leg, treat Pipecat as a
    [custom runtime](/modalities/voice/integrations/custom-runtime). The native
    SDK exposes MoQT publish/subscribe over the QUIC transport. You bind
    Pipecat's `input()`/`output()` to those primitives. This is the manual
    version of the sketch above.

    > **NOTE:**
> Native SDK bindings speak QUIC/MoQT only. They do not carry the browser's
>       WebSocket/WebRTC fallback leg. Plan for a QUIC-reachable network.
  
  
    If your goal is really "keep my existing media backend," use the built-in
    [VENDOR_BRIDGE](/modalities/voice/runtime/keep-existing) node. It routes
    call media to an external LiveKit, Vapi, or Twilio backend via a sidecar.
    Pipecat is **not** a bridge target. But if your Pipecat bot fronts one of
    those backends, this path needs only configuration.
  

> **TIP:**
> You can want Pipecat only for the *pipeline*: cascaded ASR → LLM → TTS,
>   turn-taking, and tools. FirstByte's runtime already ships that as a
>   configured DAG with transport, telephony, and barge-in built in. To bring only
>   your **models** via provider credentials is often less work than to port a
>   Pipecat pipeline onto an adapter that does not exist yet.

## Related

  - **[Pipecat + Transport](/modalities/voice/integrations/pipecat)** — The full conceptual framing and the on-ramps that ship today.
  - **[Bridge a LiveKit Agent](/modalities/voice/cookbooks/bridge-livekit)** — The vendor-bridge sibling for a LiveKit backend.
  - **[Stream to your own ASR](/modalities/voice/cookbooks/stream-to-asr)** — The uplink-subscribe half of the bridge, on its own.
  - **[Keep Your Existing Runtime](/modalities/voice/runtime/keep-existing)** — Adopt only the transport and keep your own agent loop. This is the Pipecat pattern.
