Product teardown · the voice arc, second half · 2026

The voice agent stack: every millisecond between a mouth and an ear, itemized.

The research half of this arc ends with models that answer in a hundred and sixty milliseconds. The product half begins with a support line where the caller says "hello?" twice before the agent replies. Both statements are true at the same time, and the gap between them is not a mystery — it is a ledger with about a dozen line items, most of which have nothing to do with the model. This is a teardown of that ledger: what each line costs, which architecture pays which lines, where the published numbers disagree and why, and what it takes to survive a real phone call.

SOURCES 4 primary, 2 papers DEPTH ledger to hand-arithmetic WIDGETS 12 interactive CHAPTERS 12

00 Concept constellation

Every idea in this teardown, clustered by what kind of idea it is, wired by what depends on what. Hover a node to read it; click to open its chapter.

A voice agent is the only kind of software where a two-hundred-millisecond mistake is audible. Text agents can think for four seconds and nobody notices; a voice agent that thinks for four seconds has already been talked over, hung up on, or asked "are you still there?" — and the caller now believes the company is broken.

That single constraint radiates outward into every layer of the stack. It decides which architecture you pick, where you host, which codec you accept, how long you wait before you decide the caller stopped talking, what you do with the audio already sitting in the playout buffer when they interrupt, and whether you say "one moment" out loud while a database query runs. This lesson walks the whole radius.

The graph below has four clusters. The constraint is the timing physics of human conversation and the failure modes that appear when you violate it. The architectures are the two ways to build the thing and the seam that separates them. The engineering is the unglamorous machinery — endpointing, barge-in, tool masking, transport — that turns a working demo into a system that survives a phone line. The evidence is how you know any of it works, and what the numbers can and cannot tell you.

Interactive · hover a node, click to open its chapter
The constraint Architectures Engineering Evidence & trust Links to an existing lesson

00.1 How to read this teardown

This is the product half of a two-part arc. The research half taught you how the models work. This half teaches you what the models cost when a stranger calls your support line from a car.

If you have read the veanors in this series — VALL‑E, Moshi, Qwen2.5‑Omni, PersonaPlex, Duplex‑SLA — you already know that a full-duplex speech model can hold a conversation with a theoretical latency of a hundred and sixty milliseconds. You may therefore be surprised to learn that essentially no enterprise voice deployment in 2026 uses one, and that the systems in production are chains of three separate models talking to each other over the network with a text string in between.

That is not conservatism, or at least not only conservatism. It is a set of specific, nameable constraints, and each one gets a chapter. The chapters run in dependency order:

  1. Chapters 01–03: the constraint and the ledger

    What actually breaks between a demo and a deployment; the two architectures animated side by side on the same conversation; and then the ledger itself — every line item from microphone to speaker, computed by hand and then made into a calculator you can drag.

    You cannot argue about architecture until you can price a turn
  2. Chapters 04–05: the honest case for each architecture

    Why the text boundary in the cascade is not a bug but the product's most valuable surface — and then, in the same detail, exactly what that boundary destroys and who should refuse to pay it.

    Both chapters are written to be persuasive; that is the point
  3. Chapters 06–09: the engineering nobody demos

    Endpointing, barge-in, tool-call masking, transport. This is where deployments actually die, and none of it is visible in a scripted demo video.

    Every one of these is a state machine with a race condition inside
  4. Chapters 10–11: evidence, trust, and the close

    How to measure a voice agent without fooling yourself, what consent and watermarking mean once cloning is a commodity, the build-versus-buy landscape, and the closing argument about the two ladders.

    The eval you design decides which failures you are allowed to see
Number discipline. Latency numbers in this field are published by parties with an interest in them, measured over different intervals, on different hardware, in different months. This lesson never quotes a single number as "the" number. Every figure carries its source and its measurement interval, and where sources disagree — and they disagree by a factor of four on the same quantity — the disagreement is shown as a range with an explanation of what the two parties were each actually timing. Chapter 03 is largely about that.

00.2 Concept index

The twenty ideas in this lesson, with the chapter that owns each one.

CH 01
The constraint

The conversational floor

Human speakers swap turns with gaps averaging around two hundred milliseconds. That number is not a preference; it is the budget every design must be scored against.

CH 01
The constraint

The demo-deployment gap

Six conditions a demo never sees: room noise, an eight-kilohertz codec, a mid-sentence pause, a barge-in, a slow tool, and a caller with an accent the ASR was not tuned for.

CH 02
Architectures

Cascaded pipeline

Streaming ASR, then a text LLM, then streaming TTS. Three models, two text boundaries, and complete component-level control.

CH 02
Architectures

End-to-end speech-to-speech

One multimodal model that consumes audio tokens and emits audio tokens. No mandatory transcript; nothing to intercept.

CH 02
Architectures

Half-cascade

The middle species: native audio in, text reasoning, synthesized audio out. What most "realtime API" products actually are.

CH 03
Architectures

Streaming and pipelining

Realtime does not come from a fast model. It comes from stages that overlap, and from a sentence buffer that decides when the TTS may start.

CH 03
The constraint

The latency ledger

Transport, jitter buffer, decode, endpointing, ASR, LLM time-to-first-token, TTS time-to-first-byte, encode, playout. Twelve lines, summed by hand.

CH 03
Evidence

Measurement interval

Mouth-to-ear turn gap versus platform turn gap versus model TTFA. Three numbers for what sounds like one quantity, differing by hundreds of milliseconds.

CH 04
Architectures

The text seam

The interception point between hearing and speaking where redaction, retrieval, policy and logging live. The cascade has one; end-to-end has none.

CH 04
Architectures

Per-component economics

Why a bundled audio-token price and a per-stage price behave completely differently as conversations get longer.

CH 05
Architectures

Paralinguistic loss

Tone, hesitation, sarcasm, laughter, pronunciation. Everything a transcript is designed to discard is exactly what a companion product needs.

CH 06
Engineering

Endpointing

Deciding the caller has finished. Silence timers, voice activity detection, prosody, and semantic endpointing — and the tail-latency spike each buys.

CH 06
Engineering

The tuning curve

False cuts versus dead air. One threshold, two failure modes, and an asymmetric penalty that puts the optimum far from the midpoint.

CH 07
Engineering

Barge-in state machine

Stop playback, cancel the stale generation, cancel in-flight tools, and repair the transcript to what the caller actually heard.

CH 07
Engineering

Context repair

The subtle one. If you interrupt at word nine of a thirty-word answer, the conversation history must say nine words, or the agent will believe things the caller never heard.

CH 08
Engineering

Latency masking

Acknowledgment fillers, parallel dispatch, and folding an async result into speech at the next sentence boundary.

CH 09
Engineering

Transport and jitter

WebRTC over UDP with loss concealment, WebSocket over TCP with head-of-line blocking, and telephony at eight kilohertz through a codec built in 1972.

CH 10
Evidence

Duplex benchmarks

Takeover rate, response latency, backchannel frequency, and judged quality — the four things a static clip-level metric cannot see.

CH 11
Evidence & trust

Consent and provenance

Cloning permission, audio watermarking, anti-spoofing on the input side, and disclosure — four different problems that get called "voice safety".

CH 11
Evidence & trust

The two ladders

Each research advance relaxes exactly one product constraint. Mapping the rungs is the fastest way to predict what ships next.

00.3 What we are reading, and how much weight each source carries

Four primary sources, two papers, and one rule about vendor numbers.

This lesson is built on four primary texts and two research papers. They are not equally reliable, and pretending otherwise would be the fastest way to teach you something false. Here is the honest accounting:

SourceWhat it isHow we use it
Salesforce tutorialarXiv 2603.05413, "Building Enterprise Realtime Voice Agents from Scratch." A March 2026 technical tutorial with a released nine-chapter codebase and measured benchmarks on named hardware.Highest weight. It reports its hardware (a single H200), its versions, its P50 and its minimum, and it publishes numbers that make its own approach look merely adequate. Every measured component latency in Chapter 03 traces here.
Twilio guide"A Guide to Core Latency in AI Voice Agents (Cascaded Edition)," November 2025, by a solutions architect at a telephony vendor.High weight on structure, medium on numbers. It gives the only clean decomposition of the network side of the ledger, and it is explicit that its figures are starting benchmarks rather than best-in-class. Its product pitch is clearly marked and we mark it too.
Inworld post"Cascaded vs Speech-to-Speech," July 2026, from an inference provider selling a cascaded product.Medium weight, read adversarially. Its argument — that the cascade is not inherently slower — is correct and well argued. Its first-party latency figures are vendor claims and are labelled as such, which is more honesty than most.
Softcery survey"Real-Time (S2S) vs Cascading (STT/TTS) Voice Agent Architecture," April 2026, from a consultancy that builds voice agents.Medium weight for landscape and third-party measurements; the only source giving a cross-vendor latency table with a named measurement service. Its cost figures are the most useful thing in it.
MoshiarXiv 2410.00037. The full-duplex speech-text foundation model.Used for the physics: the theoretical latency floor of a frame-based duplex model and the human turn-gap baseline it is measured against.
PersonaPlexarXiv 2602.06053, NVIDIA 2026. Voice and role control for full-duplex models.Used in Chapter 10 for the evaluation vocabulary and for the only cross-model duplex benchmark numbers in the lesson.
The rule for vendor numbers. A vendor's published latency is a measurement of something, taken by someone with an interest in the result, over an interval they chose. It is not a lie and it is not evidence. Treat every such figure as a claim with a measurement interval attached, and when two claims conflict, look first at whether they are timing the same interval. In Chapter 03 we will resolve an apparent factor-of-two disagreement between two of these sources by doing exactly that, and it will turn out neither of them was wrong.

One more thing that this lesson is not. It is not a build guide. The Salesforce tutorial already is one, with working code for every component, and it is excellent; if you want to type the thing in, start there. This lesson is the layer above: the model of why the stack has the shape it has, so that when your numbers do not match anybody's published numbers — and they will not — you know which line item to go and measure.

00.4 Concept check

Check: Two sources report the latency of a cascaded voice agent as 755 ms and 1,115 ms. What is the first thing to check?

Next chapter: six things that are true in production and false in every demo, and the one number that turns them from annoyances into failures.

01 The demo works. The deployment does not.

Every voice agent team lives the same two weeks: a prototype that feels magical on Tuesday and a pilot that gets escalated to the VP on the following Thursday.

The prototype is real. Someone wires a streaming speech recognizer to a fast model to a good text-to-speech voice, opens a browser tab, speaks into a laptop microphone in a quiet room, and the thing answers. It answers well. The voice is warm. The reasoning is correct. It calls a function and reads back an appointment slot. Everybody in the room feels the future arriving.

Then it goes onto a phone number. And the following things happen, in roughly this order, over the following two weeks:

  • The agent starts talking while the caller is still mid-sentence, because the caller paused to remember an order number and the endpoint detector called it a turn boundary.
  • The caller tries to interrupt the agent's long-winded answer. The agent keeps talking for another second and a half, then stops mid-word, then answers a question the caller had abandoned.
  • A caller with a Glaswegian accent gets a transcript that is confidently wrong, and the agent politely confirms an appointment for the wrong day.
  • A tool call to the scheduling API takes 2.1 seconds. The line goes completely silent. The caller says "hello?" The agent, which was waiting on the API, now has "hello" in its input queue and answers it.
  • Somebody calls from a car. The road noise keeps the voice activity detector permanently in the "speaking" state and the agent never gets a turn at all.
  • The whole thing sounds slightly worse than it did in the browser, because a phone call is eight kilohertz and the recognizer was trained at sixteen.

Not one of those failures is a model failure. Every single one is a timing or transport failure, and every single one is invisible in the demo because a demo is one person, in a quiet room, on a good network, asking a question they prepared, and not interrupting.

The thesis of this lesson, stated at the top. The hard part of a voice agent is not the voice and it is not the agent. It is the interface between them — the machinery that decides when to listen, when to stop listening, when to speak, when to stop speaking, and what to do with everything that was in flight when the caller changed their mind. The Salesforce tutorial puts the same idea more compactly: a voice agent is an LLM agent with voice input and output, and the hard part is the agent — but the part that fails in production is the I/O layer.

01.1 The conversational floor: where two hundred milliseconds comes from

The budget every design is scored against is not an engineering target. It is a measurement of human beings.

Conversation analysts have measured turn-taking gaps across languages for decades, and the result is remarkably stable: the modal gap between one speaker finishing and the next starting is close to zero, and the mean sits around two hundred milliseconds. The Moshi paper cites a figure of 230 ms as the average response time in natural conversation and designs its architecture explicitly to get under it. The Softcery survey uses a round 200 ms for the same quantity.

Two hundred milliseconds is a shockingly small number, and it is worth understanding why it is possible at all, because the reason has direct engineering consequences.

Producing a spoken word takes a human roughly 600 ms of planning. If a listener waited until the speaker finished before beginning to plan a reply, the minimum gap would be around 600 ms, not 200. The fact that real gaps are shorter proves something specific: humans predict the end of your turn and start planning their reply before you have finished speaking. They use syntax, prosody, and semantics to project the turn boundary, and they launch their own production process early enough that the words are ready when the gap arrives.

The consequence for architecture. Human turn-taking is not "detect end, then respond." It is "predict end, prepare response, release at the boundary." Every architecture in this lesson is somewhere on the spectrum between those two. A cascaded pipeline that waits for a final transcript before it starts the language model is doing the first thing, and pays the full serial cost. A full-duplex model that is continuously modelling both streams is doing the second, which is why its floor is so much lower. Most of the engineering in Chapters 06 through 08 is about buying pieces of the second behaviour without abandoning the first architecture.

Now, an important piece of honesty: no deployed voice agent hits 200 ms, and most products do not need to. Callers tolerate longer gaps from a machine than from a person, particularly if the gap is filled or the agent's speech is expressive. The Twilio guide's launch target for a straightforward cascaded agent is a mouth-to-ear turn gap of 1,115 ms with an upper limit of 1,400 ms — five to seven times the human figure — and agents at that latency are shipping and working.

So why does the human number matter? Because it tells you what you are trading against. Every millisecond you spend is a millisecond further from the conversational feel that makes the product worth building, and because the tolerance is not linear. Somewhere around a second, the caller stops experiencing a conversation and starts experiencing a system that is processing their request. Both can be acceptable products. They are not the same product.

01.2 Silence is a paralinguistic signal, not an absence

The reason latency is not merely annoying: people read meaning into gaps, and your system is emitting meaning it did not intend.

The Twilio guide makes an observation that is easy to skim past and worth stopping on: latency is critical to voice agents because silence functions as a paralinguistic signal. People attribute meaning to the sound of silence, and the rhythm of turn-taking shapes how the words are interpreted.

Consider what a 1,200 ms gap means when a human produces it. Ask a friend "can you cover my shift Saturday?" and count the silence before they answer. Zero to 200 ms reads as yes. Around a second reads as reluctance. Two seconds reads as a no that is being softened. The words that follow are interpreted through the gap.

Your voice agent is emitting these signals constantly and accidentally. A 1.4-second pause before "yes, that slot is available" is heard as hesitation about the slot. A pause that lands after the caller asks about a refund is heard as evasiveness. This is why latency is not merely a performance metric to be optimized when there is time: the latency profile of your agent is part of what it appears to be saying.

The corollary that surprises people. Uniform latency is better than variable latency even when the uniform value is higher. A system that always answers in 900 ms feels like a calm speaker with a slight processing delay, and callers adapt to it within two turns. A system that answers in 400 ms most of the time and 2,400 ms when a tool fires feels like a person who is hiding something on exactly the questions where the tool fires. Tail latency is not a percentile on a dashboard; it is a personality trait. This is the whole reason Chapter 08 exists.

01.3 Six conditions a demo never sees

Each of these has a chapter later. Here is the inventory, and what each one costs.

ConditionWhat it does to the stackChapter
Telephony audioThe public switched telephone network carries 8 kHz audio through the G.711 codec. Recognizers, and the speech-to-speech models, are overwhelmingly trained on 16 kHz or better. Accuracy drops, synthesis sounds thinner, and any model whose advantage was audio quality loses that advantage while keeping its price.09
Room and channel noiseA car, a café, a speakerphone in an open-plan office. Voice activity detection produces false positives, the recognizer's error rate climbs, and turn boundaries become mush. Noise suppression helps and costs both CPU and a little voice distortion.06, 09
The mid-sentence pause"My order number is… hang on… four-four-two." A fixed silence threshold of 500 ms cuts that caller off. Raising the threshold to 900 ms fixes it and adds 400 ms of dead air to every other turn in the system.06
Barge-inThe caller starts talking while the agent is speaking. You must stop playback, discard buffered audio already in flight, cancel a generation that is still streaming, cancel any tool call it launched, and rewrite the conversation history to contain only what the caller actually heard.07
Slow toolsA CRM lookup takes 1.8 s. The pipeline has nothing to say and no natural way to say nothing. Dead air on a phone line is the single most common trigger for "hello? hello?" — which then enters the input stream as a new user turn.08
Accents and code-switchingRecognition error rates published on curated benchmarks do not survive contact with real callers. A confident wrong transcript is worse than a low-confidence one, because the language model has no way to know it is being lied to.10

Notice the shape of this list. Only one entry — accents — is about model quality. The other five are about time, and specifically about what happens to work that is already in flight when the world changes. That is a distributed-systems problem wearing a speech-technology costume, and teams staffed entirely with machine-learning engineers are systematically surprised by it.

01.4 Widget: run the gauntlet

The same single conversational turn, sent through the demo conditions and then through the production conditions.

Below, one turn travels left to right through six gates. In demo mode every gate is in its friendly configuration: 16 kHz web audio, a quiet room, no pause, no interruption, a cached tool, a native-accent speaker. Press Production and the same turn runs through the gates in their realistic configuration.

Watch where the turn stops. The failure is annotated with the specific mechanism, and the counter tallies how many turns out of a hundred survive each gate under the current setting. The survival numbers are illustrative rather than measured — they are there to make the compounding visible, because the compounding is the actual lesson: six gates at 95% each is 74% end to end.

Widget 01 · the demo-to-deployment gauntlet
Robust gate Gate at risk in production Gate just failed The turn in flight Cumulative survival

The compounding is the point, and it is worth writing down explicitly because it explains a phenomenon every voice team observes and few can articulate: why the pilot feels so much worse than the demo even though every individual component tests fine.

Hand arithmetic — why "everything is 95% reliable" is a disaster.
Suppose six independent stages each work 95% of the time.
Stage 1: $0.95$.
Two stages: $0.95 \times 0.95 = 0.9025$.
Three: $0.9025 \times 0.95 = 0.857375$.
Four: $0.857375 \times 0.95 = 0.81450625$.
Five: $0.81450625 \times 0.95 = 0.7737809$.
Six: $0.7737809 \times 0.95 = 0.7350919$.

