The five hops
A turn walks the same path in both directions. The user’s audio climbs capture → encode → transport → runtime; the reply comes back runtime → transport → egress. Here is where the time goes and what actually dominates each hop.| Hop | What happens | What dominates it | Measured how |
|---|---|---|---|
| Capture | Mic → frames. Browser runs AEC/AGC/NS + jitter buffer; a phone leg is G.711 off the wire | One frame of buffering (20 ms) + the browser’s own jitter buffer | Client-side frame timestamps |
| Encode | Opus (browser/app legs) or G.711 (phone legs); runtime bus is 8 kHz PCM16 | Codec frame boundary — Opus is 20 ms/frame, ts_delta = 960 on a 48 kHz clock | SDK capture-to-publish delta |
| Transport | Encoded frames ride MoQT over WebTransport/QUIC on a single :443, or RTP for phone legs | One network RTT/2 each way; congestion state | Per-connection transport telemetry (RTT, cwnd) |
| Runtime | ASR → LLM → TTS (cascaded) or one speech-to-speech model | Almost always the LLM’s time-to-first-token, or the S2S model’s prefill | Runtime per-node tracing |
| Egress | Reply PCM → 20 ms pacer → Opus/G.711 back to the caller | The paced 20 ms tick; one frame of look-ahead | Egress pacer timestamps |
The runtime hop dominates the total by an order of magnitude. Transport and
codec hops are single- to low-double-digit milliseconds; a cascaded ASR→LLM→TTS
turn is hundreds. If you are chasing latency, start at the runtime and only then
tune transport. This page gives you both, in that order.
Capture and encode
On a browser or app leg the audio pipeline is the browser’s own — reused, not replaced. A loopbackRTCPeerConnection runs echo cancellation, gain control,
noise suppression, the Opus encoder, and the jitter buffer; an
RTCRtpScriptTransform in a Worker taps the encoded Opus frames and hands
them to the transport. See
WebRTC diversion for the
capture path in detail. The floor here is one Opus frame — 20 ms — plus
whatever depth the browser’s jitter buffer settles to on your network.
On a phone leg there is no browser pipeline: G.711 arrives in 20 ms RTP packets,
the media plane decodes to 8 kHz PCM16, and noise suppression runs on
ingress before the audio reaches the runtime. There is deliberately no
acoustic echo cancellation or AGC in the media path — PSTN legs lean on
VAD-gating instead, which is a
correctness choice, not a latency one.
Transport: QUIC earns its place here
This is the hop where our transport choice shows up as a number. In a controlled prototype rig over a WAN link (50 ms RTT, ~1% loss), measuring the same turn — end-of-user-speech to start-of-agent-speech — over QUIC versus a TCP+TLS baseline:| Turn latency (prototype rig, 50 ms RTT, ~1% loss) | QUIC | TCP + TLS |
|---|---|---|
| p50 | 91.2 ms | 193.0 ms |
| p99 | 91.9 ms | 193.8 ms |
The browser diversion tax
Reusing the browser’s encoder and shipping the tapped frames over QUIC instead of native SRTP/UDP is not free, but it is close to it. In the localhost diversion prototype, the JS-layer round trip measured 1.20 ms over QUIC versus 0.84 ms for raw WebRTC SRTP/UDP — a +0.36 ms tax for the tap and the QUIC hop. That is noise next to a single Opus frame, and it buys you a single:443 plane, no SFU, and encoded-frame E2EE readiness.
Runtime: where the turn is actually spent
The runtime hop is the whole ballgame. Two shapes:- Cascaded (ASR → LLM → TTS)
- Speech-to-speech (single model)
Three serial stages. The clock is:
- ASR finalize — how long after end-of-speech the transcriber emits a final. Streaming ASR overlaps most of this with the user still talking.
- LLM time-to-first-token — usually the single largest contributor, and the one you tune with model choice, prompt size, and self-hosted inference.
- TTS time-to-first-audio — how fast the first synthesized frame comes back; streaming TTS starts speaking before the sentence is done.
The hidden hop: endpointing
Before any of the five hops runs, the turn detector has to decide the user stopped. That decision has a cost, and it is real latency the caller feels:silence_threshold_ms— how long a pause must last before it counts as end-of-turn. Lower it and you respond faster but cut people off mid-thought; raise it and the agent feels sluggish. This is the single biggest knob you own.- Hold-and-confirm barge-in — an onset arms a pending barge that only fires if speech sustains past a short window (~300 ms), so a backchannel (“mm-hm”) doesn’t cancel the agent. It trades a little barge-in latency for not talking over the caller.
Measure your own turn
Do not trust our numbers — instrument yours. Turn latency is a client-observable quantity: you have both timestamps, so you can compute it without any server-side access.Stamp end-of-speech
Record the wall-clock time when your turn detector commits the turn (the
Commit decision, or the provider’s server-VAD end-of-turn signal). This is
t0.Stamp first audio-out
Record the arrival time of the first downlink audio frame on
voice/<sid>/downlink — the SDK hands you each frame. This is t1.
Turn latency = t1 − t0. Log it per turn and take p50/p95/p99 over a
call; a single number lies.Split transport out
Read per-connection RTT and congestion window from the QUIC transport
telemetry the platform exports to your analytics. If turn latency drifts up
but RTT is flat, the regression is in the runtime, not the network.
There is no packaged “turn-latency” dashboard panel today. You assemble the
view from the three signals above: client-side
t1 − t0, exported transport RTT,
and runtime node spans. The quality dashboards that do ship —
MOS, jitter & loss — measure
media quality, not turn latency; they are complementary, not a substitute.When the number is too high
| Symptom | Likely cause | Fix |
|---|---|---|
| Agent feels sluggish, RTT is flat | Endpointing window too long | Lower silence_threshold_ms; verify with t1 − t0 |
| Turn latency high, runtime spans small | Transport RTT/loss | Check QUIC RTT/cwnd; confirm UDP/:443 reachable, no TCP-proxy fallback |
| First token slow, everything else fine | LLM time-to-first-token | Smaller/warm model; trim prompt; self-hosted inference |
| Long tail (p99 ≫ p50), p50 fine | Head-of-line blocking on a TCP path | Ensure the QUIC/WebTransport rung is in use, not a WS/TCP fallback |
| Agent talks over the caller | Barge-in confirm too aggressive | Raise the hold-and-confirm window; see barge-in debugging |
Related
High latency runbook
Step-by-step diagnosis when turn latency is too high.
MOS, jitter & loss
Media-quality metrics — separate from turn latency, and how to read them.
Call traces
Follow a single call end to end across every hop.
Turn detection
Endpointing and barge-in — the knobs that shape the hidden hop.
Runtime overview
Cascaded vs speech-to-speech — the hop that dominates the turn.
Benchmarks
How the stack compares on latency, cost, and scale.