Every missed call is a booking the competitor took.
The client runs home-services across multiple regions — repairs, installs, maintenance plans. Demand arrives by phone: a broken appliance is an urgent, spoken problem. Their line put callers through a touch-tone menu, three agents doing the same six intake questions all day, a queue that overflowed at peak, and voicemail after hours. Roughly a third of inbound calls never became anything. The ones that connected still took eight minutes because the agent was typing into a CRM while the caller waited.
~32% of calls abandoned. 8-minute average handle time. Zero coverage 6pm–8am.
Picks up on the first ring, 24/7. Books, reschedules, quotes, and triages inside the call.
A call is a state machine, not a script.
A demo voice bot reads a decision tree. A production one holds state — who is calling, what they have already told you, what has been confirmed, what is still open — and treats every turn as a loop it can be interrupted inside. The hard parts are not the words. They are time (a turn has to complete before the silence gets awkward), interruption (the caller talks over the agent and the agent has to yield), and consequence (the agent books a real van, on a real day, exactly once).
one turn of the loop
- 01Caller speaks — streaming audio in over the SIP leg
- 02Transcribe — partial hypotheses every ~150ms, not after the sentence
- 03Endpoint — decide the caller has actually stopped, not just paused
- 04Decide — intent + slots + which tool, against the conversation state
- 05Act — run the workflow — or ask one clarifying question
- 06Speak — stream TTS back, cancellable the instant the caller talks
the whole loop, caller-stops to agent-speaks, has a budget of ~800ms before it feels like a bad line.
Six surfaces between the ring and the resolved job.
telephony
RingCentral · SIP trunk · numbers
Inbound DIDs per region, SIP termination into the agent runtime, and the transfer leg back out to a human queue. Call recording and CDR export for compliance.
agent-runtime
Vapi · Deepgram · ElevenLabs · LLM
The turn loop — streaming STT, endpointing, the decision model with tool-calling, and streaming TTS. One config per agent persona (booking, support, claims).
orchestrator
Node · TypeScript · Postgres · Redis
Our layer. Owns conversation state, the tool contracts the model is allowed to call, idempotency, retries, and the hand-off decision. The model never touches a backend directly.
workflow-engine
n8n · webhooks · queues
Every real-world action is an n8n workflow — check availability, hold a slot, create the job, send the SMS confirmation, push to the field-service system. Versioned, replayable, observable.
handoff-console
Next.js · WebRTC · SSE
What the human agent sees on a warm transfer: live transcript, extracted fields, the reason the agent escalated, and one click to accept the call with all of it pre-filled.
analytics
ClickHouse · dashboards
Per-call: intent, containment, duration, interruption count, tool latency, transcript. Aggregated: containment rate, after-hours capture, cost per resolved call.

The floor did not shrink — it moved up a tier. Agents stopped doing intake and started handling the calls that genuinely need a person.
Bought the parts that are commodities. Built the part that isn't.
Transcription, synthesis, and media transport are solved problems with three good vendors each — so we rented them, behind an interface we control. The orchestrator is ours, because that is where the risk lives: what the agent is allowed to do, how it recovers, and when it stops trying and gets a human.
RingCentral
Telephony
Owned the numbers and the SIP trunk already. We terminated it into the runtime instead of their old IVR, and used it for the outbound transfer leg.
Deepgram
Speech-to-text
Streaming transcription with word-level timestamps and endpointing hints. Chosen for latency and for interim results we could act on before the caller finished.
ElevenLabs
Text-to-speech
Low-latency streaming voice with a consistent persona per agent. Streamed in chunks so the first word plays while the sentence is still generating.
Vapi
Agent orchestration
Glues STT, the LLM turn, and TTS into one media session with barge-in handling. We ran our tool layer behind its function-calling interface.
n8n
Workflow automation
Every side-effect — availability, slot holds, job creation, confirmations, CRM writes — is an n8n workflow. Non-engineers can read the flow; every run is logged and replayable.
Orchestrator
Medhya
The part we own: conversation state, the allow-listed tool contracts, idempotency keys, retry policy, confidence gating, and the escalation rules. Provider-agnostic on purpose.
Every vendor sits behind an adapter. Swapping the TTS provider is a config change and a voice re-test, not a rewrite.
800 milliseconds or it feels broken.
On a phone call, the gap between a caller finishing and the agent replying is the whole ballgame. Past roughly a second of silence people say “hello?”, talk over the reply, or assume the line dropped. A naive pipeline — wait for final transcript, call the model, wait for the full response, synthesize the whole thing — is two to three seconds. Unusable.
The turn is a streaming pipeline with a written budget for every stage. Transcription is acted on at interim results, not final. The endpoint decision has its own bounded timer. The model streams and the first sentence is synthesized while the rest generates. Fixed confirmations are synthesized speculatively and thrown away if the branch changes. When a tool genuinely takes a second, the agent says a natural filler first so the silence is covered.
Why it's hard — Every optimisation trades against correctness — act too early on an interim transcript and you answer the wrong question; speak speculatively and you sometimes have to walk it back. The budget only holds because each stage is measured in production, not assumed.
turn latency budget — target < 800ms

