Every lesson in this course built one piece of a production voice pipeline. This capstone puts all nine modules back together as one coherent build, with the level of detail you’d actually want walking into week one: a realistic brief, a phased checklist with acceptance criteria you can check off, a launch gate that blocks going live until the system earns it, and guidance for defending the architecture to the people who fund it.
The brief
You’re the engineering lead for a voice support agent at Northbridge Mutual, a regional insurer handling roughly 850,000 policyholders across auto, home, and renters lines. The current Tier-1 phone support runs on a legacy IVR that policyholders openly hate, routing over 60% of calls to a human agent regardless of how simple the request is. Northbridge wants a voice agent that handles policy status checks, claims filing status lookups, and first-notice-of-loss intake for straightforward claims, with a warm, context-preserving handoff to a human agent for anything involving a coverage dispute, a total-loss claim, or a caller who is upset. The agent needs to run in English and Spanish, across roughly 40,000 calls a day at peak season (storm season spikes to double that), and must be reachable both by phone and through a “call support” button in the mobile app.
Three constraints make this a genuine production build rather than a demo, and each traces back to a specific module of this course. Callers are reading out policy numbers, claim numbers, and sometimes card details for deductible payments, so PII handling in transcripts and recordings is non-negotiable (Module 7). A wrong or hallucinated action, like marking a claim withdrawn when the caller only asked what withdrawal would mean, has real financial consequences, so tool-call guardrails need to be airtight (Module 7). And the system has to hold its quality bar through a storm-season volume spike that doubles concurrency overnight, not just in a calm-week pilot (Module 8).
Architecture overview
Every box in this diagram is something you built, named, and tested in an earlier lesson. Nothing here is new engineering; this is assembly.
flowchart TB
subgraph CHAN["Channels (Module 8, Lesson 24)"]
PHONE["Telephony\nTwilio Media Streams / SIP"]
APP["Mobile app\nnative SDK, WebRTC"]
end
subgraph TRANS["Transport normalization (Module 8)"]
NORM["Codec + sample-rate\nnormalization"]
end
subgraph CORE["Dialogue core (Modules 1-6)"]
ASR["Nova-3 streaming ASR\n(en + es)"]
LLM["claude-sonnet-5 dialogue core\n+ tool calls: policy lookup,\nclaims status, FNOL intake"]
TTS["sonic-2 / eleven_flash_v2_5\nTTS, barge-in enabled"]
end
subgraph GUARD["Guardrails (Module 7, Lessons 20-21)"]
INJ["Injection screen"]
PII["PII redaction\n(transcript + audio)"]
TOOLGATE["Tool-call allowlist\n+ confirmation gate"]
ESC["Human escalation\nrouter"]
end
subgraph CTL["Control plane (Module 7, Lessons 19-21)"]
EVAL["Golden eval set +\nLLM-judge scoring"]
TRACE["End-to-end tracing"]
DASH["Latency / WER /\ncost dashboards"]
end
subgraph SCALE["Scaling (Module 8, Lessons 22-23)"]
POOL["Connection pools +\nautoscaled media servers"]
end
PHONE --> NORM
APP --> NORM
NORM --> ASR --> INJ --> LLM --> TOOLGATE --> TTS
LLM --> ESC
TTS --> PII
POOL -.-> NORM
POOL -.-> ASR
POOL -.-> TTS
EVAL -.-> LLM
TRACE -.-> CORE
DASH -.-> TRACE
style PHONE fill:#EEF0F7,stroke:#6366F1,color:#0F172A
style APP fill:#EEF0F7,stroke:#6366F1,color:#0F172A
style NORM fill:#EEF0F7,stroke:#6366F1,color:#0F172A
style ASR fill:#f0fdf9,stroke:#0D9488,color:#0F172A
style LLM fill:#f0fdf9,stroke:#0D9488,color:#0F172A
style TTS fill:#f0fdf9,stroke:#0D9488,color:#0F172A
style INJ fill:#fff7ed,stroke:#f59e0b,color:#0F172A
style PII fill:#fff7ed,stroke:#f59e0b,color:#0F172A
style TOOLGATE fill:#fff7ed,stroke:#f59e0b,color:#0F172A
style ESC fill:#fff7ed,stroke:#f59e0b,color:#0F172A
style EVAL fill:#fff7ed,stroke:#f59e0b,color:#0F172A
style TRACE fill:#fff7ed,stroke:#f59e0b,color:#0F172A
style DASH fill:#fff7ed,stroke:#f59e0b,color:#0F172A
style POOL fill:#f0fdf9,stroke:#0D9488,color:#0F172A
The one design decision worth calling out explicitly: the tool-call gate sits between the LLM core and TTS, not after, so a hallucinated cancel_policy call never reaches a spoken confirmation to the caller in the first place, per Lesson 20’s principle that consequential tool calls need pre-execution checks, not post-hoc content filtering.
Build checklist, phase by phase
Work through these in order. Each phase has a concrete acceptance criterion; do not start the next phase until the current one’s criterion is genuinely met, not just “sounds fine on a test call.”
| Phase | What you build | Lessons | Acceptance criterion |
|---|---|---|---|
| 1. Transport + ASR foundation | Twilio Media Stream and WebRTC ingestion, codec normalization, Nova-3 streaming ASR configured for en + es | 1-3, 24 | WER under 8% on a 200-call bilingual sample; TTFA for ASR finalization under 300ms p95 |
| 2. Conversational core | Dialogue core on claude-sonnet-5, tool integrations for policy lookup, claims status, and FNOL intake, barge-in-aware TTS with sonic-2 | 4-9 | Task success >= 75% on a 100-item scripted-intent test set; barge-in interrupts TTS within 200ms |
| 3. Evaluation | Golden eval set built from 200 real pilot-call transcripts, LLM-judge rubric, turn- and conversation-level metrics wired to a dashboard | 19 | Golden set covers all 3 intents plus escalation cases; judge scores correlate with human QA rating on a 30-call spot check |
| 4. Guardrails + security | Injection screening on caller audio, automatic PII redaction on transcripts and recordings, tool-call allowlist with confirmation gate on destructive actions, mandatory AI-disclosure opening line | 20 | Zero PII leaks across a 50-item redaction test set; zero unconfirmed destructive tool calls across an adversarial 40-item injection set |
| 5. Observability | End-to-end session tracing across ASR/LLM/TTS stage boundaries, audio retention policy with consent notice, stage-level and drift alerting | 21 | Any slow or failed call traceable to a specific stage within 5 minutes of investigation; drift alert fires on a synthetic 12% latency regression within 24 hours |
| 6. Scaling + deployment | Connection pooling, autoscaled media servers with session affinity, rate-limit backpressure and queueing, load test at 2x expected storm-season peak | 22, 23, 24 | Sustained load test at 20,000 concurrent sessions holds p95 TTFA under 1.2s with zero dropped calls from pool exhaustion |
Two phases are easy to underestimate going in. Phase 2 typically eats more time than Phases 1 and 3 combined, because barge-in handling and multi-turn tool-call correctness are where a voice agent’s real complexity lives, not the ASR wiring. And Phase 4 is where teams are most tempted to cut corners under deadline pressure, which is precisely backwards for an insurer handling policy and payment data: a guardrail gap discovered after launch costs far more in trust and regulatory exposure than the week it takes to build the adversarial test set now.
The launch gate
Borrow directly from Lesson 19’s evaluation patterns and Lesson 20’s guardrail principles, applied across every dimension this capstone touches. A launch that passes on task success but leaks PII, or passes both but can’t hold latency under storm-season load, is not ready:
# capstone_gate.py - run this before any go-live decision.
# Every threshold maps to a phase above; a failure here means
# a specific phase's acceptance criterion regressed.
LAUNCH_THRESHOLDS = {
"task_success_rate": 0.80,
"containment_rate": 0.65,
"p95_ttfa_seconds": 1.2,
"wer_bilingual": 0.08,
"guardrail_violations": 0, # hard zero, not a threshold
}
def launch_gate(results: dict) -> bool:
failures = []
for metric, threshold in LAUNCH_THRESHOLDS.items():
value = results[metric]
# Direction depends on the metric: latency, WER, and
# violations must be BELOW threshold; everything else ABOVE.
bad = (value > threshold if metric in
("p95_ttfa_seconds", "wer_bilingual", "guardrail_violations")
else value < threshold)
if bad:
failures.append(f"{metric}: {value} (need {threshold})")
if failures:
print("LAUNCH BLOCKED:\n" + "\n".join(failures))
return False
print("Launch gate passed.")
return True
Run this against the full golden set, including the adversarial injection slice and the storm-season load test results, not a convenient calm-week subset. If guardrail_violations is anything other than zero, nothing else on the list matters until it’s fixed; that is the one metric here with no acceptable partial credit.
Presenting to stakeholders
Northbridge’s COO and CFO are not going to read this course. They’re going to ask three questions, and the answers should be ready before the launch review, not improvised during it.
“Why not just use the cheapest cascaded stack for everything and skip the native speech-to-speech comparison?” This is Module 8’s cost-modeling argument, compressed: the cascaded stack is cheaper on paper, but Lesson 22’s turn-density math shows the gap narrows fast on real, chatty calls, and cost has to be weighed against the containment-rate gains a lower-latency, more natural-feeling agent actually produces, not just the per-minute invoice line.
“What happens when the agent doesn’t know an answer?” This is where the escalation design from Lesson 20 earns its keep in a boardroom. The agent is built to recognize the edges of its own competence, coverage disputes, total-loss claims, an upset caller, and hand off to a human agent with full context preserved, rather than guessing. A wrong guess on a coverage question isn’t a minor UX miss for an insurer, it’s a liability question, which is exactly why the tool-call gate and escalation router exist as hard requirements, not nice-to-haves.
“Why does this cost more per minute than our old IVR?” The honest answer is the difference between routing tones on a keypad and an agent that actually resolves calls. The old IVR’s per-minute cost is lower because it does almost nothing; the 60%+ of calls it can’t handle still cost a human agent’s fully loaded time downstream. The relevant comparison isn’t IVR cost versus voice-agent cost, it’s total cost per resolved call, and that’s the number Phase 6’s containment-rate target is built to win on.
“Could we have just used a platform like NextNeural instead of building this?” For a smaller book of business, yes, and Lesson 3 and Lesson 24 cover exactly that path: an agent, a campaign, and a POST /v1/calls would get a basic version live in days. Northbridge’s case is different because the compliance surface (PII in transcripts, tool-call guardrails around real policy actions, a bilingual eval set tied to regulatory audit) needs the custom guardrail and observability layer this capstone builds, not because a managed platform can’t handle telephony, but because owning the request path is what lets Phase 4’s guardrail gate sit exactly where Lesson 20 says it has to. That tradeoff should be revisited as the product matures, not treated as settled forever.
Frame every technical decision this way: not what it is, but what it protects or what it costs to skip. That’s the difference between a launch review that approves go-live and one that stalls in questions.
What to build next
Once Northbridge’s agent is live and holding its launch-gate numbers through a full storm season, the natural extensions follow the same cost-benefit discipline Module 8 taught for scaling decisions: adopt them when call data justifies them, not because they’re available. Outbound proactive calling, notifying policyholders in a storm-affected zip code before they call in, would shift load away from inbound peak hours entirely. Expanding beyond English and Spanish to the top languages showing up in the “could not classify intent” tail of the eval dashboard would recover containment rate lost to language gaps rather than latency or accuracy gaps. And sentiment-based escalation, routing a caller to a human the moment frustration is detected rather than waiting for an explicit request, would extend the guardrail work in Lesson 20 from a safety mechanism into a genuine experience improvement.
The system you’ve now designed end to end, across nine modules, is not a finished artifact. It’s a pipeline with a measurement loop and a safety gate built in from day one, which is exactly what lets it keep earning caller trust through the next storm season instead of quietly failing the way an unmeasured system inevitably does.