~/$VOICE//RealtimeRealtime Voice AI · v1.0
01 · Foundation

Why Voice, Why Now

Voice was the obvious interface for AI agents and, for a decade, the worst one — clunky wake words, five-second pauses, robotic replies. What changed in 2025–2026 is that speech-to-speech models made agents conversational: they listen and talk in the same breath, carry tone and hesitation, and answer fast enough that turn-taking feels human.

Sub-second replies became table stakes. Humans leave roughly 200–500 ms of silence between conversational turns; an agent that takes three seconds is not slow, it is broken — callers talk over it, repeat themselves, or hang up. Every architecture decision in this guide is downstream of one constraint: the reply must start before the pause gets awkward.

Working definition
A realtime voice agent is a full-duplex loop: it captures audio continuously, decides when the user has finished a thought, thinks, and streams speech back — while staying interruptible at every moment. The hard part is not any single stage. It is doing all of them concurrently under a latency budget.

What Changed

🗣️
Native speech models
Models that consume and emit audio tokens directly — no transcription hop, prosody preserved end to end.
⚡
Streaming everything
ASR emits partials, LLMs stream tokens, TTS streams audio. Every stage overlaps with the next.
📞
Real deployments
Support lines, drive-through ordering, scheduling, in-car assistants — voice agents took production traffic in 2025.
01 · Foundation

Two Architectures

Every realtime voice system is one of two shapes. The cascaded pipeline chains specialist components — voice activity detection, speech recognition, a text LLM, speech synthesis — each excellent alone, each adding a handoff. The native speech-to-speech model collapses the chain into one model that hears audio and speaks audio.

CASCADED — every handoff adds latencyMIC~20msVAD~60msASR~150msLLM~300msTTS~150msstages sum: ~680 ms + networkSPEECH-TO-SPEECH — one model, no handoffsnative s2s model · audio tokens in → audio tokens out~200–300 ms to first audio
cascade latency is additive · s2s collapses the chain into one hop

The Tradeoff

Cascaded wins
Swap ASR / LLM / TTS independently
Mature tool calling + guardrails
Text transcript is source of truth
Cheaper per minute at scale
Speech-to-speech wins
200–300 ms replies, no stage sum
Tone and emotion survive the trip
No ASR errors cascading into the LLM
Interruptions feel native, not bolted on
Choosing in 2026
Latency-critical, emotionally expressive, consumer-facing → native s2s. Heavy tool use, compliance transcripts, tight control over what the agent may say → cascade. Many production stacks run s2s for the conversation and drop to a cascaded text path for regulated moments.
01 · Foundation

The Latency Budget

Treat the reply like a performance budget: every stage spends milliseconds from a fixed allowance, and the sum decides whether the conversation feels alive. The comfort window is roughly 500–800 msfrom the user finishing a sentence to the agent's voice starting — inside it you feel heard, beyond it you start wondering if the call dropped.

capture 20asr 150tts first audio 150vad 60llm first token 300network ~10002505007501000mshuman comfort window ≈ 500–800 ms
a well-tuned cascade lands ~780 ms — one slow stage blows the window

Where the Budget Goes

The two big line items are LLM time-to-first-token and endpointing delay — the VAD waiting to be sure you have actually finished speaking. Everything downstream of the first token can overlap: stream tokens into TTS as they arrive and start playback on the first audio chunk, sentence by sentence.

measure time-to-first-audiostream every stageoverlap LLM + TTSp95, not average
The metric that matters
Time-to-first-audio, measured at the caller's ear, at p95. Mean latency hides the 1-in-20 spike that makes a caller say "hello?" — and one "hello?" per call is enough to feel broken.
02 · The Pipeline

Capture & VAD

The pipeline starts at the microphone: getUserMedia for the stream, an AudioWorkletto pull PCM frames off the audio thread, and a socket shipping 20 ms chunks to the server. Send audio continuously, not just when you think someone is talking — barge-in detection needs every frame.