Knowing when the caller has actually stopped.
Humans pause mid-sentence — to think, to read a number off a bill, because a kid walked in. Treat every pause as “your turn” and the agent interrupts constantly. Wait too long to be safe and the agent feels slow and dim. And when the agent is mid-sentence and the caller cuts in, the agent has to stop instantly — finishing your sentence over someone is the rudest thing a phone bot does.
An explicit state machine over voice activity, the STT provider’s endpoint hints, and a silence timer whose length depends on what was just asked. A short MAYBE_DONE hold absorbs mid-sentence pauses. During playback, any caller speech kills the TTS within a single audio frame, keeps the partial utterance, and drops straight back to listening. Interruption-to-silence is measured and held under 120ms.
Why it's hard — It’s all timing, and timing bugs only show up on real calls with real cross-talk — the same 300ms that makes one caller feel heard makes an impatient one feel ignored, so the thresholds are per-question and tuned from transcripts.
endpointing + barge-in state machine
barge-in is the single biggest “does this feel human” lever.
It books a real van, on a real day, exactly once.
The moment the agent stops talking and starts doing, mistakes cost money and trust. A retried request after a timeout must not create two jobs. A call that drops right after the caller says “yes” must still result in the booking. A call that drops before that must leave nothing behind. And the people who run operations need to read and change what the agent does without a deploy.
Every side-effect is an n8n workflow with a deterministic idempotency key derived from the call and the confirmed slots. Booking is two-phase — a 90-second slot reservation, then a separate confirm step that is the only thing that writes. Reservations auto-release if the branch changes or the call drops. Every run is stored and replayable, and the flow is a diagram an ops lead can actually follow.
Why it's hard — “Exactly once” across a flaky phone network, a model that can retry itself, and three external APIs is a distributed-systems problem wearing a friendly voice — the failure that matters is the silent double-booking nobody notices until the second van shows up.
n8n — book-appointment workflow
a dropped call after “confirm” still completes; a dropped call before it leaves nothing behind.

Handing off without making the caller start over.
Some calls should not be handled by an agent — a genuinely angry customer, an edge case, an explicit request for a person. The worst version of this is the transfer that dumps the caller into a new queue where they repeat everything from the top. That single experience undoes every good call the agent handled that day.
Escalation is a first-class, measured path with explicit triggers. Before the human is even connected, the orchestrator pushes the transcript, the extracted fields, and the reason for escalation to the hand-off console. The transfer goes out over RingCentral with a one-sentence whisper of context in the human’s ear. The caller is told a person is joining, and never asked to repeat themselves. If no one is free, the agent books a callback as a real job.
Why it's hard — The hard part is cultural as much as technical — the system has to treat “get a human” as success, surface it on the same dashboard as containment, and resist the temptation to tune escalations down to make a number look good.
warm transfer — agent → human
the escalation is a feature, not a failure — it is measured and tuned, not hidden.

It never promises something that isn’t true.
A voice agent that makes up a price, invents an appointment slot, or agrees to a discount it can’t give is worse than no agent at all — it creates obligations the business has to honour or explain away. And you can’t catch that by reading a few transcripts; a prompt change that looks fine can regress behaviour on a whole class of calls.
The model only ever proposes — the orchestrator executes, and only allow-listed tools with schema-checked arguments. Prices come from a list, dates from real availability; neither can be asserted by the model. A confidence gate turns uncertainty into a question, then into an escalation. Every prompt or config change runs against 150+ replayed real calls plus a red-team set — accents, cross-talk, hold music, spam — and any hallucinated commitment is a hard fail that blocks the change.
Why it's hard — Evaluation is the actual product here — the agent is only as trustworthy as the harness that gates changes to it, so the harness had to be built with the same care as the runtime and kept honest as call patterns drift.
guardrails + evaluation harness
eval harness, run on every prompt change

One quarter in, on the numbers that pay for it.
71%
of inbound calls resolved end to end with no human
0
calls to voicemail — the line is answered 24/7
3.4 min
average handle time, down from ~8
+28%
after-hours bookings captured that used to be lost
Figures from the client's first full quarter on the platform. Containment is measured conservatively — a call only counts as resolved if no human touched it and the job was created.
The through-line, stated.
The model proposes, the system disposes
The LLM chooses words and suggests a tool. Whether that tool runs, with what arguments, is the orchestrator's decision against a schema and a state.
Latency is a budget, not a hope
Every stage of the turn has a number, measured in production. Regressions are caught by the budget, not by a user complaint.
Interruption is normal
The caller talking over the agent is the expected case, not an error. The agent yields inside one audio frame, every time.
Side-effects are workflows, and workflows are replayable
Nothing consequential happens in a prompt. It happens in a versioned, logged, idempotent workflow an operator can read.
Escalation is a success metric
Getting the right calls to a human, fast, with context, is a feature. It sits on the same dashboard as containment.
Vendors sit behind adapters
STT, TTS, telephony, orchestration — each is one good vendor today and a config change tomorrow. None of them are load-bearing on their own.
The eval harness gates the runtime
No prompt or config reaches a real caller without passing replayed real calls and a red-team set. A hallucinated commitment is a hard fail.
Say the honest thing
No invented prices, no asserted dates, no agreeing to something the business can't do. When unsure, the agent asks or hands off.
3
agent personas — booking, support, claims intake
~800ms
p50 turn latency, held in production
150+
real calls in the eval set, replayed on every change
24/7
answered, every region, since go-live
Booking & support agents — live in every region. Claims intake — live, expanding coverage. Outbound reminders & failed-payment calls — in build. Spanish-language agent — in build.
Telephony
RingCentral · SIP trunk · DIDs per region · CDR export
Voice
Deepgram (streaming STT) · ElevenLabs (streaming TTS) · Vapi (media + turn loop)
Orchestrator
Node · TypeScript · PostgreSQL · Redis · allow-listed tool contracts
Workflows
n8n · webhooks · queues · replayable runs
Console
Next.js · WebRTC · SSE
Analytics
ClickHouse · per-call + aggregate dashboards
Eval
replay harness · red-team set · CI gate on prompt/config changes
Case 02 — Switchboard
Anyone can wire an LLM to a phone number. Making it fast enough to feel human, safe enough to touch the calendar, and honest enough to trust with customers — that is the engineering. Switchboard runs it in production, every call, every region.
