7 min read

Published

Migrating Production Voice Agents from Vapi to LiveKit

We ran dealership voice agents on Vapi and now run them on LiveKit. What we did before the migration so it would not become a rewrite, and what it did to cost.

Our voice-agent infrastructure cost went from $0.26 to $0.10 per minute, a reduction of about 62%. Those are our deployment figures, not a Vapi-versus-LiveKit price comparison: the migration also gave us room to change providers and tune the pipeline. They describe infrastructure spend, not the engineering and operational cost of owning it.

Most of that gap is one decision: moving our production voice agents off Vapi and onto LiveKit. I have wanted to write this down for a while, partly because I kept looking for a post like it when we were deciding and never found one, and partly because the most useful thing we did was not the migration. It was the thing we did a year before it.

Where we started

I built Spyne’s conversational AI stack on Vapi from nothing. Voice agents that pick up dealership calls, qualify leads, run follow-ups, over real phone numbers, with real people on the other end who do not know or care that they are talking to software.

Vapi was the right call. I want to say that plainly, because posts like this tend to read as “we escaped.” We did not escape anything. When you have zero agents in production and a business waiting on them, a managed platform gives you media transport, telephony, STT and LLM and TTS orchestration, interruption handling and a dashboard, on day one, for a per-minute fee. That trade is excellent right up until it is not.

Why it stopped being enough

Three things, in the order they started bothering me.

The first was debugging. The incidents that matter in voice are never “the call failed.” They are “the agent went quiet for two seconds at 4pm on one call and the customer hung up.” To answer that you need per-stage latency, the transcript lined up against the audio, and the ability to follow one utterance through STT, the model, and TTS. Vapi provides recordings, transcripts and call logs. Our limit was how far we could instrument and change the execution path behind them. We wanted to follow a symptom into the worker, reproduce it, and test a fix ourselves.

The second was the conversation itself. Where a voice agent feels good or bad lives deep in the pipeline: how it decides the caller is done talking, how it handles being interrupted, what it does with a half-generated sentence when the caller cuts in. On a managed platform those are settings, if they are exposed at all. Our differentiation was conversation quality, and I could not get at the parts that determined it.

The third was the bill. A platform fee is small per minute and irrelevant at low volume. At production volume it was our single largest controllable line item, sitting on top of the model and telephony costs we were paying anyway.

None of these was decisive on its own. Together they were.

The thing we did a year earlier

While we were still fully on Vapi, we rebuilt the platform around a split: a control plane that owns tenant configuration and call state, and stateless workers that execute calls.

Every call is described by a typed runtime profile. Providers and models, prompts, tools, transport, recording, voicemail behavior, idle handling, duration limits, transfer rules, reporting. Workers take a profile in and send lifecycle events out. The events are idempotent and signed, and the pipeline is traced with OpenTelemetry so a call looks the same in our tooling no matter what ran it.

At the time this was mostly about hygiene. It turned out to be the whole migration. The LiveKit worker never had to imitate Vapi. It had to satisfy a contract we had already been running in production for months, and product code, dashboards and reporting did not know which engine was underneath.

If you take one thing from this post: do not migrate from one engine to another. Write your own contract first, make your current engine satisfy it, and then the migration is adding a second worker instead of doing a rewrite.

How the cutover actually went

Boringly, which is the point.

We built the LiveKit worker against the existing contract. Same profiles in, same signed events out.

Then we graded it before trusting it. I had built a telephony simulator for exactly this: scripted callers, answering-machine cases, IVR traversal, interruptions, and automated scoring of what the agent did. We ran both engines through the same suite. Calling the agent ourselves helped us hear problems; replayable scenarios helped us tell whether we had fixed them.

Traffic moved by profile, not by flag day. Because every call is described by data, moving a tenant or a use case to LiveKit was a configuration change, and anything that misbehaved moved back the same way.

