# Architecture

> One QUIC connection carries every modality that you build. This page shows what runs where.

You connect once. Everything that you build uses that one connection.
Voice, streams, robotics, games, and data all publish and subscribe over
the same QUIC link to the relay mesh, through one typed SDK. You pick the
modality that you need. You never wire a second transport, a second auth
scheme, or a second reconnect loop. This page shows what sits under that
connection and where each piece runs.

## The shape

```
              ┌──────────────────────────────────────────────────┐
              │              Your application code               │
              │     (Python / TS / Go / Rust / Java / .NET /     │
              │                  Unity .NET runtime)             │
              └────────────────────────┬─────────────────────────┘
                                       │ idiomatic methods
              ┌────────────────────────▼─────────────────────────┐
              │            FirstByte language SDK             │
              │  ┌────────┬────────┬──────────┬───────┬───────┐  │
              │  │ Voice  │Streams │ Robotics │ Games │ Data  │  │
              │  └────────┴────────┴──────────┴───────┴───────┘  │
              │              MoqtClient (substrate)              │
              └────────────────────────┬─────────────────────────┘
                                       │ C ABI: clutchcall_*
              ┌────────────────────────▼─────────────────────────┐
              │      C++23 core (one binary, all languages)      │
              │  - MoQT framer / parser                          │
              │  - subgroup-stream + datagram scheduler          │
              │  - capability routing + namespace auth           │
              │  - APM pipeline (AEC, AGC2, resampler)           │
              └────────────────────────┬─────────────────────────┘
                                       │ QUIC (UDP/443 + UDP/4433)
              ┌────────────────────────▼─────────────────────────┐
              │             FirstByte relay mesh              │
              │   Shard-per-core · SO_REUSEPORT packet dispatch  │
              │  Capability-based fan-out · namespace gating     │
              └────────────────────────┬─────────────────────────┘
                                       │ control plane
              ┌────────────────────────▼─────────────────────────┐
              │        FirstByte engine (per modality)         │
              │  Voice telephony · streams ingest/transcode ·    │
              │  agent runtime · webhook events · analytics      │
              └──────────────────────────────────────────────────┘
```

Every method that you call lands on a single MoQT client. The MoQT
client sits on top of the C++ core. The core speaks QUIC to the relay
mesh. The relay handles fan-out and capability routing. The engine
handles the control plane. You work in idiomatic methods at the top.
Every layer below is shared across every language and every modality.

## How your data moves: MoQT tracks

Everything that you publish reduces to **MoQT tracks**. Because of this,
you never manage delivery yourself. You name a track by
*(namespace, name)*. You attach a capability tag. You write frames. The
relay fans the track out to every subscriber that matched the
namespace + capability. You do not know who subscribed. A subscriber
does not know who you are. You can add or drop consumers at any time.
Your publish code never changes.

| Track kind     | Carries                                                  | Wire model         |
| -------------- | -------------------------------------------------------- | ------------------ |
| **Audio**      | Continuous voice (Opus / PCM / G.711)                    | subgroup stream    |
| **Video**      | Encoded video; group per keyframe                        | subgroup stream    |
| **Frame**      | Opaque binary with per-frame priority                    | subgroup OR datagram (`datagram=true`) |
| **Text**       | Reliable ordered messages                                | subgroup stream    |

Subgroup streams give in-order, reliable delivery per group. Datagrams
trade ordering for a sub-millisecond floor. You do not choose by hand.
The modality picks the correct lane per channel for you. See each
modality's concept page.

## The relay mesh

The relay makes your fan-out scale. You do not tune anything. The relay
is the same binary as the engine, with the built-in relay role enabled.
It runs shard-per-core: one shard per core. Each shard binds the same
UDP port with `SO_REUSEPORT`. An in-kernel classifier dispatches
incoming QUIC packets to the shard that owns the connection's
destination CID. Each shard:

- accepts QUIC handshakes (ECDSA P-256)
- maintains its share of MoQT sessions
- forwards published frames to subscribers without leaving the shard
- gates each publish/subscribe through the **namespace auth** hook
  (JWT verify with namespace-scoped claims)

Your frames stay low-latency at scale for three reasons. The relay's
data path is **zero-copy where the QUIC sequencer allows**. It is
GSO-batched on send. It uses UDP_GRO on receive. There are no per-hop
copies when your track fans out.

You can address POPs by DNS round-robin under `relay.firstbyte.fast`.
You can also address each edge by its POP code
(`relay-us.firstbyte.fast`, `relay-uk.firstbyte.fast`, …).

## Two ports, two stacks