So $0.95^6 = 0.735$. Roughly one call in four hits at least one failure. In a demo you run three or four turns and see none of them; at a thousand calls a day you are generating 265 broken conversations daily, each of which is a person who now believes your company cannot answer its phone.

Push each stage to 99% and the picture changes completely: $0.99^6 = 0.9415$, one call in seventeen. The lesson is not "make the model better." It is that in a serial pipeline, reliability multiplies, so the return on fixing the worst stage is enormous and the return on polishing the best stage is nearly zero.

01.5 Why these six and not others

A short argument about which failures are structural and which are just bugs.

There are hundreds of ways a voice deployment can go wrong. The six above are singled out because each of them is structural: it follows from the architecture rather than from a defect, so no amount of careful coding removes it. You can only trade it against something else.

Take endpointing. There is no implementation of a turn detector that never cuts a caller off and never adds dead air, because the information required to distinguish "pausing to think" from "finished speaking" is sometimes genuinely not present in the audio at the moment you must decide. This is not an engineering gap; it is an information-theoretic one. You are forced to pick an operating point on a tradeoff curve, and Chapter 06 is about picking it well.

Or take barge-in. The caller's interruption and your outgoing audio are two events separated by network transit in both directions. Between the moment the caller's first syllable leaves their mouth and the moment your playback actually stops, some amount of audio necessarily plays. You can shrink that window — the Twilio guide's advice to colocate the recognizer, the synthesizer and the audio buffer is exactly about shrinking it — but you cannot make it zero, because information does not travel faster than the network.

The distinction worth internalizing. A bug has a fix. A structural failure has an operating point. If you find yourself in a design review promising to "solve" endpointing or "eliminate" barge-in stutter, you have misclassified a tradeoff as a defect — and the schedule you are about to commit to is fictional. The productive question is never "how do we remove this?" but "what is the exchange rate, and which side do we want to be wrong on?" For endpointing, interrupting the caller is roughly three times worse than making them wait, so you deliberately sit on the slow side of the curve. Chapter 06 derives that operating point numerically.

One last observation before we go to the architectures. Every one of the six conditions is worse on a phone line than in a browser, and the enterprise use cases — support, scheduling, intake, collections — are overwhelmingly phone. Meanwhile the impressive demos are overwhelmingly browser-based, because the browser gives you 48 kHz audio, hardware echo cancellation and a WebRTC stack that someone else debugged. The gap between the demo and the deployment is, to a first approximation, the gap between those two channels. Chapter 09 is about what actually lives in that gap.

01.6 Concept check

Check: Why does a uniform 900 ms response gap often produce a better user experience than a gap that is 400 ms most of the time and 2,400 ms when a tool call fires?

Next chapter: the showcase. One conversation, two architectures, running side by side with the clock visible on both.

02 One conversation, two machines, one clock

The showcase of this lesson. The same eleven words go into two architectures and we watch every stage of both.

The caller says: "Hi, can I move my Thursday appointment to next week?"

That is eleven words, about 2.8 seconds of speech. What happens next depends entirely on which of two machines is listening, and the difference is not a matter of degree. The two architectures do not do the same thing faster or slower; they represent the caller's utterance in fundamentally different objects, and everything downstream — what you can log, what you can filter, what you can swap, what you can hear — follows from that representational choice.

Before the animation, both machines get described precisely, with the actual data at every boundary. Architecture diagrams without shapes are decoration; the shapes are where the engineering lives.

What to hold on to through this chapter. A cascaded pipeline turns speech into a string and back. An end-to-end model turns speech into tokens in a shared audio-text space and back. Every advantage and every limitation of each architecture in the remaining ten chapters is a consequence of that one sentence.

02.1 The cascade, with every shape written down

Three models, two text boundaries, ten network traversals, one sentence buffer holding the whole thing together.

Here is the caller's utterance as it actually moves through a streaming cascaded pipeline, using the concrete configuration from the Salesforce tutorial — Deepgram for recognition, a vLLM-served model for reasoning, ElevenLabs for synthesis.

  1. Capture: microphone to PCM frames

    The browser's AudioWorklet captures at the device's native rate and buffers into 20 ms chunks. At 16 kHz mono with 16-bit samples that is 16000 × 0.020 × 2 = 640 bytes per chunk, sent as a binary WebSocket frame. Fifty frames per second, forever, in both directions.

    20 ms is the universal audio frame quantum: small enough to be inaudible, large enough that per-packet overhead does not dominate
  2. Streaming recognition: PCM to partial transcripts

    The recognizer holds a persistent WebSocket and emits two kinds of result. Partial transcripts (is_final=false) update as more audio arrives and are useful for on-screen feedback but must never be sent to the language model, because they get revised. Final transcripts (is_final=true) are stable. A third signal, speech_final=true, is the recognizer's opinion that the caller has stopped.

    The partial-final split is the first place teams lose a hundred milliseconds by waiting for finals they did not need
  3. The turn decision

    Something must convert "the recognizer thinks there was silence" into "the caller is done, start generating." That is the endpointer, and it is the single largest controllable line in the ledger. Chapter 06 is entirely about it.

    This stage has no model of its own in naive builds — it is a timer, and the timer is usually set at 500 ms
  4. Language model: string in, token stream out

    The conversation history plus the tool definitions go to an OpenAI-compatible endpoint with stream=true. Tokens come back one at a time over server-sent events. The number that matters is time-to-first-token; everything after the first token is hidden behind speech that is already playing.

    Measured on Qwen2.5-7B-Instruct via vLLM 0.8.5: TTFT 337 ms P50, 17.5 tokens/s, 34 ms between tokens
  5. The sentence buffer: the critical bridge

    Tokens cannot be sent to the synthesizer one at a time — a text-to-speech model needs a phrase to produce sensible prosody. So a buffer accumulates tokens and flushes on sentence boundaries: terminal punctuation followed by whitespace, excluding abbreviations and decimals, with a minimum length so fragments never go out, and a final flush when the stream ends.

    This is Pipecat's SentenceAggregator and LiveKit's text pipeline under different names; the Salesforce build measures the detection itself at 143 ms
  6. Streaming synthesis: text to audio chunks

    The first sentence goes to the synthesizer, which streams audio back as it generates. Time-to-first-byte 219–236 ms P50 in the Salesforce measurements; real-time factor 0.05–0.10, meaning it generates ten to twenty seconds of speech per second of compute. The second sentence is synthesized while the first is still playing.

    Because RTF is far below 1, synthesis never becomes the bottleneck after the first chunk — only the first chunk matters
  7. Playout: audio chunks to the caller's ear

    Server-to-client frames at 24 kHz go into a queue-based worklet that plays them smoothly, resampling as needed. This buffer is a jitter buffer: it trades a few tens of milliseconds of latency for immunity to network variance, and Chapter 09 shows the exact exchange rate.

    Every buffer in the chain is a latency purchase, and most of them were bought by default settings you did not choose

Two properties of this pipeline deserve emphasis because they are the source of nearly every misconception about cascaded latency.

First, the stages overlap. The naive mental model — recognize, then think, then speak — gives a turn-based total. The Salesforce tutorial writes it out: 400 + 800 + 400 = 1,600 ms if you wait for each stage to finish. Streaming changes the sum to recognition plus first sentence of generation plus first byte of synthesis: 400 + 300 + 200 = 900 ms. Same components, same models, 700 ms saved by never waiting for a stage to complete.

Second, the boundaries are strings. Between the recognizer and the model there is a UTF-8 string. Between the model and the synthesizer there is another. Both are inspectable, loggable, rewritable, and filterable by anything you care to run. Chapter 04 argues that this is the single most commercially important property of the architecture, and Chapter 05 argues about what it costs.

02.2 End-to-end speech-to-speech, with every shape written down

One model. Audio tokens in, audio tokens out, and no string anywhere on the critical path.

Now the same utterance through a native speech-to-speech model, using Moshi as the concrete reference because it is the one with a published architecture and a released serving stack.

  1. Capture: identical

    The same 20 ms PCM frames. Nothing at the microphone knows or cares which architecture is downstream.

    The divergence begins one layer up, not at the device
  2. Neural codec: waveform to discrete tokens

    A streaming neural codec turns the waveform into a small grid of integers. Moshi's Mimi runs at 12.5 frames per second with a residual stack of codebooks, and its first codebook is distilled to carry semantic content while the rest carry acoustic detail. So one second of speech becomes roughly 12.5 frames × K codebooks integers rather than 16,000 samples.

    12.5 Hz is chosen so an audio frame costs about the same as a text token — it makes speech affordable inside a transformer
  3. The model consumes both streams at once

    The defining property of a duplex model: it models the caller's stream and its own stream simultaneously, at every frame. There is no state in which it is "not listening." It is also predicting its own next audio token while the caller talks, which is what makes sub-200 ms response possible — the reply is already forming.

    This is the architectural version of human turn projection from Chapter 01
  4. Inner monologue: text as a scaffold, not a boundary

    Moshi predicts text tokens time-aligned with its own speech, ahead of the audio. This improves the linguistic quality of what it says, and it produces a transcript — but the transcript is a by-product of generation, not an interface the audio has to pass through.

    Crucial distinction for Chapter 04: a transcript that exists is not the same as a seam you can intervene at
  5. Decode: tokens back to waveform, streaming

    The codec decoder is streaming, so audio comes out frame by frame as tokens are produced. One frame is 80 ms at 12.5 Hz, which sets the floor: Moshi reports a theoretical latency of 160 ms and about 200 ms in practice.

    The floor is a property of the frame rate and the acoustic delay pattern, not of the GPU

Count what is missing from that list compared with the cascade: no endpoint decision, no sentence buffer, no inter-service network hops, no text-to-speech first-byte latency, no recognizer finalization delay. Four of the six largest line items in the cascaded ledger simply do not exist, which is why the architecture's floor is so much lower.

Where the 160 ms actually comes from. Mimi runs at 12.5 Hz, so one frame is $1/12.5 = 0.08$ s = 80 ms. A residual codec cannot predict all codebooks of a frame simultaneously without them being conditionally independent, so an acoustic delay is introduced: higher codebooks are shifted later in time so each can see the ones below it. With a delay of one frame between the semantic and acoustic levels, the model must produce two frames before the first can be fully decoded: $2 \times 80 = 160$ ms. The paper is explicit that a larger delay pattern of eight timesteps would cost $8 \times 80 = 640$ ms, and that the trimmed pattern buys the latency back. The latency floor of a duplex model is therefore a tokenizer design decision, chosen years before any product exists.

02.3 The half-cascade, which is what most "realtime" products actually are

Native audio in, text reasoning in the middle, synthesis out. Neither pure architecture, and commercially the most common one.

The clean two-way split is pedagogically useful and empirically wrong. The Softcery survey separates three species, and the middle one is where most shipping products live:

SpeciesPathReasoning substrateExamples cited in the sources
Chained / cascadedVoice → ASR → LLM → TTS → voiceText, with a string at each boundaryDeepgram + a served LLM + ElevenLabs; every production platform built on Pipecat or LiveKit
Half-cascadeVoice → audio encoder → text LLM → TTS → voiceText, but the input side keeps acoustic informationThe OpenAI realtime family and most "native audio" APIs; Ultravox on the open-source side
Native audioVoice → one model → voiceAudio tokens end to endMoshi, Step-Audio R1.1, Amazon Nova 2 Sonic

The half-cascade is a genuinely interesting engineering compromise: it recovers the input-side paralinguistics (the model hears the sarcasm) while keeping text reasoning (the model can be a good agent, and its plan is inspectable). What it gives up is output-side expressivity, because the voice is produced by a synthesizer conditioned on text, and the Softcery survey notes bluntly that integrated synthesis in these products is generally less natural than a specialized text-to-speech model.

The Salesforce tutorial's taxonomy is the same idea sorted by how natively speech is processed, and it adds a sharp practical observation. Until recently, no Level 1 or Level 2 model supported function calling at all — which for an enterprise voice agent is not a limitation but a disqualification. An agent that cannot check the schedule cannot book the appointment. Qwen3‑Omni is called out as the first speech-native model with tool calling via XML tags, and that single capability is what moved the architecture from "research demo" to "candidate."

02.4 Showcase: the race, with the clock visible

Both architectures, the same utterance, the same wall clock. Change the deployment preset and watch which one wins — the answer flips.

The widget below runs both pipelines against the same 2.8-second utterance. The top track is the cascade; each stage is a labelled bar with its own duration, and overlapping stages are drawn overlapping because they genuinely are. The bottom track is the speech-to-speech model. The vertical playhead is the shared clock, and the two big counters are the running time-to-first-audio for each architecture.

The preset selector is where the real teaching happens. It swaps in the measured numbers from different sources and deployments — and the winner changes. That is not a bug in the widget; it is the actual state of the field in 2026.

Widget 02 · SHOWCASE — cascade against end-to-end, same clock
Cascade stages End-to-end stages First audio reaches the ear 230 ms human gap

Three things to try, in order.

One. Turn streaming overlap off on the first preset. The cascade balloons from roughly three quarters of a second to over one and a half, because now each stage waits for the previous one to finish. This single toggle is the difference between the two Salesforce equations — 1,600 ms turn-based versus 900 ms streamed — and it is worth more than any model swap on the menu.

Two. Switch to the telephony preset. Both architectures get slower, but the end-to-end model gets slower for a reason that has nothing to do with the model: the public network leg and the codec transcode are charged to both, and they are a larger fraction of the smaller total.

Three. Switch to the research floor. Now the duplex model is at 200 ms and no amount of cascade tuning gets within three times of it. This is the honest picture: when a duplex model is running locally with no network in the path, the cascade is not in the same league.

02.5 Reading the race honestly

Four sources, four different claims about which architecture is faster, and all four are defensible.

Here is where the field actually stands, with each claim attributed:

ClaimSourceWhat was measured
Cascade 755 ms, cloud S2S 702 ms — near paritySalesforce, Mar 2026Their own streaming pipeline, measured end to end; the S2S figure is a cloud realtime API average TTFA. Self-hosted S2S was 145,694 ms on the same box, which is the real finding.
"A well-engineered cascade beats some end-to-end models"Inworld, Jul 2026Component budgets, with their own synthesis at roughly 100 ms median first audio. A vendor claim about a vendor product, and also correct.
S2S is lower latency; chained is "higher due to sequential handoffs"Softcery, Apr 2026Architectural comparison. Their own table then lists third-party S2S measurements of 0.78–2.98 s, which is slower than several cascades. The prose and the table disagree.
160 ms theoretical, 200 ms practicalMoshi, 2024Model-level latency on a local GPU, excluding all network and telephony. Not comparable to any product number in this table without saying so.
The resolution, and it is not a compromise. "Which architecture is faster" is not a well-posed question, because latency is dominated by terms that belong to neither architecture: the public network leg, the endpoint decision, and the language model's time to first token. Inworld says this plainly and it is the single most useful sentence in any of these sources — if your voice-to-voice latency is bad, the fix is almost never abandoning the cascade; it is a faster model, a shorter prompt, or better endpointing.

The architectures do differ in floor. A duplex model's floor is around 200 ms and a cascade's is around 600 ms. But most deployments are running at 1,000–1,400 ms, which is far above both floors — so the architecture is not what is costing them the second. Something else is, and Chapter 03 finds it.

There is one asymmetry the sources agree on completely, and it decides more architecture choices than latency does: self-hosting. The Salesforce evaluation is the cleanest evidence in the literature. Qwen3‑Omni's optimized talker exists only behind a cloud API; served locally with vLLM you can run the thinker (audio to text, 516 ms) but not the talker; run the full model on the standard transformers backend and time-to-first-audio is about 146 seconds. Not milliseconds. If your requirement is "runs entirely on our own GPUs," the end-to-end option is currently not available at any latency, and that is why the tutorial's conclusion is what it is.

02.6 Concept check

Check: A team measures their cascaded agent at 1,600 ms and their competitor's end-to-end agent at 900 ms, and concludes the cascade is architecturally too slow. What is the most likely error?

Next chapter: the ledger. Twelve line items, summed by hand, then turned into a calculator — and the moment where two published numbers that look contradictory turn out to be measuring different intervals.

03 Twelve lines, one number, no mysteries

If you cannot itemize your turn gap, you cannot optimize it, and you will spend three weeks swapping models to recover forty milliseconds that were sitting in a jitter buffer.

Every conversation about voice latency that goes badly goes badly for the same reason: the participants are quoting single numbers without saying what interval those numbers cover. The fix is boring and complete. Write down every line item between the caller's last syllable and the first sample of your reply reaching their ear, put a number on each, and add them up.

Twelve line items. Most are not the model. Several are not even software.

The definition we will use throughout. The mouth-to-ear turn gap begins when the caller stops speaking and ends when the first sample of the agent's reply arrives at their ear. It includes everything: the public network, the codec, the buffers, the endpoint decision, all three models, and the return path. The Twilio guide names this measurement precisely and contrasts it with the platform turn gap, which excludes the public-network legs. Almost every disagreement in the field is a confusion between these two, plus a third quantity — model-level time-to-first-audio — which excludes the endpoint decision as well.

03.1 The line items

What each one is, who controls it, and how big it typically is.

#Line itemTypicalWhat it is, and who controls it
01Transport in (public leg)40 msThe caller's device to your media edge, over the internet or the phone network. You control it only by choosing where the edge is. The worst-behaved line in the ledger, because last-mile congestion and packet loss are somebody else's problem.
02Jitter buffer in30 msDeliberate delay so that packets arriving out of order or late can still be played in sequence. Pure latency purchased with pure robustness. Chapter 09 derives the exchange rate.
03Decode / transcode in25 msThe wire format is rarely the model's format. G.711 at 8 kHz to linear PCM at 16 kHz costs a resample; a hidden conversion inside a provider SDK costs the same and you did not know you bought it.
04Endpoint decision0–700 msThe silence you wait through before declaring the turn over. Usually folded into the recognizer's figure, which is exactly why it hides. Fully under your control, and the largest single lever you have.
05Recognition to final transcript350 msTwilio's launch target, upper limit 500. Salesforce measured 337–509 ms P50 with a 184 ms minimum on a commercial streaming recognizer.
06Inter-service hops8 × 10 msEvery handoff between orchestrator, recognizer, model and synthesizer. Individually trivial, collectively 80 ms, and they multiply if your services are in different regions.
07Model time-to-first-token375 msTwilio target, upper limit 750. Salesforce measured 337 ms P50 self-hosted and 457 ms P50 on a cloud API, with the cloud figure ranging 278–784 ms. The highest-variance line in the ledger.
08Sentence-boundary detection143 msWaiting for enough tokens to make a speakable phrase. Salesforce measured this separately, which almost nobody does, and it is not small.
09Synthesis to first byte100–250 msTwilio target 100, upper limit 250. Salesforce measured 219–236 ms on a commercial API. Inworld claims roughly 100 ms median on their small model and sub-250 ms P90 on their large one. The most consistent line in the ledger.
10Encode out25 msBack to the wire format. Same conversion cost, in the other direction.
11Jitter buffer out30 msThe playout buffer on the caller's side, which is also the buffer you must flush when they interrupt you. Chapter 07.
12Transport out (public leg)40 msSymmetric with line 01, and equally out of your hands.