capture.ts
// Capture 16 kHz PCM in an AudioWorklet, ship 20 ms frames const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, // non-negotiable on speakerphone noiseSuppression: true, autoGainControl: true, sampleRate: 16_000, }, }); const ctx = new AudioContext({ sampleRate: 16_000 }); await ctx.audioWorklet.addModule('/worklets/pcm-frames.js'); const src = ctx.createMediaStreamSource(stream); const node = new AudioWorkletNode(ctx, 'pcm-frames', { processorOptions: { frameMs: 20 }, // 320 samples per frame }); src.connect(node); const ws = new WebSocket('wss://voice.example.dev/session'); ws.binaryType = 'arraybuffer'; node.port.onmessage = ({ data }: MessageEvent<Int16Array>) => { // Send raw frames continuously; the server runs VAD and // endpointing so barge-in detection sees every frame. if (ws.readyState === WebSocket.OPEN) ws.send(data.buffer); };

VAD and Endpointing Are Different Jobs

Voice activity detectionanswers "is speech happening right now?" frame by frame — a small neural model like Silero runs in ~1 ms. Endpointinganswers the harder question: "has the user finished their thought?" Pure silence-based endpointing fails constantly — people pause mid-sentence to think, and rushing in at every pause is the fastest way to feel rude.

🎚️
Silence threshold
Classic: end the turn after N ms of silence. Simple, but slow talkers get cut off and fast replies feel laggy.
🧠
Semantic endpointing
A small model reads the partial transcript and judges whether the utterance is complete. Shorter waits on clear sentences.
🔀
Adaptive hybrid
Trailing question intonation → respond fast. Mid-list pause → wait longer. The 2026 default in serious stacks.
Tuning rule
Bias the endpointer toward waiting. Interrupting a user mid-thought is far more damaging than an extra 200 ms of silence — one feels rude, the other merely unhurried.
02 · The Pipeline

Streaming ASR & TTS

Nothing in the pipeline waits for anything else to finish. ASR emits partial transcripts that stabilize word by word; the LLM starts on the final transcript and streams tokens; TTS consumes those tokens sentence by sentence and streams audio. The user hears the first word while the model is still writing the last.

events.ts
// One socket, many event types. Partials arrive every 100-300 ms. type ServerEvent = | { type: 'asr.partial'; text: string } // unstable, will change | { type: 'asr.final'; text: string } // endpoint reached | { type: 'tts.audio'; chunk: string } // base64 PCM, play now | { type: 'tts.done' }; ws.onmessage = (msg) => { const ev: ServerEvent = JSON.parse(msg.data); switch (ev.type) { case 'asr.partial': ui.showCaption(ev.text); // live caption; never act on it break; case 'asr.final': ui.commitCaption(ev.text); agent.onUserTurn(ev.text); // NOW the LLM turn starts break; case 'tts.audio': player.enqueue(decodePcm(ev.chunk)); metrics.markFirstAudio(); // time-to-first-audio, the KPI break; case 'tts.done': player.markTurnEnd(); break; } };

Rules of the Stream

Partials are for display and prediction only — they rewrite themselves as context accumulates, so acting on one means acting on a sentence the user never said. Commit to the LLM only on asr.final. On the output side, chunk LLM text into natural clauses before synthesis: TTS quality collapses when you feed it three words at a time, and latency collapses when you wait for a paragraph.

partials = captions onlycommit on finalsynthesize by clauseplay on first chunk
Speculative execution
Advanced stacks start LLM prefill on a confident partial and throw the work away if the final transcript differs. When the guess holds — most of the time — you erase the entire endpointing wait from the perceived latency.
02 · The Pipeline

Turn-Taking & Interruptions

Humans interrupt each other constantly and it works fine. A voice agent must grant the same right: when the user talks over it — barge-in — the agent has to shut up within a couple hundred milliseconds, and its memory of the conversation has to match what was actually heard, not what it planned to say.

AGENTUSERagent speaking (TTS out)never playeduser speaks (barge-in)VAD: user speech detectedTTS cancelled (+~150 ms)truncate history herecancel TTS · flush the player · keep only the audio that was heardtime
barge-in: cut playback fast, then rewrite history to match reality

The Two Halves of Barge-In