And we kept the old worker around longer than felt necessary, because live traffic finds what simulators do not. Speakerphones in moving cars. Carrier quirks. Answering machines that sound exactly like a person for the first four seconds.

What I would put in the rollout gate

A migration needs acceptance criteria beyond “the call connected.” I would compare the two engines on the same scenarios and traffic cohorts:

GateWhat to compareWhat should stop expansion
Conversation timingEnd of caller speech to first audible response, at p50 and p95More long gaps or premature replies
Task completionCorrect lead capture, tool results and human transfersLost fields, duplicate actions or failed handoffs
Lifecycle integrityOne terminal call result, with recording and transcript referencesMissing, duplicated or contradictory results
OperationsWorker failures, carrier errors and cost using the same billing boundaryUnexplained regressions or missing telemetry

Set thresholds from the existing deployment before shifting traffic. Keep the engine choice fixed for the life of a call; rollback should route new calls to the previous worker while active calls drain. Replaying a failed call must not repeat a booking or send a second follow-up.

What actually hurt: telephony

The AI pipeline was not the hard part. The phone system around it was.

A managed platform hides telephony. Once you own the stack, you own SIP trunking into LiveKit, and you own everything we had already learned running the telephony layer across Twilio, Vonage, and Telnyx: caller ID and CNAM, STIR/SHAKEN attestation, answering-machine detection, cold and warm transfers to humans, the compliance machinery around outbound calling. None of it is exotic. All of it is production-critical, every carrier has opinions, and it needs real engineering time. If your team has never operated SIP, that is the half of the project to staff first.

What actually hurt: turn-taking

Owning the pipeline means owning the hardest problem in voice, which is knowing when the caller is done. Silence thresholds are wrong in both directions at once. LiveKit’s turn-detection model is a good default, but tuning end-of-turn, interruptions and filler handling against your real call distribution is where the quality comes from, and it is work.

It is also where owning the stack paid for itself. When we needed pipeline behavior that did not exist, we could read the framework and change it. That habit turned into pull requests. I now have merged changes in both the Python and Node LiveKit Agents SDKs. They expose OpenAI Responses API controls we needed for latency and cost, and keep the caller’s number on Twilio warm transfers. The distance from platform user to platform contributor is a lot shorter than it looks from the managed side.

Was it worth it

Per minute we pay a bit under 40% of what we paid before. The main gain is that we can investigate and tune the parts of a conversation we used to struggle to reach. The cost took a spreadsheet to justify; the control took one incident.

I would not tell everyone to do this. If you are before product-market fit, if call volume is low, or if nobody on the team wants to own a phone system, stay on the managed platform. The fee is cheaper than the engineering, and it buys you the time to find out what your product actually needs.

Move when conversation quality is the product, when per-minute economics dominate your unit costs, or when you notice your support tickets are really feature requests for pipeline internals.

Either way, start with a small contract around configuration, call identity and lifecycle events. It takes work while you are managed, but it gives the next engine a boundary to implement. For the economics behind our decision, I wrote up how to account for a voice-agent minute.

Written by

Software Engineer II at Spyne. I build production voice agents, telephony infrastructure, and developer tools, and contribute to the LiveKit Agents SDK.

Experience and open-source contributions →
One API, Many Carriers: Designing a Provider-Neutral Telephony LayerProduct code should talk to a capability, not a vendor SDK. Lessons from running Twilio, Vonage and Telnyx behind one interface.Sep 2026The Real Cost of a Voice Agent MinuteOur infrastructure cost fell from $0.26 to $0.10 per minute. How to meter each component, compare operating costs, and protect conversation quality.Sep 2026Answering-Machine Detection Is Turn-Taking's Evil TwinBefore a voice agent can decide when to speak, it has to decide whether anyone is there. Why AMD is hard, and why you cannot test it by calling your own phone.Sep 2026
Continue exploringWriting indexAll writing.

Built for

The systems behind this post