Look at the shape of the ledger before we sum it. Lines 05, 07 and 09 — the three models — total about 825 ms of the roughly 1,100. Lines 01, 02, 03, 06, 10, 11 and 12 — the plumbing — total about 270 ms. And line 04, the endpoint decision, can be anywhere from zero to 700 depending on one configuration value.

That last point deserves its own sentence. A single timer setting can be the largest line in your ledger. A team that spends a quarter migrating to a faster inference provider to save 100 ms of time-to-first-token, while running a 700 ms fixed silence threshold, has optimized the wrong thing by a factor of seven.

03.2 Hand-worked: summing the ledger, one line at a time

No shortcuts. Every partial sum written out, then two cross-checks against published totals.

The Twilio guide gives a figure for each stage and a total of roughly 1.1 seconds, but it does not show the addition. We will do the addition, because the residual is informative.

Step 1 — the inbound path.
Transport in: $40$ ms. Running total: $40$.
Jitter buffer in: $+30 \Rightarrow 70$.
Decode in: $+25 \Rightarrow 95$.
So 95 ms is spent before a single sample reaches your recognizer. Nothing intelligent has happened yet.

Step 2 — the recognition stage.
First inter-service hop, edge to orchestrator: $+10 \Rightarrow 105$.
Recognition including its own end-of-turn logic: $+350 \Rightarrow 455$.
Hop, recognizer to orchestrator: $+10 \Rightarrow 465$.

Step 3 — the reasoning stage.
Hop, orchestrator to model: $+10 \Rightarrow 475$.
Time to first token: $+375 \Rightarrow 850$.
Hop, model back to orchestrator: $+10 \Rightarrow 860$.

Step 4 — the synthesis stage.
Hop, orchestrator to synthesizer: $+10 \Rightarrow 870$.
Synthesis to first byte: $+100 \Rightarrow 970$.
Hop, synthesizer back: $+10 \Rightarrow 980$.

Step 5 — the outbound path.
Encode: $+25 \Rightarrow 1005$.
Jitter buffer out: $+30 \Rightarrow 1035$.
Transport out: $+40 \Rightarrow 1075$.
Two more hops to and from the media edge: $+20 \Rightarrow \mathbf{1095}$ ms.

Cross-check. Twilio publishes a mouth-to-ear launch target of 1,115 ms. Our reconstruction gives 1,095. The residual is 20 ms — about two more service hops, or the orchestration overhead their prose mentions but their diagram does not itemize. A 1.8% residual on a twelve-line reconstruction means we have the right model of their system.

Now the second, more interesting piece of arithmetic. Twilio publishes two totals: mouth-to-ear 1,115 ms and platform 885 ms. The difference is defined to be everything outside the platform — the audio's journey across the public internet or the phone network, in both directions.

Deriving the public-network cost from two published totals.
$\text{public legs} = 1115 - 885 = \mathbf{230}$ ms round trip, so about $115$ ms each way.

Now compare that with the diagram, which shows only $40$ ms for device to media edge. Those two numbers are inconsistent by a factor of nearly three, and the inconsistency is the finding, not an error.

Why they differ. The 40 ms figure is one specific hop: caller to the nearest media edge. The 230 ms figure is the entire difference between "inside the platform" and "at the caller's ear," which additionally contains the jitter buffers ($2 \times 30 = 60$), the codec conversions ($2 \times 25 = 50$), and the edge-to-platform relay in both directions. Adding those: $80 + 60 + 50 = 190$, leaving $40$ ms for the private relay legs. Consistent.

Independent check. The Softcery survey states that phone networks add 100–200 ms of fixed latency. Our derived 230 ms round trip, or 115 ms each way, sits inside that band. Two unrelated sources, one arrived at by subtraction, agree.

This is what number discipline buys you. We now have a defensible model of where a second of latency goes, assembled from a vendor diagram, a vendor target table, and a consultancy's throwaway sentence — and the three agree to within tens of milliseconds once you are careful about what each is measuring.

03.3 Three numbers for one quantity

Reconciling the 755 ms that Salesforce measured with the 1,115 ms that Twilio targets. Neither is wrong.

The Salesforce tutorial reports a measured time-to-first-audio of 755 ms, with a best case of 729. Twilio targets 1,115. These describe the same product category and differ by nearly 50%. Here is the reconciliation, and it is entirely mechanical.

Read the Salesforce pipeline test line by line. It lists model time-to-first-token 296 ms, sentence detection 143 ms, synthesis 316 ms, measured time-to-first-audio 755 ms. Add the three components: $296 + 143 + 316 = 755$. Exactly. The reported total is the sum of those three stages and nothing else.

What the 755 ms therefore excludes. It starts when the transcript is available. It contains no transport, no jitter buffer, no codec conversion, no endpoint decision, and no recognition — all of which Twilio's 1,115 ms includes. Salesforce measured the recognizer separately at 337 ms P50 and reports it in a different table.

Rebuild their number on Twilio's interval:
$755$ (their measured stages) $+\ 337$ (their measured recognition) $= 1092$ ms — a platform-style figure, against Twilio's platform target of 885 and upper limit of 1,100. Inside the band, near the top.
Add the derived public legs: $1092 + 230 = \mathbf{1322}$ ms mouth to ear — against Twilio's target of 1,115 and upper limit of 1,400. Again inside the band, again near the top.

Conclusion: the two sources agree. A browser-based self-hosted pipeline measured platform-side reports 755; the same class of system measured mouth-to-ear over a phone line reports something in the twelve-to-thirteen-hundred range. The apparent 50% disagreement was entirely an interval mismatch.

Keep this table when you read anybody's latency claim:

IntervalStarts atEnds atTypical figure
Model TTFAModel receives its inputModel emits first audio160–700 ms; excludes endpointing and all transport
Pipeline TTFAFinal transcript existsFirst audio byte leaves the synthesizerSalesforce 755 ms
Platform turn gapAudio enters the platformReply audio leaves the platformTwilio target 885 ms; their managed product reports p50 491, p95 713
Mouth-to-ear turn gapCaller stops speakingReply reaches the caller's earTwilio target 1,115 ms, upper limit 1,400
The number you should ask for. When a vendor quotes a latency, ask one question: does the clock start when the caller stops speaking? If the answer is no, the number is a component measurement and cannot be compared with anybody else's product number. Note that Twilio's own managed-orchestration figures (p50 491 ms, p95 713 ms) are platform-gap numbers, not mouth-to-ear — the footnote says so, in the footnote.

03.4 The overlap formula, derived

Why streaming changes a sum of durations into a sum of first-response times, and what that is worth.

In a turn-based pipeline, every stage runs to completion before the next begins, so the total is the sum of full durations:

Turn-based total $$T_{\text{turn}} = D_{\text{ASR}} + D_{\text{LLM}} + D_{\text{TTS}}$$
  • $D_{\text{ASR}}$ — time to produce the complete transcript
  • $D_{\text{LLM}}$ — time to generate the complete response, all tokens
  • $D_{\text{TTS}}$ — time to synthesize the complete audio

With the Salesforce illustrative values, $400 + 800 + 400 = 1600$ ms. In a streaming pipeline each stage begins as soon as its input has enough content to act on, so what matters is not each stage's duration but its latency to first useful output:

Streaming time-to-first-audio $$T_{\text{stream}} = D_{\text{ASR}} + L_{\text{LLM}} + L_{\text{TTS}}$$
  • $L_{\text{LLM}}$ — time to the first speakable unit: first token plus enough tokens to close a sentence
  • $L_{\text{TTS}}$ — time to first audio byte, not to complete synthesis
  • $D_{\text{ASR}}$ stays a full duration, because you cannot answer half a question

With the same values, $400 + 300 + 200 = 900$ ms. The 700 ms saved comes from two places: the model's remaining tokens are generated while the first sentence is being spoken, and the remaining sentences are synthesized while earlier audio plays.

The condition for the overlap to hold, and what breaks it. Streaming only hides the remaining work if the downstream stage consumes faster than the upstream stage produces. Speech plays at roughly 150 words per minute, or about 2.5 words per second. The measured pipeline generates 17.5 tokens per second, roughly 13 words per second. Since $13 > 2.5$ by a factor of five, generation outruns playback and the audio never starves.

Now break it. Put a reasoning model in the loop that emits 3 tokens per second, or under 2.3 words per second. Now $2.3 < 2.5$: the synthesizer runs out of text mid-answer and the caller hears the agent stall in the middle of its own sentence, which is worse than the original gap because it sounds like a malfunction rather than a pause. This is the real reason voice agents use small fast models and delegate hard reasoning to background systems — not that the big model is slow to start, but that it cannot keep up with a mouth.

Twilio makes the same architectural recommendation from the product side: real-time dialogue is driven by a lightweight conversational model optimized for latency and speaking style, while deeper reasoning is handled asynchronously by background systems. The derivation above is why that pattern exists, expressed in words per second.

03.5 Widget: the budget calculator

Drag any line item and watch the total move against four reference marks. This is the whole chapter in one control panel.

The stacked bar is your turn gap, drawn to scale, segment by segment. The vertical rules are the marks that matter: the human conversational gap at 230 ms, the point around 800 ms where callers still experience a conversation, Twilio's launch target at 1,115 ms, and their upper limit at 1,400 ms beyond which the pilot gets escalated.

Presets load the measured configurations from the sources. Toggling telephony adds the public-network legs and the codec transcode. Toggling streaming off replaces the three first-response latencies with full durations, so you can see the 1,600 ms sequential world.

Widget 03 · the latency ledger, live
Transport & buffers Endpoint wait Recognition Model Synthesis

One reconciliation first, because the widget's default total is higher than the 1,095 ms we computed in 03.2 and that is deliberate. Twilio folds the endpoint wait into its 350 ms recognition figure and never itemizes the sentence buffer. This widget separates both, so a default configuration lands near 1,740 ms — which is what a naively-configured agent actually does, and is exactly the 1,738 ms the budget function below computes with its default arguments.

The experiment worth running before you read on: load the Twilio components preset, then drag the endpoint wait from 500 down to 200 and watch 300 ms disappear in one gesture — more than any other control on the panel can buy you. Then read Chapter 06 to find out what that 300 ms actually costs, because it is not free and the bill arrives in a different currency.

03.6 The ledger in code: three versions

Manual sequential, explicit streaming, and the one-liner that expresses the whole pipeline.

Version 1 — the naive pipeline. Correct, readable, and 1,600 ms per turn. Almost every first implementation looks like this, because it is what the SDK examples show.

python — turn-based: each stage awaits the previous one
async def handle_turn_naive(audio_frames):
    # 1. Wait for the WHOLE transcript.
    transcript = await asr.transcribe(audio_frames)      # ~400 ms

    # 2. Wait for the WHOLE response. This is the killer:
    #    we block on the last token before speaking the first word.
    reply = await llm.complete(history + [transcript])   # ~800 ms

    # 3. Wait for the WHOLE audio file.
    wav = await tts.synthesize(reply)                   # ~400 ms

    await playout.write(wav)
    # Total: 400 + 800 + 400 = 1600 ms before the caller hears anything.

Version 2 — explicit streaming with the sentence buffer written out. Every step named, including the one that everybody forgets: the flush at the end of the stream.

python — streaming, step by step
ABBREV = {"dr.", "mr.", "mrs.", "ms.", "st.", "no.", "p.m.", "a.m."}

def is_boundary(buf: str) -> bool:
    # Rule 1: terminal punctuation followed by whitespace.
    if not re.search(r"[.!?]\s$", buf):
        return False
    # Rule 2: reject abbreviations -- "Dr. Chen" is not two sentences.
    tail = buf.strip().split()[-1].lower()
    if tail in ABBREV:
        return False
    # Rule 3: reject decimals -- "4.5 minutes" is not a boundary.
    if re.search(r"\d\.\s$", buf):
        return False
    # Rule 4: minimum length, so "Ok." does not become its own TTS request.
    return len(buf.strip()) >= 10

async def sentences(token_stream):
    buf = ""
    async for tok in token_stream:
        if not tok:                     # guard: empty SSE chunks are real
            continue
        buf += tok
        if is_boundary(buf):
            yield buf.strip()
            buf = ""
    if buf.strip():                  # the forgotten flush: last sentence
        yield buf.strip()          # has no trailing punctuation + space

async def handle_turn_streaming(transcript, history):
    # The LLM streams. We do not await it; we iterate it.
    tokens = llm.stream(history + [transcript])       # first token ~337 ms

    async for sentence in sentences(tokens):        # +143 ms to close one
        # TTS streams too. First chunk leaves in ~220 ms;
        # the rest arrives while the caller is already listening.
        async for chunk in tts.stream(sentence):
            await playout.write(chunk)
            if barge_in.is_set():                    # Chapter 07
                await playout.flush(); return

Version 3 — the one-liner. Once the two generators exist, the entire streaming pipeline is a composition, and the whole architecture fits on one line:

python — the pipeline as a composition
await playout.pipe(tts.stream_all(sentences(llm.stream(history))))

And the ledger itself, which is the function you should actually write first, before any of the above, so that you have a target to measure against:

python — the budget, as a function
def turn_gap_ms(*, endpoint=500, asr=350, ttft=375, sent=143,
                ttfb=100, hop=10, hops=8, telephony=True):
    plumbing = (30 + 25) * 2 + hop * hops           # buffers, codecs, hops
    public   = 40 * 2 if telephony else 0            # the two legs you do not own
    return plumbing + public + endpoint + asr + ttft + sent + ttfb

turn_gap_ms()                       # 1738 ms -- a default-configured agent
turn_gap_ms(endpoint=200, ttfb=100)   # 1438 ms -- one timer changed
turn_gap_ms(endpoint=200, ttft=180,
            asr=200, telephony=False)  # 1013 ms -- browser, tuned stack
Why write the budget function before the agent. It costs ten minutes and it turns every subsequent engineering decision into arithmetic. When somebody proposes moving to a bigger model, you evaluate the proposal by changing one argument. When the pilot comes back at 1,900 ms, you instrument each line and find the one that does not match its argument, instead of guessing. Almost no team does this, and it is the single highest-leverage hour in the project.

03.7 Concept check

Check: A pipeline streams tokens at 3 per second (about 2.3 words/s) into a synthesizer, and speech plays at about 2.5 words/s. What does the caller hear?

Next chapter: the strongest argument for the architecture that loses on paper — what the text boundary is actually worth.

04 The lossy boundary is the product

Every critique of the cascade names the same flaw: it destroys information at the text boundary. Every enterprise buying decision names the same feature: it exposes a text boundary.

Here is a sentence that sounds like a joke and is not: the reason enterprises buy the architecturally inferior system is that its inferiority is load-bearing.

The cascade's defining property is that at two points per turn, the entire state of the conversation is a UTF-8 string. That string is lossy — Chapter 05 is a full accounting of what it loses — but it is also inspectable, storable, searchable, diffable, redactable, injectable, and testable with tooling that already exists and that your compliance team already understands.

Inworld, who sell a cascaded product and are therefore worth reading adversarially, put the argument in one line: because every boundary is text, you can intercept it — run moderation, inject retrieved context, call tools, log the exact words both sides said. And then the sharper claim: end-to-end models offer no equivalent seam.

The word to keep. Seam. An architectural seam is a place where you can insert behaviour without modifying either side. The cascade has two per turn. A native speech-to-speech model has zero on the critical path — and a transcript emitted alongside generation, as Moshi's inner monologue does, is a log, not a seam, because nothing downstream is required to pass through it. You can read it. You cannot make the model's audio obey an edit you make to it.

04.1 What actually lives in the seam

Six things enterprises put between hearing and speaking, and what each costs in milliseconds.

This list is the reason the architecture wins procurement, so it is worth being specific rather than gesturing at "compliance."

InterceptorWhereWhat it doesTypical cost
PII redactionAfter recognitionDetect and mask card numbers, national IDs, dates of birth before they enter the model's context or your logs. In several jurisdictions this is the difference between a legal recording and an illegal one.5–30 ms (regex plus a small classifier)
Retrieval injectionAfter recognitionLook up the caller's account, recent orders, entitlements; splice them into the prompt. The retrieval runs concurrently with nothing else, so its latency is fully on the critical path unless you prefetch on call setup.20–200 ms, or 0 if prefetched
Policy guardrail (input)After recognitionClassify the turn: abuse, self-harm, attempted prompt injection, out-of-scope. Route to a human or a scripted response rather than to the model.15–60 ms for a small classifier
Policy guardrail (output)Before synthesisThe one that gets you sued if you skip it. Check the generated sentence for prohibited claims — medical advice, pricing commitments, legal opinions — before it becomes sound. Once it is audio in the caller's ear, you cannot unsay it.15–60 ms per sentence
Verbatim transcript logBoth boundariesThe evidentiary record. Not a summary, not an approximation: the exact strings that the model saw and the synthesizer spoke, timestamped, immutable.Asynchronous, ~0 on the critical path
Deterministic flow controlBefore the modelEnforce that identity verification happened before balance disclosure. A state machine outside the model that can refuse to forward a turn, or override a response that skipped a required step.<5 ms

Notice the asymmetry between the input-side and output-side guardrails. Input filtering is a convenience: if you miss an abusive turn, the model probably handles it acceptably. Output filtering is a hard requirement in regulated domains, and it has a property that makes the architecture choice nearly forced: it must run after generation and before sound.

Why output filtering forces the architecture. A guardrail must sit between "the system has decided what to say" and "the caller has heard it." In a cascade that interval is a string sitting in the sentence buffer, and it lasts as long as you want it to. In a native speech-to-speech model, generation and vocalization are the same act — the model emits audio tokens, and by the time you have a token sequence you could classify, the decoder has already produced the waveform. Your only options are to buffer the output audio and delay everything (paying latency, which was the reason you chose the architecture) or to transcribe your own output and filter after the fact (paying an entire recognition stage, and finding out about the violation after the caller heard it).