The first half is fast plumbing: cancel synthesis and flush the playback queue the moment VAD fires. The second half is the one teams forget — context truncation. The model generated a full answer, but the user heard 40% of it. If the transcript keeps the full answer, the model will later refer to things it never actually said.

barge-in.ts
// Barge-in: the user starts talking while the agent is talking function onVadSpeechStart(session: Session) { if (!session.agentSpeaking) return; // normal turn, nothing to cancel ws.send(JSON.stringify({ type: 'tts.cancel' })); // stop synthesis const playedMs = player.flush(); // drop queued audio // The history must match what the user actually HEARD, not what // the model generated. Truncate the assistant turn to played audio. session.history.truncateAssistantAudio(playedMs); session.agentSpeaking = false; // floor passes to the user } // Without truncation the model believes it said things the user // never heard, and the conversation quietly diverges from reality.
Not everything is a barge-in
"Mm-hm", "right", a cough — backchannels are listening signals, not interruptions. Killing TTS on every sound makes the agent feel skittish. Gate the cancel on sustained speech (~250 ms) or on the semantic class of the partial transcript.
03 · In Practice

Realtime Stack 2026

The 2026 landscape splits into three layers: native s2s model APIs, orchestration frameworks that run the pipeline and the transport, and component vendors for teams assembling their own cascade. Most production systems mix layers — a framework for plumbing, a model API for the brain.

The Short List

OpenAI Realtime API
Native s2s over WebRTC or WebSocket. Speech in, speech out, tool calls mid-stream. The reference point everything else is measured against.
Gemini Live
Google's bidirectional streaming API — voice plus video input, s2s output, tight tool integration. Strong multimodal angle.
ElevenLabs
The TTS quality bar, now a full conversational platform — agents, voices, and telephony under one roof.
LiveKit Agents
Open-source WebRTC infrastructure plus a Python/Node agent framework. Run any model behind production-grade transport.
Pipecat
Open-source Python pipeline framework from Daily — composable frame processors for VAD, ASR, LLM, TTS. Maximum control over the cascade.
Component vendors
Deepgram and AssemblyAI for streaming ASR, Cartesia and Rime for low-latency TTS — the parts bin for custom cascades.
Selection heuristic
Fastest path to a working agent → a native s2s API. Custom models or on-prem constraints → LiveKit Agents or Pipecat with your own cascade. Voice quality as the product → ElevenLabs. Phone-heavy workloads → whichever framework has first-class SIP.
03 · In Practice

Tool Calls Mid-Conversation

In text chat a tool call costs nothing socially — a spinner is fine. In voice, a two-second tool call is two seconds of dead air on a phone call. Humans solve this constantly: "let me check on that" while pulling up the account. Voice agents must do the same — hold the floor with an acknowledgment, run the tool, then speak the result.

voice-tools.ts
// A tool call means dead air. Hold the floor, then speak the result. const FILLERS = [ 'Let me check on that.', 'One moment while I pull that up.', 'Give me a second here.', ]; agent.on('tool.start', (call: ToolCall) => { // More than ~800 ms of silence reads as a dropped call. if (call.expectedMs > 800) { tts.speak(pick(FILLERS), { interruptible: true }); } }); agent.on('tool.result', (call: ToolCall, result: string) => { // Never read raw JSON aloud. Re-enter the LLM with the result // and let it phrase a spoken answer. agent.continueTurn({ toolResult: { id: call.id, result } }); }); agent.on('tool.timeout', (call: ToolCall) => { tts.speak('This is taking longer than expected. Want me to keep trying?'); });

Designing for Spoken Output

A tool result destined for a voice reply is different material than one destined for a UI. Long IDs, tables, and lists of twelve options do not survive being read aloud. Shape results for the ear: pre-summarize, round the numbers, and cap options at two or three — offer "or I can list the rest" instead of reciting them.

filler before slow toolsresults shaped for the earfillers stay interruptibletimeout has a spoken path
Latency masking
A well-placed filler buys 2–3 seconds of tool time for free — the caller experiences attention, not delay. But fillers are a loan, not income: two fillers back to back with no result reads as stalling. If the tool needs longer, say so honestly and offer a callback.
03 · In Practice

Choosing a Transport

