Assembled Primitives vs. a Platform
I built a voice agent out of cloud primitives, and it worked. It has since been replaced by a purpose-built voice platform, and I think that was the right call.
Part of Voice Systems Field Notes
I spent a long time building a phone channel for a chatbot out of cloud primitives — telephony, an intent service, a function bridge, a speech synthesizer. It shipped, it worked in production, and I learned an enormous amount doing it.
It has since been replaced by a purpose-built voice agent platform, and the honest summary is that the platform is better at this.
What "assembled primitives" actually costs
Every one of the primitives was good. The problem is that a real-time conversation is not the sum of them, and every gap between two services was mine to own:
- Turn-taking — deciding when the caller has finished speaking, when the agent may start, and what happens when they collide. No individual service owns this. The seam between them does, and the seam is code you wrote.
- Barge-in — a caller interrupting mid-sentence means cancelling in-flight synthesis, which turns every downstream call into something that must be cancellable, which changes idempotency requirements in places that have nothing to do with voice.
- Latency accounting — callers hear every hop as silence. Each service was individually fast. The budget was consumed by the joins.
- Correlation — one caller's journey crossing four services means traces that die at each boundary unless you design correlation in from the start. I added it during hardening, which was the wrong time.
A platform built for voice agents owns those seams as its product. That is not a small convenience; it is most of the hard part.
Why I do not regret building it
Two reasons, and neither is sentimental.
The first is that the constraints only became legible by building it. "Turn-taking is subtle" is a sentence you can read and not understand. Having shipped the thing that gets it wrong at 2am is different knowledge, and it is what makes me able to evaluate a platform's turn-taking rather than take its word for it.
The second is that the part underneath survived the migration. The integration layer beneath the channel is a factory over adapters that normalize every channel into one envelope, and the interface deliberately says nothing about whether a reply arrives in two seconds or two hundred milliseconds. Because fulfillment never branched on channel, replacing the entire voice front end did not touch it.
That is the thing worth generalizing: put the abstraction where the churn isn't. Vendors, models and platforms in this space change on a timescale of months. The shape of "a conversation with a user" changes far more slowly. If your abstraction is drawn around the vendor, every vendor change is a rewrite. If it is drawn around the conversation, a vendor change is an adapter.
Still evaluating
Evaluation of further voice platforms is ongoing, running in parallel with the current build rather than after it.
That sounds like indecision and it is not. The deciding constraints on a production voice system are frequently not technical — they are contractual, jurisdictional, and about where data is allowed to be processed. Those constraints can change independently of anything engineering does, and they can invalidate a choice that was correct on the day it was made.
So the useful posture is not "pick the winner and stop looking." It is to keep the integration boundary sharp enough that the answer can change without the system caring, and to keep a current read on the alternatives so that if the answer does change, the migration is a decision rather than an emergency.
I have now been on both sides of that migration once. The second time will be cheaper, entirely because of where the seam is.
Written by
Adrian Romo
Senior Backend Engineer building scalable Python APIs, AWS Lambda architectures, voice systems, and enterprise integrations.
Related
Keep reading
Six Posts a Day, and the Scheduler That Learned to Say When
A social pipeline that published one Reel a day and nothing else, because per-format quotas were ceilings and nothing was asking for the other formats.
A Planner That Will Not Onboard Anything
The tool that decides how to onboard a new service into my homelab has no ability to onboard a service. That separation is the design, not a limitation.
A Sandbox for Demos You Throw Away
One box runs every client demo I have live. The pattern is unremarkable; the interesting part was my monitoring reporting four healthy demos as missing.
Keep going
Where to next?
Browse more technical writing, see the engineering case studies, or reach out directly.