So the constraint chain is: regulated domainoutput guardrail requireda seam between decision and sound is requiredcascade. Latency never enters the argument. This is why the Inworld decision checklist puts compliance second, before any performance question — and why Softcery's conclusion, from a different vantage, is that chained pipelines remain the production default for telephony.

04.2 Widget: the interception seam

Switch interceptors on and watch two counters move in opposite directions: control coverage up, latency headroom down.

The widget draws one turn through both architectures. The cascade lane has six insertion points; toggle each interceptor and watch it appear in the lane, adding its cost to the turn gap. The end-to-end lane is drawn alongside with the same interceptors greyed out, because there is nowhere to put them.

The readout tracks three quantities: total turn gap, control coverage as a fraction of the six, and the remaining headroom before the 1,400 ms upper limit from Chapter 03. The design question this widget makes concrete is not "should we have guardrails" but "which guardrails fit in our budget, and what did we give up to afford them."

Widget 04 · the seam — what you can insert, and what it costs
Cascade lane End-to-end lane Interceptor active No insertion point

Press prefetch and watch the retrieval cost drop out of the critical path entirely. That is the general shape of every good latency fix in this chapter: not "remove the feature," but "move the feature off the turn." Retrieval that runs when the call connects, guardrail classifiers that run concurrently with the first sentence's synthesis, transcripts written asynchronously. Chapter 08 generalizes the trick to tool calls.

04.3 Debuggability: what a failed call looks like in each architecture

A concrete incident, traced twice.

Take a real-shaped incident. A caller reports that the agent confirmed the wrong appointment date. You have the call recording and whatever your platform logged. Here is the investigation in each architecture.

In a cascade

You open the turn log. You see the exact strings:

turn log — cascade
[14:02:11.204] asr.final    "move my thursday appointment to next week"
[14:02:11.560] tool.call    check_availability("2026-08-20")
[14:02:11.712] tool.result  {"slots": ["09:00", "14:30"]}
[14:02:12.030] llm.text     "I can move that to Tuesday the 20th at 9am."
[14:02:12.244] tts.spoken   "I can move that to Tuesday the 20th at 9am."

The bug is visible in four seconds: "next week" was resolved to the 20th, and the 20th is a Tuesday, but the caller's appointment was Thursday and they meant next Thursday. The defect is in date resolution, in the tool arguments, on the input side of the tool. You write a test with that exact transcript string, fix the resolver, and the test now guards the fix forever. Total cost: an afternoon.

In an end-to-end model

You have the audio. If the vendor emits a transcript you have an approximation of what was said. What you do not have is the intermediate decision — there is no place in the system where "the model resolved next week to the 20th" existed as an object. The model heard audio and produced audio, and the date arithmetic happened inside a forward pass.

Your options: prompt-engineer defensively and hope; add an explicit tool call for date resolution so at least that step becomes visible (which is you rebuilding a seam by hand, in the one place you could); or collect failing calls and fine-tune, which requires a data pipeline, a labelling process and a training budget to fix what was a four-line patch in the other architecture.

The honest counterpoint, because this chapter is one-sided by design. The cascade's log is not the truth either. It is the truth of the text pipeline. If the caller said "next Thursday" with clear emphasis on Thursday and the recognizer dropped the word, your beautiful log shows "next week" and the root cause — a recognition error caused by the emphasis pattern — is invisible in exactly the same way. You have moved the blind spot, not removed it. The difference is that recognition errors are a known, measurable, benchmarkable failure class with a number attached to it, and "the model resolved a date wrongly inside a forward pass" is not.

04.4 The economics, computed rather than asserted

Per-component pricing and bundled audio-token pricing behave differently as the call gets longer. Here is the arithmetic.

The cost story is where the sources are most useful, because they publish list prices with dates. Two structural facts drive everything:

  • Cascade cost is roughly linear in call duration. You pay recognition per minute of audio, synthesis per character spoken, and the model per token — and with prompt caching the repeated system prompt collapses to roughly a tenth of its price. Softcery quotes a chained pipeline at approximately $0.15 per minute with no context accumulation.
  • Some end-to-end pricing is super-linear. Where the model re-charges for accumulated context on every turn, cost per minute rises through the call. Softcery reports developer accounts of one realtime API at roughly $0.30 per minute baseline climbing past $1.50 per minute in long sessions, and a fifteen-minute session where audio input alone cost several dollars.
Hand arithmetic — a ten-minute support call, both ways.
Cascade. Recognition at $0.15 per hour is $0.0025$ per minute, so ten minutes costs $0.025$.
The agent speaks for roughly 40% of a support call: $0.4 \times 10 = 4$ minutes of speech.
At 800 characters per spoken minute that is $4 \times 800 = 3200$ characters.
Synthesis at $25 per million characters: $3200 \times 25 / 1{,}000{,}000 = \$0.08$.
Say the model adds $0.05$ for the call with caching on the system prompt.
Total: $0.025 + 0.08 + 0.05 = \mathbf{\$0.155}$, which is the $\$0.15$/min figure — wait. It is not. It is $\$0.0155$ per minute. The commonly quoted $0.15/min chained figure is roughly ten times our bottom-up estimate, because it bundles orchestration, telephony minutes and a more expensive model. Both numbers are real; they price different scopes.

Context-accumulating end-to-end. Take $\$0.30$ per minute at the start, rising to $\$1.50$ by minute thirty as context grows. Model the rise as linear in elapsed minutes: rate$(t) = 0.30 + 0.04t$.
Ten-minute call: $\int_0^{10}(0.30 + 0.04t)\,dt = 0.30(10) + 0.02(100) = 3.00 + 2.00 = \mathbf{\$5.00}$.
Thirty-minute call: $0.30(30) + 0.02(900) = 9.00 + 18.00 = \mathbf{\$27.00}$ — the second half of the call costs more than three times the first.

The structural point. Not "end-to-end is expensive" — flat-priced audio models exist at roughly $\$0.017$–$\$0.023$ per minute, cheaper than several cascades. The point is that per-turn context re-charging makes your unit economics a function of call duration, which is the one variable a support organization cannot control. A pricing model whose cost curve bends upward with the thing your customers do more of when they are frustrated is a business risk, not just a line item.

And the lever that only the cascade has: you can move one component. If the model is 60% of your cost, you swap in a smaller one for the routine 80% of turns and keep the expensive one for escalation, without touching the voice, the recognizer, or a single line of transport code. In a bundled architecture the price is a package, and the only lever is switching vendors.

04.5 The self-hosting argument, which currently ends the debate

For a large class of buyers the decision is made before any of the above, by a constraint that no amount of engineering removes.

Health, finance, government and defence buyers frequently have a hard requirement: the audio does not leave our perimeter. This is not preference. It is contractual, and sometimes statutory.

The Salesforce evaluation is the clearest evidence available on what that requirement does to the architecture menu. They took the strongest open-weights candidate and tried three deployments:

DeploymentTime to first audioSelf-hosted?Verdict
Cloud realtime API~702 msNoFast, streaming, works — and disqualified by the requirement.
Local vLLM, thinker only516 ms to textYesProduces text, not audio. Collapses recognition and reasoning into one stage, but still needs an external synthesizer.
Local transformers, full model~145,694 msYesAbout two and a half minutes to first audio. Not a latency problem; a category error.
Cascaded pipeline~755 msYesFull function calling, component-level control, and it runs on your GPUs today.

Their conclusion is blunt and, as of the paper's date, correct: until an optimized self-hostable talker serving solution exists, the cascaded pipeline remains the only viable architecture for fully self-hosted realtime voice agents.

What would change this. One thing: a high-throughput serving stack for the audio-generation half of a speech-native model, in the way that vLLM and its peers exist for text. The gap is not architectural and not research — the 702 ms cloud figure proves the model can do it. It is an inference-engineering gap, which historically closes in quarters rather than years. When it closes, the self-hosting argument in this section evaporates and only the seam argument in 04.1 remains. That is the single most predictable structural change in this field, and it is worth building your abstractions so that it does not hurt you.

Which is the last practical note of the chapter, and the one Inworld's checklist ends on: build the cascade behind a clean interface. Migrating cascade to end-to-end later means replacing a pipeline with a connection. Migrating the other way means rebuilding turn-taking, voices and observability from nothing. The asymmetry says: when uncertain, choose the architecture that is cheaper to leave.

04.6 Concept check

Check: A health product must block prohibited medical claims before the caller hears them. Why does that requirement effectively select the cascaded architecture?

Next chapter: the other side of the same coin, argued just as hard — everything the string throws away, and the products that cannot be built without it.

05 Four different sentences that produce one identical string

The previous chapter argued that the text boundary is the product. This one argues that the text boundary is a hole. Both are true, which is why the field has two architectures instead of one.

A caller says four words: "I said Thursday."

Here are four situations in which a person produces exactly those four words:

  • Flat, falling pitch, quiet. A neutral restatement. They are supplying information they assume you missed.
  • Rising pitch on "Thursday," slight elongation. Uncertainty. They are checking their own memory, and they would welcome being corrected.
  • Sharp stress on "said," clipped, faster, higher energy. Frustration. This is the second or third time, and the next thing they say may be "let me talk to a human."
  • Slow, low, elongated, breathy. Resignation or fatigue. They have given up on being understood and are going through the motions.

The recognizer transcribes all four as i said thursday. Not approximately — identically, character for character. And that is not a defect in the recognizer; it is the recognizer's design goal. A recognizer is trained to be invariant to exactly the acoustic variation that distinguishes these four situations, because otherwise it would fail whenever somebody had a cold.

The precise statement of the loss. A cascade does not degrade paralinguistic information; it projects it to zero. There is no lossy-but-recoverable version of the frustration in variant three sitting somewhere in the string. The string is an equivalence class, and all four utterances are the same member of it. Anything downstream that needs to distinguish them must be given a different input, and the only different input available is the audio the cascade already discarded.

Now the consequence for the agent's behaviour, which is what makes this a product argument rather than a signal-processing observation. In variant three the correct action is to stop confirming details and offer a human. In variant two the correct action is to gently re-state the date so the caller can confirm. In variant one the correct action is to proceed. A text-only agent must respond identically to all four, because it received identical input. It will pick whichever behaviour its prompt favours, and it will be right about a quarter of the time.

05.1 Hand-worked: how much information does the transcript keep?

Not a metaphor. A rate comparison in bits per second, computed three ways, with every step shown.

We can put a number on "how much is thrown away," and it is worth doing because the number is more extreme than people expect. Three representations of the same one second of speech.

Step 1 — the raw waveform.
16 kHz sampling, 16 bits per sample, mono:
$16{,}000 \times 16 = 256{,}000$ bits per second $= \mathbf{256}$ kbps.
This is an upper bound on everything the microphone captured, most of which is not linguistically meaningful.

Step 2 — the neural codec representation.
Moshi's Mimi runs at 12.5 frames per second with 8 codebooks of 2,048 entries each.
Bits per codebook entry: $\log_2 2048 = 11$ bits.
Bits per frame: $8 \times 11 = 88$ bits.
Bits per second: $12.5 \times 88 = \mathbf{1{,}100}$ bits per second $= 1.1$ kbps.
So the codec compresses by a factor of $256{,}000 / 1{,}100 \approx 233$ and still reconstructs speech you would accept on a call. That factor is the measure of how much of a waveform is not speech.

Step 3 — the transcript.
Speech runs at roughly 150 words per minute $= 2.5$ words per second.
At about 1.3 subword tokens per English word: $2.5 \times 1.3 = 3.25$ tokens per second.
A 50,000-entry vocabulary gives at most $\log_2 50{,}000 \approx 15.6$ bits per token.
Transcript rate: $3.25 \times 15.6 \approx \mathbf{51}$ bits per second.

Step 4 — the ratio.
$51 / 1100 = 0.046$. The transcript preserves under 5% of the information the codec considered worth keeping.
Put the other way: the text boundary discards roughly $1100 - 51 = 1049$ bits per second, about $\mathbf{95\%}$ of a representation that was already compressed 233-fold specifically to throw away everything inaudible.
Three honest caveats on that 95%.
One. Coding rate is an upper bound on information, not a measurement of it. Real text entropy is well below 15.6 bits per token, and the codec's 1,100 bps contains redundancy too. The ratio is more robust than either absolute figure, but it is still an estimate.
Two. Not all bits are equally useful. Most of the codec's surplus encodes speaker timbre and room acoustics, which matter for reconstruction and rarely for deciding what to do next. The decision-relevant paralinguistic content — pitch contour, energy, rate, pause placement — is a small fraction of the 1,049.
Three. And that fraction is the point. Even at, say, 30 bits per second of genuinely decision-relevant prosody, you are discarding an amount comparable to the entire linguistic channel. The claim is not "audio has more bits." It is that a meaningful, decision-changing signal exists in the discarded remainder, and no downstream cleverness recovers it.

05.2 Widget: what survives the boundary

Pick a rendition, see its acoustic shape, then watch three architectures each report what they received.

The top panel draws the four renditions of "I said Thursday" — pitch contour in amber, energy envelope in moss, pauses marked. The panels below show what each architecture actually has available at the moment the reply is planned.

The cascade lane shows the transcript, which is identical in all four cases. The half-cascade lane shows the transcript plus retained acoustic features on the input side. The native audio lane shows the token stream, in which the four renditions are genuinely different sequences. Switch renditions and watch which lanes change and which do not.

Widget 05 · four renditions, three architectures, one string
Pitch contour Energy envelope Pause Available to the architecture

Turn on correct agent action and the argument becomes uncomfortable. Four situations, four different right answers, and one of the three lanes has the information needed to tell them apart on the input side while a second has it on both sides. The cascade lane is choosing blind, and its accuracy ceiling on this decision is set by the prior, not by the model.

One more observation the widget is built to make. The four contours differ most in the places where the words are identical, which is the same structural fact that makes prosody hard to evaluate: the difference between an accusation and a question can live entirely in the last two hundred milliseconds of the final word.

05.3 The sidecar workaround, and the two reasons it disappoints

Every cascade team eventually builds this. It helps. It does not close the gap, and it is worth understanding exactly why.

The obvious fix: keep the cascade, and run a small audio classifier alongside the recognizer that emits a label — emotion=frustrated, confidence=0.71 — which you splice into the prompt. This is a real technique, it is cheap, and it genuinely improves behaviour on the frustration case. It also has two structural limitations that no amount of implementation quality removes.

Reason one: quantization to a label

The classifier converts a continuous, multidimensional, time-varying signal into one categorical token. Compare the rates. The pitch contour over 900 ms of speech is a trajectory; the label is roughly $\log_2 6 \approx 2.6$ bits for a six-way emotion taxonomy, delivered once per turn. You have replaced a signal with a summary statistic, and the summary was designed by someone who did not know which distinction your product needs.

This bites hardest on the distinctions that matter most and are least standard. "Frustrated" is in every taxonomy. "Reading a number off a screen and about to correct themselves" is in none of them, and it is the one that would let you not interrupt.

Reason two: it arrives on the wrong side

The sidecar tells the agent something about the caller. It does nothing for the agent's own voice. If the correct response to a frustrated caller is to slow down, soften, and shorten — and it is — then the cascade must express that through the synthesizer, using whatever style controls the synthesis API exposes. Twilio's own taxonomy is useful here: neural voices give you coarse controls like pitch and rate and are deterministic; generative voices are expressive, non-deterministic, and capable of breaths and hesitations. So the output side is not hopeless, but the control surface is a handful of parameters rather than a continuous conditioning signal.

Where a native model differs, precisely. In a speech-native model the caller's audio tokens are in the context window. The model's own next audio token is conditioned on them directly, at frame resolution, without anybody choosing a taxonomy or a control parameter. Convergence in speaking rate, pitch range and even pronunciation is available for free because the conditioning is on the signal rather than on a summary of it.

That is the whole of the architectural argument for end-to-end, and it is a real one. Whether it is worth the seam you gave up in Chapter 04 depends entirely on whether your product's value is in what is said or in how the exchange feels — which is a product question, not an engineering one.

05.4 Pricing the half-cascade: what the middle option actually buys

Half the paralinguistic channel, one of the two seams, and a synthesis quality ceiling. Worth costing out rather than hand-waving.

Chapter 02 introduced the half-cascade as a species: native audio in, text reasoning, synthesized audio out. It is worth returning to now, with the information argument in hand, because it is the option most teams should be considering and the one that gets least analysis.

Take the four properties this arc keeps trading against and score all three architectures on each:

PropertyCascadeHalf-cascadeNative audio
Input paralinguisticsDestroyed at the boundaryPreserved — the encoder sees the waveformPreserved
Output expressivityWhatever your synthesizer exposes — and you can pick the best one on the marketWhatever the bundled synthesizer exposes, generally weaker than a specialist modelConditioned on the conversation at frame resolution
Input seamYes — redaction, retrieval, guardrails, spoof detectionNo text before reasoning; the audio goes straight inNo
Output seamYes — the guardrail that decides regulated deploymentsYes — reasoning is text, so the sentence exists before it is spokenNo

Read the third and fourth rows together, because that combination is the interesting part and it is easy to miss. A half-cascade keeps the output seam and loses the input seam. The model's plan exists as text before it becomes sound, so you can filter it — the Chapter 04 compliance argument survives. What you lose is the ability to inspect and rewrite what the caller said before it reaches the model, which is where redaction, retrieval injection and spoof detection live.

Which seam you actually needed. Go back through the six interceptors from 04.1 and sort them by which boundary they need:
Input side: PII redaction, retrieval injection, input guardrail, flow control, spoof detection.
Output side: output guardrail, and half of the transcript log.

So a half-cascade costs you five of seven interceptors and keeps the single most legally load-bearing one. Whether that trade is acceptable is not an architecture question — it is a question about which of those five your regulator, your fraud team and your retrieval strategy actually require. For a companion product the answer is often "none of them." For a bank the answer is "four."

There is one more cost that is easy to underrate. In a half-cascade you have no transcript of the caller's turn that is authoritative — there may be one produced for logging, but the model did not reason over it, so your evaluation set, your quality metrics, your analytics and your dispute records are all derived from a secondary artefact. Chapter 10's point applies with force: you cannot regression-test on a string the system never used.

And the quality ceiling is real and specific. The bundled synthesizer inside a multimodal product is optimized jointly with everything else, under latency pressure, and the sources are consistent that it lags specialist text-to-speech models on naturalness. If your brand voice is a competitive asset — and for consumer products it usually is — you are trading the thing your users actually notice for a paralinguistic channel they cannot name.