Audio has to move between the caller and your agent, and the wire you pick shapes everything downstream. Three realistic options: WebRTC for browsers and apps on real-world networks, WebSocket for server-to-server plumbing, and SIP/PSTN when the client is a phone number.

WebRTC vs WebSocket

WebRTC wins
UDP: late packets dropped, not queued
Jitter buffers + Opus FEC built in
Echo cancellation in the stack
Survives wifi → LTE handoffs
WebSocket wins
Trivial server-to-server setup
TCP is fine inside a datacenter
Easy to proxy, log, and record
No SDP / ICE / TURN to operate
The classic mistake
Shipping WebSocket audio from end-user devices because it worked on office wifi. On a real mobile network, TCP head-of-line blocking turns one lost packet into a growing audio backlog — the conversation drifts seconds behind. Client-to-server audio over the public internet wants WebRTC.

SIP / PSTN — When the Client Is a Phone

Phone calls remain the largest deployed voice surface: support lines, reservations, outbound reminders. A SIP trunk (Twilio, Telnyx, or a framework's built-in telephony) bridges the PSTN into your pipeline. Budget for its constraints — 8 kHz narrowband audio that degrades ASR, carrier-side latency you cannot optimize away, and DTMF keypresses as a parallel input channel.

browser/app → webrtcserver ↔ server → websocketphone number → sip/pstn
04 · Execution

Failure Modes

Voice agents fail in ways text agents cannot, and the failures are audible — a user forgives a clunky UI far more readily than being interrupted, talked over, or trapped in a loop with a machine hearing its own voice. Four modes account for most production incidents.

Echo loops
The agent's own TTS leaks from speaker to microphone, ASR transcribes it, and the agent starts responding to itself — sometimes indefinitely. Defense in depth: acoustic echo cancellation at capture, half-duplex gating of ASR while the agent speaks, and a similarity check that discards transcripts matching what the agent just said.
Aggressive VAD cutting users off
An endpointer tuned for snappy demos fires on every thinking pause. The user says "I want to change my flight to… um…" and the agent barrels in. Users stop trusting the agent to listen and start talking in defensive, compressed bursts — which degrades ASR further. Tune on recordings of real, hesitant speakers, never on your own crisp test phrases.
Latency spikes mid-conversation
The first three turns land in 600 ms; turn four takes four seconds because the context grew, a tool call blocked, or the LLM provider hiccuped. The caller says "hello? are you there?" — and that utterance lands as new input on top of the late reply, derailing the turn state. Track p95 per turn, cap context growth, and speak a filler whenever the budget is about to blow.
Noise-induced hallucinated transcripts
ASR models are trained to produce fluent text, so silence, music, or a passing truck can transcribe as confident phrases — famously, whole sentences from nothing. The agent then responds to words never spoken. Gate ASR behind VAD so noise never reaches it, respect confidence scores, and confirm before acting on anything consequential heard through noise.
Common thread
Every one of these is invisible in a quiet office on good wifi. Test with speakerphones, background chatter, hesitant speakers, and throttled networks — the conditions of an actual phone call.
04 · Execution

Ship Checklist

The distance between a voice demo and a voice product is measured in edge cases: noisy rooms, slow talkers, flaky networks, long tool calls. Green across the board before a real caller hears your agent.

pre-launch · 0/10 complete
Echo cancellation verified on speakerphone and earbuds
capture
VAD endpointing tuned — slow speakers not cut off
vad
Time-to-first-audio p95 under 800 ms
latency
Barge-in cancels TTS and truncates context to audio heard
turns
Filler phrases cover any tool call slower than 800 ms
tools
ASR tested against noise: car, cafe, street, bad mic
asr
Fallback path when ASR confidence collapses (text or human)
safety
Load-tested over real jitter and packet loss, not localhost
transport
Every session records audio + transcript for replay
ops
Silence timeout and graceful goodbye implemented
ux
Where to start
If you adopt only three: measure time-to-first-audio at p95, implement real barge-in with context truncation, and record every session. Those three surface almost every other problem on this list.
Voice AI Reference
vad · asr · tts · duplex
experiments.tyler.lab