The relay runs **two QUIC stacks on two ports**. It does not multiplex
on one port:

| Port | Stack            | Carries                                |
| ---- | ---------------- | -------------------------------------- |
| 443  | QUIC · MoQT      | MoQT (audio / video / frame / text tracks)     |
| 4433 | QUIC · HTTP/3    | HTTP/3 + WebTransport (REST, MCP, signalling) |

You do not pick a port. Your SDK finds the correct port through RFC 9460
HTTPS records. The split keeps each stack's SO_REUSEPORT DCID dispatch
intact. It also avoids the ~300-500 LOC of userspace ALPN-sniffed
routing that single-port multiplexing would need.

## Why every language behaves the same

You get identical wire behavior and identical latency in every language,
because one core sits under all of them. Two results follow from that.

**Identical wire envelopes.** The relay sees the same MoQT framing from
every SDK. A message that you send from Python looks the same as one
from Rust. There is no per-language parser to keep in sync. There is
only the C++ core, which every SDK imports via FFI / WASM.

**Audio without a copy.** The core does µ-law / A-law to 16-bit PCM
conversion in SIMD. Your voice path stays fast everywhere. Browser
(WASM) and Node / Python / Go / Rust / Java / .NET (native FFI) call
the same code path with the same latency profile.

## What happens when you connect

You call `connect` once. The SDK then does these steps:

1. **Connect.** The SDK dials MoQT on `relay.firstbyte.fast:443` over QUIC
   (or WebTransport in the browser). The SDK presents your tenant token as
   the first MoQT envelope.
2. **Handshake.** The relay's namespace_auth hook checks the token
   and stamps a namespace scope on the session.
3. **Publish / subscribe.** The SDK opens MoQT publish and subscribe
   requests on demand. The relay routes by namespace and capability.
4. **Auto-reconnect.** You do not write reconnect logic. If the link
   drops, the SDK reconnects with capped exponential backoff. It
   **re-establishes every publication and subscription** transparently.
   Your application code never sees the reconnect.

The same client also gives you a control plane. Call originate,
live-input create, room token mint, and similar operations run over
**HTTP/3 on port 4433** as control-plane (tRPC) procedures on the
engine. Each modality client internally holds a MoQT client (data
plane) and an HTTPS client (control plane). You talk to only one object.

## Where your SDK runs

The same core ships everywhere that you deploy. You get the same
behavior on every runtime.

- **Browsers.** The TypeScript SDK speaks MoQT directly over native
  WebTransport (no FFI, no custom framing). The C++ core is compiled to
  WebAssembly via Emscripten for audio APM + framing fast paths.
- **Native runtimes.** The SDK loads `clutchcall_core_ffi.so` / `.dylib` / `.dll`
  via the language's native loader: JNI (Java), P/Invoke (.NET), CGO
  (Go), `libloading` (Rust), `ctypes` (Python).
- **Unity.** The .NET runtime SDK plus a `com.clutchcall.transport` UPM
  package exposes `INetworkInterface` over the games modality. It is a
  drop-in for Unity Netcode for GameObjects / Entities. See
  [Netcode (Unity)](/modalities/netcode/details).

## Direct-media (voice)

When you run a server-side AI call (`default_app=AI_BIDIRECTIONAL_STREAM`),
your audio takes the shortest path. The voice path uses **direct-media**
between the carrier and the agent runtime. The gateway negotiates SIP
signalling with the carrier. Then it publishes the SDP answer that
points at the agent runtime's RTP socket. RTP flows straight from the
carrier to the runtime. The gateway is signalling-only on that path.

For SIP/RTP-only calls (no AI bridge), the gateway still terminates RTP
and runs a local VAD. Thus the same `call_sid` can take either RTP path.
The path depends on `default_app`.

## Legacy RPC (still supported)

The original control plane was a method-id RPC envelope over QUIC:

```
+----------------+----------------+----------------------+
| u32 length LE  | u32 method_id  | serde envelope body  |
+----------------+----------------+----------------------+
```

`ClutchCallClient` (`dial`, `originate_bulk`, `hangup`, `barge`,
`push_audio`, …) used this surface. It is kept for backwards compat.
New code should use the [Voice modality](/modalities/voice/overview). See
[Envelope Format](/rpc/envelope-format) for the full wire detail.

## Code generation

One IDL keeps the SDKs in lockstep across languages. Method IDs and DTO
definitions in every language come from one IDL
(`api/clutchcall.json`). A shared IDL code generator compiles it.
The modality clients' wire formats are generated from that same IDL. A
new modality method needs only an IDL edit plus a compiler run. It then
lands in every language at once.