The honest summary of the middle option. A half-cascade is the right answer when you need the model to hear the caller and you also need a place to check what it is about to say — which is a real and common combination, and neither pure architecture serves it. It is the wrong answer when your input-side controls are load-bearing, or when your voice is part of the product. Notice that neither of those is a latency argument, and that is the pattern of this entire chapter: the architectural choice in voice is almost never decided by speed.

05.5 The products that cannot be built on a transcript

Four categories where the discarded 95% is the entire value proposition.

Inworld's framing is the cleanest in the sources: choose end-to-end when expressivity transfer is the product. Here is that criterion made concrete.

ProductWhy the transcript is fatalWhat the audio provides
CompanionsThe entire value is that it notices how you are. A companion that responds to your flat, exhausted "I'm fine" with a bright "great!" is not a degraded product; it is an actively unpleasant one.Energy, rate, pitch range, breathiness — the signals humans use for the same judgement
Language tutorsMispronunciation is invisible in a transcript, because the recognizer's language model helpfully corrects it. The student says the wrong vowel and the transcript shows the right word.Phonetic detail below the word level, which is exactly what the recognizer is built to normalize away
Social and game agentsLaughter, sighs, hesitation and overlap are the medium. A non-lexical sound has no transcript representation at all; it is either dropped or rendered as an unhelpful token.Non-lexical vocalizations as first-class content, and timing precise enough to laugh with rather than after
Clinical and wellbeing triageSpeech rate, pause structure and prosodic flattening carry clinically meaningful signal. Whether you should act on that is a serious ethical question — but a transcript forecloses it entirely.Temporal and prosodic structure across a whole session

And the mirror-image list, from the same source and equally worth respecting: end-to-end is also simply the pragmatic choice when you want one vendor, one connection and one bill; when your turns are short and conversational so the audio-token premium is small; when you were going to use that vendor's model anyway; and when a fixed voice set costs you nothing because any pleasant voice will do.

The decision rule, compressed to one question. Does your product need to hear how the caller spoke? If yes, go end-to-end and accept that you are giving up the seam, the per-component economics, and the self-hosting story. If no — and for scheduling, support, intake, collections and order management the answer is no — then the cascade gives you everything the transcript can express, plus six places to stand between hearing and speaking. Both answers are correct. Only pretending there is one answer is wrong.

05.6 Concept check

Check: A team adds an emotion classifier beside their recognizer, injecting a label into the prompt. Why does this improve the agent but not close the gap to a speech-native model?

Next chapter: the single largest controllable line in the ledger, and the tradeoff curve it sits on. Endpointing.

06 The hardest easy problem in the stack

Deciding that somebody has stopped talking sounds like a solved problem. It is the largest controllable line in the ledger and the most common cause of a pilot being cancelled.

Chapter 03 showed that the endpoint wait can be the single biggest item in the turn gap — 500 ms of pure silence, larger than recognition, larger than the model's time to first token. It is also the only line you can change by editing one number, which makes it irresistible to optimize and dangerous to optimize carelessly.

Here is why it is hard. The caller says:

"My account number is four four two ……… sorry, let me find it ……… eight one."

At the first gap the acoustic evidence is: silence. At the end of the utterance the acoustic evidence is: silence. If you decide on silence alone, you must pick a duration, and any duration you pick will be wrong in one direction or the other on some fraction of calls. That is not an implementation gap. The information needed to distinguish those two silences is frequently not in the audio at the moment you must decide — it is in the syntax of what was just said, in the caller's intonation contour, or in nothing at all because even a human listener would have to guess.

The asymmetry that shapes every design. Twilio states it plainly: end-of-turn detection is usually the long pole, because systems are tuned to avoid false positives. When the system mistakes a natural pause for the end of speech, the agent starts talking and interrupts the caller — which feels far worse than a little extra latency. So endpointers ship with padding, deliberately, and the padding is the price of not being rude. Section 06.2 puts a number on the exchange rate.

If you want the from-zero treatment of voice activity detection, endpointing and turn-taking — the acoustics, the state machines, the psycholinguistics — the gleam Voice turn-taking: VAD, endpointing and barge-in builds all of it from first principles. This chapter assumes it and goes straight to the production tradeoff.

06.1 Four signals, four generations of endpointer

Each generation adds a signal that resolves ambiguity the previous one could not see.

GenerationSignalResolvesFails on
Silence timerAudio energy below a threshold for a fixed durationNothing beyond "the line is quiet." It is a stopwatch.Room noise (never fires), breathing (fires early), any pause longer than the timer
Voice activity detectionA small trained model classifying speech versus non-speech per frameNoise. It distinguishes a fan from a voice, so the timer measures real silence rather than quiet.Still a timer underneath. A thinking pause is speech-free and looks exactly like a finished turn.
ProsodicPitch contour, final-syllable lengthening, energy decayA great deal. Human turn-final intonation is a real, measurable signal, and it is available before the silence begins.Speakers who do not follow the expected contour: some dialects, some neurodivergent speech patterns, anyone reading from a screen
SemanticThe transcript prefix, judged for completeness by a modelThe account-number case. "My account number is four four two" is syntactically incomplete; a model can see that and hold the turn open.Its own errors, and it needs the transcript — so it inherits recognition latency and recognition mistakes

Twilio compresses this into two buckets: fixed endpointing using silence timers or lightweight voice activity detection, and smart endpointing using trained models over advanced detection, acoustic features and semantics. And they add the detail that matters operationally: nearly all smart systems still include a raw-silence timeout as a fallback, because a model that decides the caller is still speaking must be overruled eventually.

That fallback is not a footnote. It is the mechanism behind the tail-latency behaviour in section 06.3, and if you do not know it is there you will misdiagnose the resulting latency histogram.

06.2 Hand-worked: choosing the threshold from data

A twenty-sample pause distribution, an explicit cost model, and the optimum computed four times.

Suppose you instrument a hundred calls and extract every within-turn pause — every silence where the caller was not finished. You observe twenty of them, in milliseconds:

Observed within-turn pauses, 100 turns, sorted (ms) $$120,\ 160,\ 180,\ 200,\ 220,\ 240,\ 260,\ 280,\ 300,\ 340,$$ $$380,\ 420,\ 480,\ 540,\ 600,\ 680,\ 760,\ 860,\ 980,\ 1200$$

Now define the two costs, because you cannot optimize until you have.

Cost of an endpoint threshold T, per 100 turns $$C(T) \;=\; 100 \cdot T \;+\; N(T) \cdot K$$
  • $100 \cdot T$ — every turn pays $T$ ms of dead air waiting out the timer, and there are 100 turns
  • $N(T)$ — the number of within-turn pauses at least as long as $T$; each one is a turn where the agent cuts the caller off
  • $K$ — the cost of one interruption, expressed in milliseconds-of-dead-air-equivalent. This is the number that encodes your product's values.
Step 1 — count the false cuts at each candidate threshold.
$T = 300$: pauses $\geq 300$ are $300, 340, 380, 420, 480, 540, 600, 680, 760, 860, 980, 1200 \Rightarrow N = 12$.
$T = 500$: pauses $\geq 500$ are $540, 600, 680, 760, 860, 980, 1200 \Rightarrow N = 7$.
$T = 700$: $760, 860, 980, 1200 \Rightarrow N = 4$.
$T = 900$: $980, 1200 \Rightarrow N = 2$.

Step 2 — pick K. Take an interruption as costing 6 seconds of dead air.
Justification: an interrupted caller stops, waits, repeats themselves, and often the agent has already committed to a wrong answer. Six seconds of recovery is conservative.
$K = 6000$ ms.

Step 3 — evaluate the cost function.
$C(300) = 100(300) + 12(6000) = 30{,}000 + 72{,}000 = 102{,}000$.
$C(500) = 100(500) + 7(6000) = 50{,}000 + 42{,}000 = \mathbf{92{,}000}$.
$C(700) = 100(700) + 4(6000) = 70{,}000 + 24{,}000 = 94{,}000$.
$C(900) = 100(900) + 2(6000) = 90{,}000 + 12{,}000 = 102{,}000$.

Step 4 — read the answer.
The minimum among these four candidates is at $T = 500$ ms, and the curve is flat near the bottom: 500 and 700 differ by 2%. A finer sweep of the same cost function — which the widget below does continuously — puts the true minimum at about $T = 430$ ms with a cost of 91,000; the four-point evaluation brackets it. The industry default of 500 ms is approximately optimal under a 6-second interruption cost — which is a satisfying result, and more importantly it tells you exactly which assumption the default encodes.

Now change the assumption and watch the answer move, because this is the part that turns a default into a decision.

Step 5 — sensitivity to K.
Chatty consumer app, interruptions cheap: $K = 2000$.
$C(300) = 30{,}000 + 24{,}000 = 54{,}000$; $C(500) = 50{,}000 + 14{,}000 = 64{,}000$; $C(700) = 70{,}000 + 8{,}000 = 78{,}000$.
Optimum moves down to about 300 ms. Snappier, occasionally rude, and correct for that product.

Regulated intake reading out card numbers, interruptions expensive: $K = 20{,}000$.
$C(500) = 50{,}000 + 140{,}000 = 190{,}000$; $C(700) = 70{,}000 + 80{,}000 = 150{,}000$; $C(900) = 90{,}000 + 40{,}000 = 130{,}000$; $C(1000)$: pauses $\geq 1000$ is just $1200 \Rightarrow N = 1$, so $100{,}000 + 20{,}000 = \mathbf{120{,}000}$.
Optimum moves up to about 1,000 ms. Slow, patient, and correct for that product.

The generalizable result. The optimal endpoint threshold spans a factor of three across product types, driven entirely by one judgement about how bad an interruption is. There is no universally correct value, and a team that copies a competitor's setting has copied their cost model without knowing it.

One caveat worth stating: this treats the pause distribution as fixed. It is not. It shifts with the caller population, with the question you just asked (open questions produce longer pauses than yes/no ones), and with the channel. The right practice is per-prompt thresholds — short after "is that correct?", long after "what can I help you with?" — which costs nothing and is skipped by nearly everyone.

06.3 Widget: the tuning curve

The same twenty pauses, drawn. Drag the threshold and watch both failure modes move against each other.

The top strip is the pause distribution: each mark is one within-turn pause from the dataset above, and the vertical line is your threshold. Marks to the right of the line are the calls where the agent cuts the caller off.

The lower panel plots three curves against threshold: false cuts per 100 turns, dead air per turn, and the combined cost with your chosen interruption penalty. The circle marks the optimum, and it moves as you drag $K$ — which is the point of the widget.

Widget 06 · endpoint threshold, both failure modes at once
False cuts per 100 turns Dead air per turn Combined cost Optimum

Turn on semantic endpointing and something interesting happens to the curves: the false-cut curve drops sharply, because the pauses that follow a syntactically incomplete phrase are now held open regardless of the timer. The cost minimum moves left — you can afford a much shorter timer — which is exactly the claimed benefit. Then look at the readout's tail figure, because that is where the bill arrives.

06.4 Semantic endpointing and the discrete tail

Smart endpointing lowers the median and creates a bimodal distribution. If you only watch the median, you will not see the thing your callers hate.

A semantic endpointer classifies the transcript prefix: does this look like a finished thought? If yes, fire almost immediately; if no, hold the turn open and fall back to a long silence timeout. That gives you two operating regimes in one system:

Latency under semantic endpointing $$L = \begin{cases} T_{\text{short}} & \text{model says complete, and it is} \\ T_{\text{short}} & \text{model says complete, and it is not (a false cut)} \\ T_{\text{long}} & \text{model says incomplete, and it is} \\ T_{\text{long}} & \text{model says incomplete, but the caller had finished} \end{cases}$$
  • $T_{\text{short}}$ — the aggressive timer used when the utterance parses as complete, perhaps 200 ms
  • $T_{\text{long}}$ — the fallback silence timeout, perhaps 900 ms
  • Row four is the failure that produces the tail: the caller is finished, the model disagrees, and everyone waits out the fallback
What the histogram looks like. Suppose the model calls 85% of finished turns "complete."
85 turns in 100 wait $T_{\text{short}} = 200$ ms.
15 turns in 100 wait $T_{\text{long}} = 900$ ms.
Mean: $0.85(200) + 0.15(900) = 170 + 135 = \mathbf{305}$ ms — better than a flat 500.
Median: $\mathbf{200}$ ms — dramatically better, and this is the number that goes in the launch post.
P90: $\mathbf{900}$ ms — because the top 15% all sit at exactly the fallback.

So the distribution is bimodal with a spike at the timeout. Twilio names this exact signature: a common sign of suboptimal smart endpointing is a discrete jump in the tail of your latency distribution roughly aligned with your endpointing silence timeout. If you see that spike, you are not looking at network variance or a slow model. You are looking at your semantic endpointer being wrong, and the height of the spike is its error rate.

Two operational consequences follow immediately.

First, report percentiles, not means. A mean of 305 ms conceals that one caller in seven experiences 900. And it is not a random seventh: the model's errors correlate with speaker characteristics — irregular pacing, non-standard grammar, accents, degraded call quality. Twilio says this outright. Your tail latency is unevenly distributed across your callers, and the people who get the slow experience are systematically the people who already have the worst time with automated systems. That is an accessibility issue wearing a performance-metric costume.

Second, measure the spike height as its own metric. The fraction of turns landing at exactly $T_{\text{long}}$ is a direct estimate of your semantic endpointer's false-negative rate, available for free from production telemetry, with no labelling required. It is the cheapest model-quality signal in the entire stack, and almost nobody instruments it.

06.5 Buying aggression back with graceful aborts

The clever move: make being wrong cheap, then you can afford to be wrong more often.

Everything above treats a false cut as a fixed cost. Twilio points out that it does not have to be, and this is the most useful engineering idea in their guide.

When the endpointer fires early, the pipeline does not immediately make sound. It has to generate a first token, close a sentence, synthesize the first chunk, and push the media to the edge — several hundred milliseconds during which nothing is audible yet. If, during that window, the caller resumes speaking, you can abort the whole thing and the caller never knows the endpointer made a mistake.

The abort window, computed from Chapter 03's ledger.
After the endpoint decision fires, first audio reaches the ear after roughly:
model TTFT $375$ + sentence detection $143$ + synthesis TTFB $100$ + encode/buffer/transport out $\approx 95$ + a couple of hops $\approx 20$, giving about $\mathbf{730}$ ms.

So there is a 730 ms window in which a false cut is completely invisible, provided you can abort everything inside it. Recall from 06.2 that moving the threshold from 700 down to 300 costs you 8 extra false cuts per 100 turns. If 730 ms of abort window catches, say, three quarters of those, you pay 2 audible interruptions instead of 8 — while banking 400 ms of latency on all 100 turns.

That is the best trade in this chapter, and it is bought entirely with engineering rather than with a model.

The abort itself has four steps, and skipping any one of them produces a distinct, recognizable bug:

  1. Clear the outgoing audio buffer immediately

    Anything already queued for playout will otherwise be spoken. Skip this and the caller hears a fragment of a word after they resumed.

    Symptom: a syllable of the aborted reply, then silence
  2. Abort the model stream

    Close the connection or cancel the request. Skip this and you pay for tokens nobody will hear, and the completion may later arrive and be spoken by a stale handler.

    Symptom: the agent answers a question from thirty seconds ago
  3. Cancel any open tool requests

    The aborted turn may have already dispatched a database write. Cancellation must be real, and where it cannot be, the tool must be idempotent.

    Symptom: two appointments booked from one conversation
  4. Clean up the conversation history

    The partial turn must not persist as though it were spoken. This is context repair, and Chapter 07 shows exactly how to get it right.

    Symptom: the agent references an answer the caller never heard
The race you cannot fully win. Aborting is only clean if the recognizer, the synthesizer and the audio buffer are tightly coupled. Twilio is explicit: latency between these services creates an unavoidable race condition that will produce a disorienting stutter some percentage of the time, and the tighter the coupling the smaller that percentage. This is the concrete, mechanical reason behind their otherwise-abstract advice to colocate the media services — and it is why "just deploy the recognizer in whichever region had capacity" is a decision with an audible consequence.

06.6 Concept check

Check: After enabling semantic endpointing, median latency drops from 500 ms to 210 ms, but the latency histogram grows a sharp spike at 900 ms. What is the spike?

Next chapter: what happens when the caller talks over you, and the four things that must be undone in the right order.

07 The caller talks over you. Now what?

Four things are in flight, three of them cost money, and one of them will corrupt the conversation history if you get it wrong.

The agent is thirty words into a forty-word answer. The caller says "no, wait —". At that instant your system has:

  • Audio playing in the caller's ear, and more audio queued in a buffer they cannot un-hear.
  • A language model still streaming tokens for a reply that has been overtaken by events.
  • Possibly a tool call in flight that the abandoned reply had launched.
  • A conversation history that currently claims the agent said forty words, when the caller heard about nine.

Handling this well is what separates a system that feels like a conversation from one that feels like an answering machine. Handling it badly produces the single most-reported complaint in voice pilots: "it doesn't let me interrupt" — and its subtler sibling, "it forgets what it just said."

Why this chapter is separate from endpointing. Endpointing decides when the caller has finished. Barge-in decides what to do when they start while you are talking. They use the same voice-activity signal and they are otherwise unrelated problems: endpointing is a statistical decision under uncertainty, barge-in is a distributed cancellation with a hard real-time deadline. Teams that conflate them end up with one threshold controlling both, and then discover they cannot tune it, because the right sensitivity for "should I start speaking" is not the right sensitivity for "should I stop speaking."

07.1 The state machine, with the interruption path drawn in

Five states, six transitions, and one path that everybody forgets to implement.

The Salesforce tutorial gives the canonical machine, built on a small voice-activity model — a two-megabyte network that processes 32 ms chunks in under a millisecond on CPU. States:

StateMeaningLeaves when
IDLENobody is speaking. The line is open and quiet.Voice activity detected → LISTENING
LISTENINGThe caller is speaking; audio is streaming to the recognizer; partial transcripts are arriving.Endpointer fires after its silence threshold → PROCESSING
PROCESSINGGeneration and synthesis are running. No audio is audible yet — this is the abort window from Chapter 06.First audio chunk queued for playout → SPEAKING
SPEAKINGThe agent's audio is playing. The microphone is still open, or should be.Playback completes → IDLE, or voice activity detected → INTERRUPTED
INTERRUPTEDThe transient state where cancellation happens. It should last milliseconds.All four cancellations complete → LISTENING

Two properties of this machine are worth stating because implementations routinely violate them.

The microphone is never closed. A machine that stops listening while in SPEAKING cannot be interrupted at all — it is half-duplex in the worst sense. Section 07.5 explains why teams close it anyway and what the right answer is.

INTERRUPTED is a real state, not a branch. It exists because cancellation is not atomic: four things must be undone, they complete at different times, and events arriving in between must go somewhere. Implement it as a branch inside SPEAKING and you get the classic bug where a second interruption during the first interruption's cleanup leaves the machine in a state that no code path can leave.

07.2 Hand-worked: how much audio does the caller hear after they start talking?

Every millisecond of that overlap is agent speech competing with the caller's voice. Here is the exact accounting.

Let $t = 0$ be the moment the caller's first syllable leaves their mouth. We want the time at which the last agent audio stops being audible.

Server-side detection, no client flush — the naive build.

Step 1: the caller's voice reaches your detector.
Transport in: $40$ ms. Jitter buffer in: $30$ ms. Running total: $70$ ms.

Step 2: the detector decides it is speech.
A frame-based detector needs a few frames of evidence plus a small hangover to reject clicks. At 32 ms frames and three frames of evidence: $\approx 96$ ms, call it $100$.
Running total: $70 + 100 = 170$ ms.

Step 3: the server stops sending.
Assume this is instant — it is a flag on a queue. Still $170$ ms.

Step 4: but audio is already downstream.
Audio sent before $t = 170$ is still in flight or buffered. Two contributions:
  in-flight on the wire: $40$ ms of transport;
  sitting in the client's playout buffer: whatever its depth is, commonly $200$ ms.
The last already-delivered sample therefore plays until $170 + 40 + 200 = \mathbf{410}$ ms.

Result: the caller hears 410 ms of agent speech after they started talking — roughly one word. That reads as the agent stubbornly finishing its thought, and it is the mechanical source of "it doesn't let me interrupt."
Now fix it two ways.

Fix 1 — flush the client buffer instead of merely stopping the send.
A control message tells the client to discard queued audio. It arrives after one downstream transport, $40$ ms.
Overlap becomes $170 + 40 = \mathbf{210}$ ms. The 200 ms buffer disappears from the calculation entirely.
Cost: one extra control message. Saving: 200 ms, or nearly half.

Fix 2 — detect on the client.
Run voice-activity detection in the browser or the edge, next to the playout buffer. Now step 1's upstream transport vanishes and the stop is local.
Overlap becomes $100 \text{ (detection)} + 0 \text{ (local stop)} = \mathbf{100}$ ms.
Cost: detection logic on the client, and the server must be told separately so it can cancel generation. Saving: another 110 ms.

The floor. Even perfectly implemented, you cannot go below the detector's own decision time, because you must hear enough of the caller to know they are speaking rather than coughing. That is $\approx 100$ ms and it is the same floor a human has — which is why people also occasionally talk over each other for a syllable before one yields. A 100 ms overlap is not a bug; it is what conversation sounds like. The 410 ms version is the bug.

Twilio's colocation advice now reads as arithmetic rather than as vendor guidance. Every service hop between the recognizer, the synthesizer and the audio buffer adds directly to the overlap window, and the overlap window is audible. Colocation is not a performance nicety here; it is the difference between an agent that yields and an agent that talks over its customers.

07.3 Widget: the barge-in race

Set the detection delay, the coupling latency and the buffer depth, then trigger an interruption and watch exactly what the caller hears.

The timeline shows both audio streams. The agent's speech runs along the top; the caller's interruption enters from below. The shaded overlap is the region where both are audible at once, annotated in milliseconds, and the four cancellation events are marked where they land.

The failure-mode readout is the part to pay attention to. Depending on your settings you will see clean yield, talk-over, the stutter (where playback stops, restarts because a queued chunk arrived after the flush, then stops again), and the zombie completion — where the cancelled generation finishes and gets spoken because nothing cancelled it.

Widget 07 · the interruption, millisecond by millisecond
Agent audio Caller audio Overlap the caller hears Cancellation events

Try the combination that produces the stutter: client flush on, but a long upstream transport. The flush command and a freshly-sent audio chunk cross on the wire, so the chunk arrives at a client that has already emptied its queue, and it plays. Silence, then a fragment, then silence. Callers describe this as "it glitched." Twilio describes it as an unavoidable race condition whose probability shrinks with coupling tightness, and they are right — the widget just lets you watch the probability.

07.4 Context repair: the bug that survives the demo

The other three cancellations produce audible symptoms. This one produces a conversation that quietly stops making sense.

Your history says the assistant said this:

conversation history — unrepaired
{"role": "assistant", "content":
 "I can move that appointment for you. I have Tuesday the twentieth
  at nine in the morning, or Thursday the twenty-second at two thirty
  in the afternoon. The Thursday slot is with Doctor Chen, who you saw
  last time, and I would recommend that one for continuity of care."}

The caller heard: "I can move that appointment for you. I have Tues—" before they interrupted with "no, next week."

If you leave the history as written, the agent now believes it has offered two slots and made a recommendation. Three turns later it says "as I mentioned, Doctor Chen would be best," and the caller has no idea what it is talking about. This is not a rare edge case; it happens on every single interruption, and it is invisible in testing because testers do not interrupt.

Hand-worked: how many words did they actually hear?

The synthesizer had streamed 47 words when the interruption landed.
Playback had been running for $2.30$ s at the moment of the stop.
Conversational speech runs at roughly 150 words per minute $= 2.5$ words per second.
Words played: $2.30 \times 2.5 = 5.75$.
Add the overlap window from 07.2, during which audio kept playing: $0.21$ s $\times\ 2.5 = 0.525$ words.
Total heard: $5.75 + 0.53 = 6.28$ words. Round down — a half-spoken word was not communicated.
Spoken prefix: 6 words. "I can move that appointment for" — and the history must say exactly that.

Why round down. Truncating short is safe: the agent may repeat something the caller already heard, which is mildly redundant. Truncating long is unsafe: the agent believes it communicated something it did not, and every later turn inherits the error. Asymmetric consequences, asymmetric rounding.

The better implementation does not estimate at all. Timestamp the audio chunks as you emit them, and have the playout layer report the position it actually reached:

python — context repair from real playout position
class SpokenTracker:
    def __init__(self):
        self.chunks = []          # (word_index_end, cumulative_ms)
        self.words  = []

    def emit(self, text, audio_ms):
        # Called for every synthesized chunk BEFORE it is queued.
        self.words.extend(text.split())
        prev = self.chunks[-1][1] if self.chunks else 0
        self.chunks.append((len(self.words), prev + audio_ms))

    def spoken_prefix(self, played_ms):
        # played_ms comes from the playout layer, not from a clock:
        # it is how much audio actually left the speaker.
        n = 0
        for word_end, cum_ms in self.chunks:
            if cum_ms <= played_ms:
                n = word_end
            else:
                # Partial chunk: interpolate within it, then floor.
                prev_ms = 0 if n == 0 else self._ms_at(n)
                frac = (played_ms - prev_ms) / max(1, cum_ms - prev_ms)
                n += int((word_end - n) * frac)   # floor: never overclaim
                break
        return " ".join(self.words[:n])

# On interruption:
heard = tracker.spoken_prefix(playout.played_ms())
history[-1]["content"] = heard + " --"          # the dash marks the cut
history.append({"role": "system",
                "content": "The user interrupted you mid-sentence."})

Two details in that snippet are doing more work than they look like they are.

The trailing -- tells the model that its own turn was cut off, which changes its next turn in exactly the right way: it stops re-explaining from the top and responds to what the caller actually said. And the system note is what lets it acknowledge the interruption naturally — "sure, go ahead" rather than starting a fresh paragraph.

The general principle, worth carrying to any streaming agent. In a system where output is produced faster than it is delivered, the model's belief about what it said and the user's experience of what was said are different objects, and only one of them is authoritative. The user's is. Any architecture that lets generated-but-undelivered content persist in the history will drift, and the drift compounds silently because each turn is individually plausible. Voice makes this vivid because the delivery rate is a physical constant, but the same bug exists in any streamed interface where the user can navigate away.

07.5 The echo paradox

To hear an interruption you must keep the microphone open. If you keep the microphone open, you hear yourself.

The agent's voice comes out of the caller's speaker and goes back into the caller's microphone. Without intervention, the recognizer transcribes the agent's own words, the voice detector sees continuous speech, and the system either interrupts itself or refuses to ever detect a real interruption.

Three mechanisms exist, and each one has a cost:

MechanismHow it worksWhat it costs
Acoustic echo cancellationThe client knows what it is playing, so it subtracts an adaptively-filtered copy of that signal from the microphone input. Browsers give you this free with one flag in the capture constraints; WebRTC stacks include it by default.Nothing on the web. On a phone line you are relying on network echo cancellers you do not control, and on a speakerphone in a reverberant room the adaptive filter can take a second to converge after any change.
Echo gatingAttenuate or ignore microphone input while the agent is speaking. The Salesforce build adds exactly this as a server-side gate on top of browser cancellation.It directly fights barge-in. A hard gate makes interruption impossible; a soft gate raises the effective detection threshold, so quiet interruptions are missed and the caller has to raise their voice — which they experience as the agent ignoring them.
Content-aware rejectionCompare what the recognizer heard against what you are currently speaking; discard input that matches your own output.Latency and complexity, and it fails on the exact case you care about — the caller repeating your words back to you, which is a common and meaningful conversational move.
The paradox, stated exactly, and the resolution. Barge-in sensitivity and echo rejection are the same knob turned in opposite directions. Turn it toward rejection and the system cannot be interrupted; turn it toward sensitivity and the system interrupts itself.

The resolution is not to find a better setting on that one knob. It is to use different information for the two decisions: echo is highly correlated with your own output signal, which you have a perfect copy of, while a real interruption is not. Cancel first — using the reference signal, which is what acoustic echo cancellation is — and only then apply an unbiased detector to the residual. Teams that skip cancellation and reach for a gate are trying to solve a subtraction problem with a threshold, and there is no setting of that threshold which is correct.

07.6 Concept check

Check: On every interruption, the agent later references information the caller never heard. Which cancellation step is missing?

Next chapter: the other source of dead air, and the three techniques for filling it without lying to the caller.

08 The two-second hole in the middle of your conversation

Everything so far assumed the agent knows what to say. A tool call is the case where it does not, and cannot, for as long as somebody else's API takes.

An enterprise voice agent is defined by its tools. The Salesforce build is a hospital receptionist with five of them — check availability, schedule, cancel, look up a patient, look up a doctor — and without them the whole thing is a very expensive way to say "I'm sorry, I can't help with that."

Tools also destroy your latency budget, and they do it in the ugliest possible way: not by adding a constant, but by adding a large variable amount on exactly the turns where the caller is most invested in the answer.

Here is the sequence, with the Chapter 03 ledger applied:

  1. The caller finishes speaking

    Endpoint wait, recognition. About 850 ms gone before anything intelligent happens.

    Same as every turn
  2. The model decides to call a tool

    It does not emit text; it emits a structured tool call. You pay time-to-first-token and then wait for the arguments to complete — and there is nothing speakable in a function call.

    The first place a tool turn diverges: no tokens the synthesizer can use
  3. The tool runs

    A scheduling system, a CRM, a legacy database behind three layers of middleware. 200 ms if you are lucky, 2–4 s if it is a real enterprise system, occasionally 10 s.

    Entirely outside your control, and frequently outside your monitoring
  4. The model runs again

    Results go back, and the model generates the actual answer. A second full time-to-first-token, plus sentence detection.

    You pay the model's start-up cost twice on a tool turn
  5. Finally, synthesis

    First audio byte, transport, and the caller hears something.

    By now they have said "hello?"
Why this is worse than an equivalent delay in a chat product. In a text interface a slow tool produces a spinner, which is an honest, legible signal that work is happening. Voice has no spinner. The channel's only idle state is silence, and Chapter 01 established that silence carries meaning — here it means "the line is dead" or "the agent did not understand." The caller then does the worst possible thing: they speak. And now their "hello?" is an input turn that your endpointer will happily process, generating a second request while the first is still in flight.

08.1 The tool loop, and the re-entrancy bug hiding in it

The standard agent loop is recursive. Voice adds a concurrent input stream that the loop was never designed for.

The Salesforce agent processes each utterance through a recursive loop: send history plus tool definitions to the model; if it returns tool calls, execute them, append the results, and repeat; if it returns text, stream it through the sentence buffer to synthesis. That loop is correct for text and incomplete for voice, because in voice a new user turn can arrive in the middle of it.

python — the loop, with the voice-specific guards marked
async def agent_turn(history, turn_id):
    for _ in range(MAX_TOOL_HOPS):              # bound it; voice cannot wait
        resp = await llm.complete(history, tools=TOOLS)

        if turn_id != current_turn_id:            # GUARD 1: superseded
            return                                # the caller moved on

        if not resp.tool_calls:
            await speak_streaming(resp, turn_id)
            return

        # GUARD 2: say something before a slow call.
        if expected_ms(resp.tool_calls) > FILLER_THRESHOLD_MS:
            await speak_filler(resp.tool_calls, turn_id)

        results = await asyncio.gather(*[            # GUARD 3: parallel,
            run_tool(c, timeout=TOOL_TIMEOUT_MS)      # and always bounded
            for c in resp.tool_calls
        ], return_exceptions=True)

        history += as_messages(resp.tool_calls, results)

Three guards, three real bugs.

Guard 1 — the superseded turn. Every asynchronous step must check whether the turn it belongs to is still the current one. Without this, a tool that returns after the caller has interrupted causes the agent to answer a question that was abandoned thirty seconds ago. This is the zombie completion from Chapter 07, arriving via the tool path instead of the generation path.

Guard 2 — speak before you wait. Covered in the next two sections.

Guard 3 — parallel and bounded. Two independent lookups must not be serialized: 900 ms plus 700 ms is 1,600 ms sequentially and 900 ms in parallel. And every tool needs a timeout shorter than the caller's patience, with a graceful spoken fallback. An unbounded tool call is an unbounded silence, and a caller who hangs up generates a support ticket about your product, not about the vendor whose API stalled.

Idempotency is not optional in a voice agent. Interruptions, timeouts and retries all create the same hazard: a write-side tool that may have executed even though you never saw the result. The caller interrupts during schedule_appointment; you cancel; did it write? Every mutating tool needs an idempotency key derived from the turn, so a retry collapses instead of duplicating. Voice generates far more of these cases than a text agent does, because voice gives users a way to cancel that has no equivalent of closing the tab.

08.2 Hand-worked: what the caller actually experiences

The same 1,800 ms tool call, priced with and without an acknowledgment. The difference is not what you would guess.

Setup. Ledger values from Chapter 03: endpoint wait 500, recognition 350, model TTFT 300, sentence detection 143, synthesis TTFB 100, transport out 95. The tool takes 1,800 ms.

Case A — no filler.
Caller stops speaking at $t = 0$.
Endpoint fires: $500$.
Transcript final: $500 + 350 = 850$.
Model emits the tool call — first token plus arguments, call it TTFT plus a little: $850 + 300 = 1150$.
Tool returns: $1150 + 1800 = 2950$.
Second generation, first token: $2950 + 300 = 3250$.
First speakable sentence: $3250 + 143 = 3393$.
First audio byte: $3393 + 100 = 3493$.
Reaches the ear: $3493 + 95 = \mathbf{3588}$ ms.
Perceived silence: 3.59 seconds. Well past the point where a caller says "hello?"

Case B — canned acknowledgment fired the moment the tool call is seen.
The filler is pre-synthesized, so it skips generation, sentence detection and synthesis: it is bytes on disk.
Filler starts at $1150 + 95 = 1245$ ms (just transport).
"Let me check that for you" is 6 words at 2.5 words/s $= 2400$ ms of audio.
Filler ends at $1245 + 2400 = 3645$ ms.
The real answer was ready at $3588$ ms — before the filler finished.

Perceived silence: 1.245 seconds (the gap before the filler), and then continuous speech.
Reduction: 3.59 s of dead air becomes 1.25 s, a 65% cut, with no change to any component latency.

Now the second-order observation, which is the one that separates people who have shipped this from people who have read about it.

The filler length must be tuned to the tool, and getting it wrong is audible in both directions.
If the filler is shorter than the remaining wait, you have split one silence into two: a gap, some speech, another gap. Two short silences are worse than one long one, because the second arrives after the caller has been reassured, so it reads as the system failing after promising to help.
If the filler is much longer than the wait, the answer is ready and queued behind speech the caller no longer needs, and you have added latency to a fast path.

The rule. Pick the filler from the tool's own latency distribution. For a tool whose P90 is 1,800 ms and whose median is 400, use a short filler ("one moment") and hold the long one for the tail — or better, emit an extensible filler: a short acknowledgment that can be followed by a second one if the tool is still running, so the length adapts to the actual wait instead of to your estimate of it.

And the cheapest trick of all, which costs nothing: fire the filler from the tool name, not from a model call. The moment you see check_availability you know a schedule lookup is happening, and a pre-recorded "let me check the schedule" is both faster and more specific than anything the model would have generated. Generating fillers with the model adds a whole time-to-first-token to the thing whose entire purpose is to eliminate a gap.

08.3 Widget: masking a slow tool

Drag the tool duration and the filler length; watch perceived silence and actual latency move independently.

The timeline shows one tool turn. The top track is real work: recognition, generation, tool execution, second generation, synthesis. The bottom track is what the caller hears: silence in terracotta, speech in amber. The two are not the same shape, and the gap between them is the entire discipline of latency masking.

The three modes are cumulative in sophistication: none, filler, and async fold-in — where the agent starts answering the part it already knows and splices the tool result in at the next sentence boundary.

Widget 08 · real latency against perceived latency
Work happening Caller hears speech Caller hears silence Tool result arrives

Set the tool to 400 ms with a 2,400 ms filler and watch the pathology: the answer is ready almost immediately and the caller sits through two and a half seconds of "let me check that for you" before hearing it. Masking applied to a fast path is pure added latency. This is why the filler decision belongs behind a threshold on the tool's expected duration, as in the loop above, and not on every tool call.

08.4 Three techniques, ranked by how much they buy

One is nearly free, one requires care, one changes what your agent is.

  1. Acknowledgment fillers — cheap, effective, slightly dishonest

    Pre-synthesized audio triggered by the tool name, behind a duration threshold. Sixty-five percent of the dead air on a slow tool turn, for a day of work and a folder of WAV files. Twilio explicitly excludes "generative interstitial fillers" from their core latency budget precisely because they are an orchestration-layer trick that hides latency rather than reducing it — which is a fair description and not a criticism.

    Buys: perceived latency. Costs: a small honesty problem, addressed in 08.5
  2. Speculative dispatch — free milliseconds, at the cost of wasted calls

    You often know which tool is needed before the model tells you. If the caller's partial transcript contains "reschedule" and an account is already identified, fire the availability lookup while the caller is still speaking. When the model asks for it, the result is already warm.

    Buys: up to the full tool duration. Costs: read-only tools only, ever, and a wasted-call rate you must monitor
  3. Async fold-in — the technique that makes the agent feel alive

    Do not stop talking. Answer the part you already know — "Sure, I can move that Thursday appointment" — while the lookup runs, and splice the result in at the next sentence boundary: "… and I have two slots next week." The caller experiences a continuous, thinking-aloud conversation partner instead of a request-response system.

    Buys: the entire gap on many turns. Costs: real orchestration complexity, and a hard requirement that what you say first is true regardless of the result

That last constraint is subtle and worth an example. "Sure, I can move that appointment" is unsafe to say before the lookup, because the lookup may reveal no availability. "Let me look at the schedule for next week" is safe, because it commits to nothing. Async fold-in requires a prefix that is informative but not load-bearing, and writing those prefixes is a real authoring skill.

Fold-in is also where this chapter meets the research half of the arc. A cascade fakes continuous speech-while-thinking with orchestration; a full-duplex model that synchronizes speech, language and action can do it natively, because generation never has to stop for the tool. That thesis is the subject of the Duplex‑SLA veanor, and it is the clearest example in the whole arc of a research advance that removes a product hack rather than making it faster.

08.5 The honesty constraint

Fillers are a claim about what the system is doing. Some of those claims are false, and callers eventually notice.

"Let me check that for you" asserts that a check is happening. If you fire that phrase on every turn as a latency blanket, including on turns where no tool runs, you have taught your agent to say something untrue several times per call. This matters for three reasons, in increasing order of seriousness.

It degrades. A filler that always plays stops carrying information and becomes a verbal tic. Callers learn to talk over it, which puts you back in Chapter 07 with extra steps.

It misleads about state. If the caller hears "let me check" and then the agent answers from its prompt without checking anything, they will reasonably believe the answer was verified against a system of record. In a regulated domain that is a compliance problem with a clear paper trail, because your transcript log from Chapter 04 contains the false claim in your agent's own voice.

It hides failures. The most damaging pattern is a filler that plays while a tool times out, followed by a model answer generated without the tool result. The caller heard the check happen and got an answer; nobody upstream knows the answer was unsourced. A filler must never be able to cover a failed call. If the tool fails, the agent must say so.

The rule that resolves all three. A filler is a promise, and it must be discharged. Fire it only when the action it describes has actually been dispatched; make it specific enough to be true and vague enough to be safe; and wire the failure path so a timeout produces "I'm having trouble reaching the scheduling system — can I take your number and call back?" rather than a fluent guess. That sentence costs you the call. Not saying it costs you the customer.

08.6 Concept check

Check: A team fires a 2.4-second acknowledgment filler on every tool call. Median tool latency is 400 ms. What happens?

Next chapter: the layer underneath all of it — packets, buffers, codecs, and a telephone network designed in the nineteen-seventies.

09 The layer nobody demos and everybody debugs

Two hundred and seventy milliseconds of the ledger, one entire class of production incident, and the reason your phone deployment sounds worse than your browser prototype.

Fifty times a second, in both directions, for the entire call, a 20 ms packet of audio has to cross a network that was not designed for it and arrive within a window narrow enough that nobody hears the seam. That is the transport layer, and it is where a surprising fraction of "the AI sounds bad" tickets actually live.

The numbers are small and the sensitivity is enormous. From Chapter 03: transport in 40, jitter buffer in 30, decode 25, and the same three on the way out. About 190 ms of a 1,100 ms budget, before any model runs. And unlike the model latencies, these lines are set by physics, geography and defaults you did not choose.

The framing that makes this chapter tractable. Every transport decision is the same trade: latency against robustness. A bigger jitter buffer means fewer glitches and more delay. A reliable protocol means no lost packets and a stall when one goes missing. A better codec means clearer audio and more compute. There is no configuration that is good at everything, and the correct choice depends on which failure your product can absorb.

09.1 Three transports, three failure modes

What each one does when a packet goes missing, which is the only question that matters.

TransportBuilt onOn packet lossUse it when
WebRTCUDP, with jitter buffer, loss concealment, echo cancellation, gain control and noise suppression built into the stackConceals it. The decoder synthesizes plausible audio for the missing interval; the caller hears a brief artefact rather than a gap. Late packets are simply discarded.Anything with a browser or mobile app on the other end. The client network is unpredictable and this stack was built for exactly that.
WebSocketTCP, with your own framingBlocks. TCP guarantees ordered delivery, so a lost packet stalls everything behind it until the retransmission arrives — head-of-line blocking. For audio this is precisely backwards: you would rather lose 20 ms than delay the next 200.Server to server, or server to a cloud model API, where the path is short and reliable. This is what most model APIs actually expose.
Telephony (SIP/PSTN)RTP over carrier networks, G.711 or G.722, with media gateways in betweenDepends on the carrier and the gateways. You typically have no visibility and no control, and each network boundary can add an encode, a decode and another buffer.You do not choose this. Your callers do, by dialling a phone number.

The pattern that most production systems converge on, and which the Softcery survey reports as the common shape: WebRTC from the client to your relay, WebSocket from your relay to the model APIs. Each protocol is used where its failure mode is acceptable — concealment where the network is hostile, reliability where the path is short.

Two implementation details in that arrangement bite people repeatedly:

Nagle's algorithm. TCP will, by default, coalesce small writes to reduce overhead. Your 20 ms frames are exactly the small writes it wants to coalesce, and the resulting delay is invisible in a throughput test and audible in a conversation. Most WebSocket libraries disable it; verify yours does rather than assuming.

Silent format conversion. Every audio model expects raw uncompressed samples at a specific rate. Recognizers typically want 16 kHz; synthesizers commonly emit 24 kHz or 48 kHz; most models are trained on mono. Provider APIs accept a variety of inputs and convert silently, which costs latency and can cost quality. Twilio's advice is not to do all conversion yourself but to know where it is happening — because the resample you did not know about is the 15 ms you cannot find.

Bandwidth, for sizing.
Raw PCM at 16 kHz, 16-bit mono: $16{,}000 \times 16 = 256$ kbps.
G.711 telephony: $8{,}000 \times 8 = 64$ kbps.
Opus at conversational quality: roughly $24$ kbps.
Packet rate at 20 ms frames: $1000/20 = 50$ packets per second, each way.

So one concurrent call is 50–100 packets per second and tens of kilobits — trivial individually. At 1,000 concurrent calls it is 100,000 packets per second through your relay, and packet rate, not bandwidth, is what your network stack runs out of. Softcery's figure of 8–64 kbps per stream is the right order of magnitude, and the cost is minor per stream and real at scale.

09.2 Hand-worked: sizing a jitter buffer from real arrival times

Ten packets, one table, and the exchange rate between delay and glitches computed exactly.

Packets are sent every 20 ms. They arrive after a base network delay plus a variable extra amount — the jitter. The playout buffer's job is to absorb that variance: hold each packet for $D$ ms before playing it, so packets that arrive late still make their slot. A packet whose jitter exceeds $D$ misses its deadline and is dropped, and the caller hears a concealment artefact.

Observed jitter for ten consecutive packets (ms above the 40 ms base) $$0,\ 12,\ 5,\ 30,\ 8,\ 2,\ 45,\ 15,\ 6,\ 20$$
Step 1 — count late packets at each buffer depth.
A packet is late when its jitter exceeds $D$.
$D = 10$: late are $12, 30, 45, 15, 20 \Rightarrow 5$ of 10 $= \mathbf{50\%}$ loss. Unusable.
$D = 20$: late are $30, 45 \Rightarrow 2$ of 10 $= \mathbf{20\%}$.
$D = 30$: late is $45 \Rightarrow 1$ of 10 $= \mathbf{10\%}$.
$D = 50$: none late $= \mathbf{0\%}$.

Step 2 — price the delay.
The buffer sits on both directions of the ledger, so a depth of $D$ costs $2D$ of turn gap.
$D = 20 \Rightarrow 40$ ms of the budget, 20% concealment events.
$D = 30 \Rightarrow 60$ ms, 10%.
$D = 50 \Rightarrow 100$ ms, 0%.

Step 3 — find the knee.
From 20 to 30 you spend 20 ms and halve the loss: excellent.
From 30 to 50 you spend 40 ms to remove the last 10%: expensive, and it is buying you protection against one outlier packet.
The knee is around $D = 30$ ms, which is exactly the figure in Twilio's diagram — and now you know it is not a magic constant but the knee of a distribution.

Step 4 — the reason real stacks adapt.
That single 45 ms outlier is forcing a 67% increase in buffer depth for every other packet. This is why production jitter buffers are adaptive: they track a high percentile of recent jitter and resize continuously, paying for the tail only while the tail exists. Note the second-order effect — when an adaptive buffer grows mid-call, your barge-in overlap window from Chapter 07 grows with it, so a congested network makes your agent measurably worse at yielding.

That last connection is the kind of thing that only shows up in production. A caller on a congested network experiences two degradations that look unrelated in your dashboards: slightly worse audio, and an agent that seems reluctant to let them interrupt. They are the same cause.

09.3 Widget: the jitter buffer

Packets arriving with jitter, a playout deadline you control, and the two failure modes drawn against each other.

Each mark is a packet: send time on the left, arrival after its network delay, and the playout deadline as a vertical line. Packets landing left of the line play cleanly; packets landing right are late and are drawn in terracotta with a concealment marker on the output track.

Increase the network's jitter to simulate a congested last mile, and watch the loss rate climb until you compensate with buffer depth — and watch the ledger cost at the bottom climb with it. Switch the protocol to see the difference between concealment and head-of-line blocking on the same packet trace: with reliable delivery nothing is lost, but everything after the missing packet is delayed until it is retransmitted.

Widget 09 · jitter, buffer depth, and what the caller hears
Packet arrived in time Late or lost Playout deadline Clean audio out

09.4 Telephony: everything is worse at eight kilohertz

The enterprise use cases are phone-shaped, and the phone network undoes several of the advantages you paid for.

The standard telephone network carries speech sampled at 8 kHz through the G.711 codec. By the Nyquist limit that represents frequencies up to 4 kHz, and it discards everything above — which is where a great deal of consonant discrimination lives. The difference between "f" and "s", between "p" and "t", is largely above 4 kHz.

Every source in this lesson agrees on the consequence. Modern recognizers are trained overwhelmingly on 16 kHz or better, so 8 kHz input reduces accuracy significantly. Synthesis sounds thinner because the model's output is bandlimited on the way out. And speech-to-speech models optimized for high-quality web audio lose much of their advantage over the phone while keeping their premium pricing — which Softcery states directly and which is, on its own, a substantial argument for the cascade in telephony deployments.

Telephony factConsequence for the stack
8 kHz G.711Recognition accuracy drops; consonant confusions rise; any model trained at 16 kHz is running out of distribution. Wideband codecs (G.722, Opus) exist and require both endpoints and every gateway in between to support them.
Fixed 100–200 ms network latencyCharged straight to your turn gap before anything else happens. It is also why Twilio's derived public-leg figure of about 230 ms round trip in Chapter 03 is unsurprising.
DTMF is out of bandTouch tones are delivered as signalling events, not as audio, precisely so they do not confuse the recognizer. You must handle them as a separate input channel — and callers will press keys at your voice agent no matter what you tell them.
Network echo cancellersSomeone else's echo cancellation, of unknown quality, sitting in your barge-in path. This is why the client-side flush from Chapter 07 matters more on phone than on web.
Every network boundary re-encodesCross from a CPaaS to a SIP intermediary to your platform and you may pay three transcodes and three buffers. Twilio's Bring-Your-Own-Carrier pitch is exactly a pitch to remove boundaries, and the underlying engineering claim is sound regardless of the vendor.
The measurement mistake this makes almost inevitable. Teams benchmark recognition accuracy and latency on clean 16 kHz web audio because that is what the demo runs on, then deploy to a phone number. Word error rate on curated benchmarks does not survive the channel change, and one source reports phone-audio error rates from roughly 5% to over 20% across providers on the same test set — a fourfold spread that no clean-audio leaderboard predicts.

If you deploy on telephony, benchmark on telephony. Record real calls through your real carrier path, at 8 kHz, with real background noise, and evaluate on those. Any other number is measuring a system you are not shipping.

09.5 Where to put things

Placement is a latency decision with an audible consequence, and it is nearly impossible to change later.

Twilio's placement guidance reduces to three rules, and each one now has a mechanism attached from earlier chapters.

  1. Media services close to the media edge, and close to each other

    Audio enters through the edge nearest the caller, so what matters is the distance from that ingress point to your services, not the distance to the caller. Every hop between recognizer, synthesizer and audio buffer is a line in the ledger and a millisecond in your barge-in overlap window.

    Mechanism: Chapter 03 line 06, and Chapter 07's overlap arithmetic
  2. Prioritize audio over text

    Audio is heavier, more bandwidth-hungry and more sensitive to degradation. Model latency is easier to mask — Chapter 08 is a whole chapter of techniques for masking it — and text hops are cheap. So spend the colocation budget on the recognizer and synthesizer first.

    Mechanism: you have latency-masking tools for the model and none for the media path
  3. Colocate, but do not co-host, the recognizer and the synthesizer

    The subtlest advice in the guide. The two workloads are normally out of phase — one runs while the caller talks, the other while the agent talks — which makes sharing a machine tempting. But interruptions and false endpoint triggers put them in phase, so the spikes align, contention rises, latency rises across the box, which causes more collisions. A feedback loop with a bad ending.

    Mechanism: the correlation between the two loads is not stable, and it goes to one under exactly the conditions you care about
Why placement is nearly irreversible. Changing an inference provider after you have colocated, integrated, tuned prompts against a specific model's speaking style and signed a volume commitment is expensive and disruptive. Twilio's warning to do the regional homework up front is the practical version of an architectural principle: decisions whose cost is paid in migration should be made early and deliberately. Choose providers with regional coverage matching your callers even if the launch region is one, and keep your component interfaces boring enough that a swap is a config change — which is, once again, the cascade's structural advantage from Chapter 04.

09.6 Concept check

Check: Why is a reliable ordered transport (TCP/WebSocket) usually the wrong choice from an unpredictable client network to your relay?

Next chapter: how to know whether any of this is working, and why the metric you pick decides which failures you are allowed to see.

10 How do you measure something that only exists in time?

Every metric in the previous nine chapters scores a component. A conversation is not a component, and the properties that make it good are properties of the interaction.

Suppose you have two voice agents and one is better. Which number is different?

Not word error rate: both use the same recognizer. Not time-to-first-token: both use the same model class. Not synthesis quality: both use the same voice. Yet one of them is the system callers finish their business with and the other is the one they escalate. The difference is in when each one speaks, whether it yields, how it recovers from a mistake — and none of those is visible in a clip.

The eval design principle, stated once and used throughout. A test is only measuring a capability if it is impossible to pass without that capability. A turn-taking test that a system with no turn-taking model can also pass is not measuring turn-taking; it is measuring something else that happens to correlate. Every metric below is judged by that standard, and several widely-used ones fail it.

The corollary is uncomfortable and worth sitting with: the metrics you choose determine which failures you are permitted to notice. A team tracking word error rate, time-to-first-token and uptime has a dashboard on which every one of Chapter 01's six production failures is invisible. The dashboard will be green during the incident.

10.1 Three layers of metric, and what each is blind to

Component, conversation, product. You need all three, and most teams have only the first.

LayerMetricsWhat it seesWhat it is blind to
ComponentWord error rate; time-to-first-token; tokens per second; synthesis time-to-first-byte; real-time factor; uptimeRegressions in a single service. Cheap, continuous, automatable, and the right thing to alert on.Every interaction failure. All six conditions from Chapter 01 leave these metrics untouched.
ConversationMouth-to-ear turn gap and its percentiles; interruption success rate; false-cut rate; endpoint timeout-spike fraction; backchannel behaviour; recovery rate after a misunderstandingWhether the thing behaves like a conversational partner. This is the layer that distinguishes the two agents in the opening question.Whether the caller got what they wanted. A delightful agent that fails to book the appointment scores well here.
ProductTask completion; containment (calls resolved without a human); escalation rate and reason; repeat-call rate within 24 hours; caller-reported satisfaction; cost per resolved callWhether the system is worth operating. The only layer the business cares about directly.Why. A containment drop tells you something broke and nothing about which of eleven chapters it lives in.

The layers are diagnostic in one direction and motivating in the other. Product metrics tell you whether; conversation metrics tell you where; component metrics tell you what. A team with only component metrics ships regressions users hate; a team with only product metrics knows it is losing and cannot say why.

Two component metrics deserve specific warnings, because both are routinely over-trusted.

Word error rate does not transfer across channels. Chapter 09 covered why. A leaderboard number measured on clean read speech tells you very little about your telephony deployment, and the spread across providers on phone-quality audio is far larger than the spread on curated benchmarks.

Real-time factor is a threshold, not a score. Below 1.0 the system keeps up; above 1.0 latency accumulates without bound until the queue collapses. An RTF of 0.1 is not "ten times better" than 0.5 in any way the caller can perceive — both keep up. What matters is the RTF under peak load with contention, which is exactly the condition Chapter 09's co-hosting warning describes.

10.2 Duplex benchmarks: measuring the interaction itself

Four categories, four metrics, and the first eval family designed for systems that listen and speak at the same time.

Full-Duplex-Bench, used as the evaluation backbone in the PersonaPlex paper, is the clearest published attempt to score conversational behaviour rather than clip quality. It probes four categories, each constructed so that a system without the corresponding capability cannot pass:

  1. Pause handling

    The user's turn contains a silence that is not a turn boundary. The metric is takeover rate: how often the model starts talking during the pause. Lower is better here — this is exactly the false-cut failure from Chapter 06, measured directly.

    Impossible to pass by being a good speaker; only turn projection helps
  2. Backchannel

    While the user is speaking at length, does the model produce the short acknowledgments — "mm-hm", "right" — that humans use to signal attention without taking the floor? Measured by frequency and by the distributional distance between the model's backchannel timing and human timing.

    A half-duplex system scores zero by construction: it cannot speak while listening
  3. Smooth turn-taking

    The user finishes. Does the model take the floor, and how quickly? Takeover rate (higher is better here) paired with response latency. Both are needed: a model that always responds instantly but also barges into pauses is gaming one number.

    The two paired metrics make the trivial strategies fail
  4. User interruption

    The user barges in mid-response. Does the model yield, and is what it says afterwards actually a response to the interruption? Scored by takeover rate, by latency, and by a judged quality score on the resulting turn.

    Chapter 07's entire cancellation chain, reduced to two numbers and a judgement

The headline results, from the PersonaPlex paper's own tables, are worth quoting with their uncertainty attached because the uncertainty is the lesson:

ModelDialogue MOS, Full-Duplex-BenchDialogue MOS, Service-Duplex-BenchSpeaker similarity
PersonaPlex3.90 ± 0.153.59 ± 0.120.57
Gemini3.72 ± 0.143.22 ± 0.140.00
Qwen2.5-Omni3.70 ± 0.132.37 ± 0.200.07
Freeze-Omni3.51 ± 0.182.38 ± 0.210.05
Moshi3.11 ± 0.152.83 ± 0.130.10

And from the same paper's benchmark table, the two numbers most relevant to this lesson — response latency on smooth turn-taking, and takeover rate when the user interrupts:

ModelTurn-taking latency (s, lower better)Interruption takeover rate (higher better)
PersonaPlex0.071.00
Gemini0.271.00
Moshi0.350.92
Qwen2.5-Omni0.950.87
Freeze-Omni1.300.89
How we read those numbers, and what we are not claiming. The second table is our reading of the paper's benchmark table, whose column groups are dense; we report only the two columns we are confident of and we do not report the backchannel or pause-handling figures, because their column alignment is ambiguous in the extracted text. Take the ordering as reliable and the third decimal place as not.

Also note what the first table shows about the other axis: speaker similarity of 0.57 against roughly zero for every baseline. Most duplex models ship a fixed voice, so "did it sound like the requested speaker" is not a close contest; it is a capability one system has and the others do not.

10.3 Widget: the eval console

Five systems, four metrics, and confidence intervals that widen as you shrink the rater pool.

Pick a metric and the bars re-sort. The whiskers are 95% intervals; on the subjective metrics they come from the paper, and on the objective ones they are computed from the rater count you set with the slider. Drag the rater count down and watch a clear ranking dissolve into a tie — which is the single most useful thing this widget does.

Widget 10 · duplex evaluation, with the uncertainty drawn
Best in metric Other systems 95% interval Overlaps the leader

10.4 Hand-worked: is that difference real?

Two published means, two published intervals, and a conclusion that most write-ups of this table get wrong.

The top of the Full-Duplex-Bench opinion-score table reads 3.90 ± 0.15 for the leading system and 3.72 ± 0.14 for the runner-up. Is the leader better?

Step 1 — write out both intervals.
Leader: $3.90 \pm 0.15 \Rightarrow [3.75,\ 4.05]$.
Runner-up: $3.72 \pm 0.14 \Rightarrow [3.58,\ 3.86]$.

Step 2 — check for overlap.
The leader's lower bound is $3.75$. The runner-up's upper bound is $3.86$.
Since $3.75 < 3.86$, the intervals overlap on the range $[3.75, 3.86]$.

Step 3 — say what that licenses.
Overlapping 95% intervals mean the data are consistent with the two systems being equal on this metric. It is not a proof of equality, and non-overlap would be a stronger test than necessary — but the honest reading of a 0.18-point gap with those intervals is "leading, not established."

Step 4 — find a difference that is real.
Leader $[3.75, 4.05]$ against the lowest system, $3.11 \pm 0.15 \Rightarrow [2.96,\ 3.26]$.
$3.75 > 3.26$: no overlap, with room to spare. That gap is real.

Step 5 — the other table.
Turn-taking latency of $0.07$ s against $0.95$ s is a factor of thirteen. No plausible interval on a latency measurement covers a factor of thirteen. Objective timing measurements separate these systems far more decisively than opinion scores do, which is a general property worth remembering: when you can measure a physical quantity instead of asking humans, the measurement usually wins on power.

The practical rule that falls out: report intervals, and read other people's. A leaderboard that publishes only means invites you to over-read the ordering, and the top few rows of a subjective leaderboard are frequently one system wide.

Why rater count matters more than sample count, and the free fix. Ratings from one person are correlated: individuals differ in strictness and in how much they care about prosody. With $m$ ratings per rater and intra-rater correlation $\rho$, the effective sample size is not $n$ but $n / (1 + (m-1)\rho)$.

At $n = 1600$ ratings from 200 raters, $m = 8$, and a modest $\rho = 0.1$: the design effect is $1 + 7(0.1) = 1.7$, so $n_{\text{eff}} = 1600/1.7 \approx 940$. You paid for 1,600 and you have the resolving power of 940.

The fix is free and almost nobody applies it: more raters with fewer ratings each beats fewer raters with more. Two hundred raters at 8 ratings resolves better than 50 raters at 32, for the same total cost, because the correlated component shrinks.

10.5 The suite you should actually build

Four tiers, in the order you should build them, each one catching failures the previous tier cannot see.

  1. Tier 1: the latency ledger, instrumented per line, in production

    Every line item from Chapter 03 emitted as a timing span on every turn, with percentiles. This is a week of work and it makes every subsequent question answerable. Include the endpoint-timeout-spike fraction from Chapter 06 as a first-class metric — it is a free estimate of your endpointer's error rate.

    Catches: regressions, provider degradation, the tail nobody noticed
  2. Tier 2: an adversarial turn-taking suite, on your own channel

    Recorded utterances containing the hard cases: mid-sentence pauses of 400, 700 and 1,100 ms; barge-ins at 0.5 s and 2 s into the reply; overlapping speech; a caller who says "hello?" during a tool call. Replayed through your real telephony path at 8 kHz. Scored on false cuts, overlap window, and whether the transcript history matches what was audibly spoken.

    Catches: everything in Chapters 06 through 09, before your callers do
  3. Tier 3: task completion on scripted scenarios with instrumented tools

    Enterprise scenario suites — the Salesforce work cites Benchforce for exactly this — define environments with function calling, log every tool invocation, and score whether the goal state was reached. This is the layer where "the agent was charming and booked the wrong day" finally shows up as a failure.

    Catches: agent-layer defects that all timing metrics call success
  4. Tier 4: human listening on paired conditions

    Expensive, slow, and the only thing that measures whether the experience is good. Design it as a paired comparison where exactly one variable changes, report rater counts and intervals, and prefer many raters over many ratings each.

    Catches: the residual that objective metrics cannot represent
The one eval most teams are missing. Tier 2. Component metrics are easy so everybody has them; task completion is business-legible so everybody eventually builds it. The adversarial turn-taking suite sits in between, is nobody's obvious responsibility, and is where every failure in this lesson's middle chapters lives. It is also the cheapest to build: forty recorded utterances, a replay harness, and four scored quantities. A day of work standing between you and the class of bug that gets pilots cancelled.

10.6 Concept check

Check: Two systems score 3.90 ± 0.15 and 3.72 ± 0.14 on a listening study. What is the correct conclusion?

Final chapter: consent, provenance, spoofing and disclosure — and then the two ladders, which is where the research half of this arc and the product half finally meet.

11 Four different problems that all get called "voice safety"

They have different threat models, different mitigations and different owners. Conflating them is how teams end up with a policy document and no defences.

The moment your product can produce a convincing human voice on demand, four distinct problems arrive at once. They are usually discussed as one topic, which guarantees that at least three of them go unaddressed.

ProblemThreatDirectionPrimary mitigation
Cloning consentSomeone's voice is reproduced without their permissionYour outputProvenance of the enrollment sample: who authorized it, for what, with what revocation path
ProvenanceYour synthetic audio is later presented as a real recording of a real personYour outputAudio watermarking and detection classifiers, plus signed logs
Anti-spoofingSomeone uses synthetic audio against you — to pass voice authentication or to impersonate a customerYour inputSpoof detection on inbound audio; never using voice alone as an authentication factor
DisclosureThe caller does not know they are speaking to a machineThe interactionSaying so, in the first turn, in a way that survives a caller who talks over the greeting

Note the directions. Two are about what you emit, one is about what you accept, one is about the frame around the conversation. A team that has done cloning consent well may have no spoof detection at all, and the second is the one that costs money when it fails.

Why this chapter belongs in an engineering lesson. Every one of these four is implemented as a component in the stack you have just spent ten chapters building. Consent is a data-pipeline property of your voice enrollment path. Watermarking is a post-processing stage after synthesis. Spoof detection is a classifier next to your recognizer — the same insertion point as the guardrails in Chapter 04. Disclosure is a turn in your dialogue policy with a barge-in interaction. None of it is a policy document; all of it is code, in places you already have.

11.2 Watermarking and anti-spoofing: the two directions

One marks what you emit. The other inspects what you receive. Both are classifiers; only one is usually built.

Watermarking your output

An audio watermark embeds an imperceptible signal in generated speech so that a detector can later establish that the audio came from a generator. The duplex-model literature takes this seriously enough to ship detection alongside generation — the audio-language-model lineage this arc covers includes released watermark classifiers, precisely because the authors understood what they had built.

What a watermark is good for, honestly:

  • Good for: establishing provenance for your own audio in a dispute; letting platforms label synthetic media; detecting your own model's output leaking into your training data, which is a real and underrated failure.
  • Not good for: stopping a determined adversary. Watermarks degrade under re-encoding, telephony transcoding, and deliberate attack. Passing a watermarked clip through an 8 kHz phone codec — which your own product does on every call — is itself an aggressive transformation.
The asymmetry that makes watermarking weaker than it sounds. Detection is only meaningful when the absence of a watermark means something. It does not: an unmarked clip may be genuine, or it may be from a generator that does not mark, or from a marked generator after a transform that destroyed the mark. So a positive detection is informative and a negative detection is nearly worthless. Design your process to use watermarks as evidence for, never as screening against.

Spoof detection on your input

The inbound direction gets far less attention and is where the immediate financial loss lives. Your voice agent answers calls. Some of those calls will be synthetic audio operated by someone trying to reset a password, redirect a payment or extract account details in a voice matching your customer's.

Two engineering positions follow, and the first is not negotiable:

Voice is not an authentication factor. Voice matching is a convenience signal and a fraud-scoring input. Any flow where a matching voice alone unlocks an action is a flow that a three-second sample defeats. This was defensible in 2015 and is not now.

Spoof detection belongs in the seam. A synthetic-speech classifier on inbound audio sits exactly where the input guardrail sits in Chapter 04, costs tens of milliseconds, and produces a score that feeds risk decisions rather than a binary gate. Note the architectural implication, which is a small irony: the cascade's inspectable input boundary is where you attach the defence against synthetic speech — the same seam, doing a job nobody listed when the architecture was chosen.

11.3 Disclosure, and a regulatory surface that is not one surface

Where the same recorded call is routine in one city and a criminal offence in another.

Disclosure is the cheapest of the four and the one most often reduced to a line in a script. Two engineering details make the difference between real disclosure and theatre:

It must survive barge-in. Callers talk over greetings; that is what greetings are for. If your disclosure lives in the first sentence of a greeting the caller interrupts, it was not delivered. Either disclose in a form that cannot be skipped, or re-deliver on the first substantive turn.

It must be true about what happens next. "You're speaking with an automated assistant, and this call may be recorded" is disclosure. It becomes false the moment a human silently joins, or the recording is used to train a model, and the caller was told something specific enough to be wrong.

The jurisdictional picture is genuinely difficult, and the honest summary is that it is not one picture. The Softcery material characterizes the European situation as one rulebook on paper and twenty-seven regimes in practice, with the same recorded call being routine in one member state and a criminal offence in another; and it characterizes several Gulf jurisdictions as places where recording without consent is a crime rather than a fine, with data-residency requirements that forbid the audio leaving the country and permitting regimes around voiceprint features.

Read that as an engineering requirement, not as legal advice. This lesson is not competent to give legal advice and neither is a blog post. What the landscape does imply architecturally is concrete and worth building for:
1. Recording, retention and training-use must be configurable per jurisdiction, not global constants. If your consent behaviour is a boolean in a config file, you are one market launch away from a rewrite.
2. Data residency must be a deployment property. Which is the self-hosting argument from Chapter 04 arriving again through a different door: a stack that can only run in one cloud region cannot satisfy a rule that audio must not leave a country.
3. Your transcript log is discoverable. Chapter 04 sold it as an observability win; it is also a permanent, searchable record of everything your agent ever said, and it should be designed with retention limits and redaction from the first day rather than the day of the first request.

11.4 Build versus buy, layer by layer

Five levels of abstraction, what each one hands you, and what each one takes away.

LevelWhat it isYou getYou give upChoose when
From scratchYour own orchestrator over provider APIs, as in the Salesforce tutorial: a WebSocket server, a voice-activity state machine, a sentence buffer, an agent loop.Total control, and an actual understanding of every line in the ledger. Roughly 750 ms time-to-first-audio is achievable.Months. And you will rebuild the sentence buffer, the barge-in cancellation chain and the jitter handling that the frameworks already have.You have a hard self-hosting or data-residency constraint, or the product is the voice infrastructure.
Open frameworkPipecat or LiveKit Agents: frame-based or session-based pipelines with dozens of service integrations, first-class function calling, WebRTC transport.The hard parts, solved and battle-tested. LiveKit ships adaptive interruption handling reported at 86% precision with 100% recall, dynamic endpointing and preemptive generation on by default — which is Chapters 06 through 08 as a config flag.Opacity, unless you read the source. The Salesforce critique is fair: these are excellent tools that do not teach you what they do.Almost always. This is the default answer for a team building a voice product rather than a voice platform.
Managed orchestrationA service that owns the media path: streaming, transcription, turn detection and synthesis behind one interface, colocated at the network edge. Twilio's ConversationRelay is the example, reporting p50 491 ms and p95 713 ms platform turn gap.The media plumbing and the colocation problem, gone. You write the agent and receive text.Component choice inside the managed boundary, and a dependency on their edge footprint.Telephony is your primary channel and you would rather own the agent than the audio.
Full platformAn end-to-end voice-agent product where you configure prompts, voices and tools in a console.Time. A working agent in days.The ledger. You cannot instrument what you cannot see, and per-component economics disappear into a per-minute price.Validating demand, or a volume small enough that engineering time dominates unit cost.
Speech-to-speech APIA single realtime connection to a multimodal model, with tool calling and, increasingly, protocol integrations and direct telephony dialling.Radical simplicity, and paralinguistic fidelity no cascade can match.The seam, per-component pricing, model choice, self-hosting, and cloned brand voices.Expressivity is the product, or you are prototyping and want to ship this week.
The recommendation, stated plainly. For most teams: open framework, cascaded, with a managed media path if you are on telephony. Build the latency ledger instrumentation yourself regardless of what you buy — it is the one thing no vendor gives you in the form you need, and it is the difference between debugging and guessing. And keep the agent logic, the tool definitions and the evaluation transcripts vendor-neutral, so that when the missing rung in the next section finally ships, moving is a project rather than a rewrite.

11.5 The two ladders

Every research advance in this arc relaxes exactly one product constraint. Mapping the rungs tells you what ships next — and which constraints no model will ever relax.

Here is the closing argument of the whole series, and the widget is the argument.

On the left, the research ladder: the sequence of advances that took speech from a synthesis problem to an interface. On the right, the product ladder: the constraints from this lesson that decide whether a deployment survives. Click a research rung to see which product constraints it relaxes, and how strongly.

Widget 11 · research rungs against product constraints
Research rung Product constraint Relaxed by the selected rung Not relaxed by anything on the ladder

Press show only unrelaxed constraints and read what is left, because that is the actual prediction this lesson makes.

What no model advance relaxes.

The output guardrail. A seam between deciding and speaking is an architectural property, not a capability. A better model can be more reliably safe, which is worth a great deal — but it cannot give a regulated buyer a place to stand between the decision and the sound. Only an architecture does that.

Transcript-native observability. Same argument. An inner monologue produces a transcript, which is a log; it is not an interface that generation is required to pass through, so you can read it and you cannot enforce against it.

Per-component economics. A bundled model has a bundled price. No advance inside the bundle turns it into a set of independent levers.

The public network leg. Around 230 ms round trip, set by geography and the phone system. It is charged identically to both architectures and it is the reason the gap between a 200 ms model and a 700 ms cascade shrinks so much in the field.

And the one rung that is about to fall: self-hostable streaming audio generation. The cloud figure of roughly 702 ms proves the model can do it; the 146 second local figure proves only that the serving stack does not exist yet. That is an inference-engineering gap of exactly the kind that has closed in quarters, repeatedly, for text. When it closes, the self-hosting argument — today's strongest reason enterprises choose the cascade — disappears, and only the seam arguments remain.

11.6 The close: the PhD ladder meets the product ladder

Where this arc has been, and the one sentence worth keeping from all of it.

The research half of this arc is a story about representation. Speech became tokens, so a transformer could model it. The first codebook was distilled toward semantics, so a small number of tokens could carry meaning. Two streams were modelled at once, so a machine could listen and speak in the same instant. Text was threaded through the audio as an inner monologue, so the thing could reason while it spoke. Voice and role were made conditionable, so it could be a specific person doing a specific job. And speech, language and action were synchronized, so it could act while talking.

Read as a ladder, each rung removes a constraint that a product engineer had previously been working around by hand. Dual-stream modelling removes the cancellation chain of Chapter 07. Speech-language-action synchronization removes the latency masking of Chapter 08. Role conditioning removes a chunk of the voice-selection and brand-consistency work that pushed teams toward the cascade in the first place.

And yet the product half of the arc is not converging on those systems, and this lesson has spent eleven chapters explaining why in specifics rather than in vibes. Not because the research is overhyped — it is not; a 160 ms duplex model is a genuinely astonishing object — but because a deployed voice agent is scored on properties the research ladder was never climbing. Debuggability. A place to stand between deciding and speaking. A price that decomposes. Audio that never leaves the building. A transcript that a regulator can read.

The sentence worth keeping. A voice agent is an interface, and an interface is judged by what it lets you intervene in, not only by what it can do. The research ladder increases capability; the product ladder increases intervention. They are different ladders, they are climbed by different people, and the systems that win are the ones whose authors understood that both existed.

The practical version, for whatever you build next. Instrument the ledger before you optimize anything. Tune your endpointer against your own cost model rather than a default. Implement all four steps of the barge-in cancellation, including the one nobody sees. Mask the tools you cannot make fast, and never let a filler cover a failure. Measure the interaction and not just the components, and read your own confidence intervals. Put the seam where the compliance requirement is. And keep the interfaces boring, because the rung that is about to fall will change the calculus and you want the migration to be a project rather than a rewrite.

Then pick up the phone and call your own agent from a moving car. That is the eval that matters.

11.7 Concept check

Check: Which of the product constraints in this lesson can a better speech model, on its own, never relax?

That is the arc. The research half is in the veanors; this was the half where it meets a phone line.