What it takes to go from 43 calls a month to a quarter of a million, what a full-duplex interaction model changes, and what each option costs per minute with every fee counted.
Cost is not what stops this system from handling call-center volume. State is. The arithmetic below matters, but it is the second problem.
A duplex model deletes two thirds of the per-minute bill. In the current stack, speech-out and the language model are 68% of the compute cost. A speech-to-speech model has neither line: one model hears audio and emits audio. Cascade with Opus 5 is $0.0446 a call-minute; Gemini Live is $0.0228.1
Self-hosting PersonaPlex is not a cost play. On an H100 at eight concurrent streams it costs $0.0355 per stream-minute in GPU time alone, more than the entire managed duplex stack. It becomes cheaper only past roughly twelve streams per GPU, or on small GPUs that scale to zero. The case for it is control, not price.2
One unmeasured number decides the whole question. Concurrent streams per GPU. Every published figure I could find for a Moshi-class model is second-hand. Measure it before committing to hardware; the answer moves the monthly bill by 8×.
Elle today takes about 43 calls a month and never sees two at once. The ceilings below are ordered by when they bite, and the first three are in our own code rather than in anyone's pricing page.
| Ceiling | Bites at | Why | Fix |
|---|---|---|---|
| Per-instance state | 2 instances | SQLite lives on the container's own disk. Two Cloud Run instances hold two different databases: caller history, consent, and the architecture override disagree depending on which one answers. | Move to Cloud SQL or Firestore. The GCS archive made history durable; it did not make it shared. |
| Double-booking | ~5 concurrent | Booking reads free/busy, then writes. Two callers offered the same slot both pass the check before either writes. | A short-lived hold row with a unique constraint on the slot, taken before the calendar write. |
| No overflow path | capacity | When capacity runs out the caller gets silence. There is no queue, no callback offer, no "we are busy" branch. | A TwiML fallback that offers a callback and captures the number. |
| Vendor concurrency | ~10–50 | Twilio caps concurrent calls per account, Deepgram caps concurrent streams, and Anthropic rate limits are per-organisation. | Raise all three by ticket before a launch, not during one. |
| Cold start under burst | ~25 concurrent | One min-instance. A burst above what one container holds starts new ones while callers are already on the line. | Min-instances tracking the busy-hour floor, not one. |
Ordered by the concurrency at which each becomes a live incident. Concurrency estimates assume 3.5-minute calls arriving over an eight-hour business day with a 2.5× busy-hour peak.3
None of the first three cost money to fix. They are a week of work, and they are load-bearing for every number in the rest of this document, because a cheaper per-minute price on a system that loses bookings is not cheaper.
The cascade, which is what runs today. Audio goes to Deepgram for transcription, the transcript goes to Claude, Claude's text goes back to Deepgram for speech. Three vendors, three network hops, and a turn cannot start until silence is detected. Our endpointing is set to 1000 ms, so every reply carries a full second of dead air before any model has been asked anything. Realistic turn latency is 1.2 to 2.0 seconds.
Managed duplex. Gemini Live on Vertex takes audio in and gives audio out from a single model that is listening and speaking at the same time. There is no endpointing parameter because there is no endpoint: interruption is a property of the model, not of a timer. We already run this; it is one of the three providers behind the runtime switch.
Self-hosted duplex. PersonaPlex is NVIDIA's full-duplex conversational speech model, built on the Moshi architecture, with role prompts for persona and audio conditioning for voice.4 Code is MIT; weights are under the NVIDIA Open Model License and gated behind accepting terms on Hugging Face. It runs on our hardware, which is the entire point: the voice can be cloned and fixed, the persona is a prompt we own, no audio leaves the project, and there is no per-minute meter.
US dollars per call-minute, marginal cost only; the monthly floor is in §05. Assumes a 3.5-minute call, the assistant speaking half of it at 150 words per minute, and two model turns a minute.5 GPU figures assume eight concurrent streams per GPU at 65% utilisation.2
Two things fall out of this. Speech-out is the most expensive line in the cascade at $0.0135 a minute, more than double speech-in, because Aura-2 bills per character and the assistant does half the talking.6 And the model line swings 5× on model choice alone: Opus 5 costs $0.0168 a call-minute against Haiku 4.5's $0.0040.7
Which means the cheapest available change to the current system is not architectural. Moving the SMS and booking turns to Haiku 4.5 while leaving Opus 5 on the calls that need judgment takes 29% off the per-minute bill, today, with no new infrastructure.
| Architecture | 1k min | 10k min | 100k min | 1M min | $/min at 1M |
|---|---|---|---|---|---|
| Cascade + Opus 5today's stack | $151 | $553 | $4,572 | $45,704 | $0.0457 |
| Cascade + Haiku 4.5one config change | $139 | $425 | $3,292 | $32,904 | $0.0329 |
| Gemini Livemanaged duplex, Vertex | $130 | $335 | $2,392 | $23,904 | $0.0239 |
| PersonaPlex on L4Cloud Run GPU, scales to zero, 2 streams/GPU | $128 | $319 | $2,224 | $22,220 | $0.0222 |
| PersonaPlex on H100GKE, autoscaled, 8 streams/GPU | $8,354 | $8,786 | $13,140 | $57,745 | $0.0577 |
| PersonaPlex on H100GKE, reserved for peak, 3-year commit | $3,861 | $3,974 | $12,404 | $123,202 | $0.1232 |
All-in monthly cost. Every row includes telephony, the model, the Cloud Run bridge, and the fixed floor: a phone number,8 always-allocated min-instances,9 Secret Manager,10 logging,11 the GCS call archive,12 Artifact Registry,13 and egress to Twilio.14 GKE rows add the cluster fee, a load balancer, and node boot disks.15 What is deliberately not counted is at note 16.16
The last row is the trap, and it is the one a vendor will steer you toward. A three-year commitment discounts the hourly rate by 55%, but you must reserve for the busy-hour peak and then pay for all 730 hours in the month. With a 2.5× peak factor that is 2.5× the hardware you average, running idle most of the time. Committed pricing turns a utilisation problem into a contract.
Autoscaling inverts it: you pay for what you use, but the warm floor that keeps first callers off a cold GPU costs $8,074 a month on one H100 whether anyone calls or not. At 1,000 minutes a month that floor is the entire bill, which is why the H100 row starts at $8,354 and the managed row starts at $130.
All-in cost per call-minute against monthly volume, both axes logarithmic. The fixed floor dominates below 10k minutes, which is why every line falls steeply and then flattens. Above about 30k minutes the lines are flat and the ranking stops changing.
Below 10,000 minutes a month, architecture is nearly irrelevant: you are paying for a warm container and a phone number, and the difference between the best and worst option is $24 a month. Above 30,000 the marginal rate is all that matters and the order is fixed.
The H100 line never catches the others at these volumes. It is still falling at 1M minutes because the warm floor is still amortising; it would cross Gemini Live somewhere past 4M minutes a month, which is a 40-seat call center running flat out.
Everything above rests on how many concurrent conversations one GPU holds while staying real-time. A Moshi-class model needs roughly 16–20 GB in fp16, which fits an L4's 24 GB with no room for KV cache growth, and fits an H100's 80 GB with room to batch. Published guidance suggests four to eight sessions on a 48–80 GB card, but that figure is second-hand and I could not reach the paper to confirm it.4 Treat the table below as the shape of the answer, not the answer.
| Streams per GPU | L4, Cloud Run | A100 40 GB | H100 on demand | H100 3-yr commit | vs. Gemini Live at $0.0135 |
|---|---|---|---|---|---|
| 2 | $0.0086 | $0.0471 | $0.1418 | $0.0638 | L4 only |
| 4 | $0.0043 | $0.0235 | $0.0709 | $0.0319 | L4 only |
| 8 | $0.0022 | $0.0118 | $0.0354 | $0.0160 | L4, A100 |
| 12 | $0.0014 | $0.0078 | $0.0236 | $0.0106 | everything but on-demand H100 |
| 16 | $0.0011 | $0.0059 | $0.0177 | $0.0080 | everything but on-demand H100 |
GPU cost per stream-minute at 65% utilisation.2 The rightmost column names the hardware that beats managed duplex on compute at that density.
Measuring this is two days of work and it is the highest-value two days in the whole plan: stand one GPU up, drive N synthetic conversations at it from recorded call audio, and find the N at which time-to-first-audio crosses 300 ms or the model starts falling behind real-time. The answer sets the hardware, the fleet size, and whether this is worth doing at all.
The provider seam is already there. Realtime models sit behind one interface with three implementations, chosen per call, with a runtime override and a TTL. Adding a fourth is one file and one enum value.
src/realtime/ factory.ts VoiceProvider = "deepgram" | "openai" | "gemini" | "personaplex" personaPlex.ts new: WebSocket to the inference service, PCM16 in and out audio/g711.ts already converts mu-law 8 kHz to PCM16 and resamples 8k to 24k
The audio plumbing is done. Gemini Live already needs PCM16 in at 16 kHz and out at 24 kHz, so the stateful resampler with carries across chunk boundaries already exists and is already verified. PersonaPlex speaks the same shape.
A speech-to-speech model does not emit tool calls. Elle's job is booking: she checks a calendar, writes a hold, sends a confirmation. PersonaPlex will happily discuss a Thursday appointment and cannot make one.
The pattern that solves it is a supervisor. The duplex model owns the conversation; its transcript stream feeds a cheap text model that owns the tools and writes results back into the duplex model's context as it goes. That keeps the latency win, because the caller hears the duplex model immediately while the booking resolves behind it, and it costs one Haiku turn per exchange, which is the $0.0040 a minute already in the tables.
It also means the failure modes multiply. The supervisor can lag the conversation, and the caller can be told a slot is held a second before the write fails. That needs designing, not discovering.
| Stage | Trigger | Work | Cost impact |
|---|---|---|---|
| Fix the state | Before any volume at all | Shared database, a booking lock, an overflow branch. Nothing else on this list is safe without it. | ~$40/mosmallest Cloud SQL instance |
| Split the models | Now | Haiku 4.5 on SMS and booking turns, Opus 5 on calls. One config change. | −29% |
| Move calls to duplex | Past 10k min/mo | Flip the runtime switch to Gemini Live for voice, keep the cascade as the fallback provider. Both already exist. | −46% |
| Benchmark PersonaPlex | Past 50k min/mo, or any voice/persona requirement Gemini cannot meet | One GPU, synthetic load, find streams-per-GPU. Two days. | ~$500one-off |
| Self-host | Only if the benchmark clears 12 streams per GPU, or control is worth the premium | Supervisor pattern, GPU autoscaling, a real on-call rotation. This is where you start operating infrastructure rather than consuming it. | see §05 |
Triggers are volumes, not dates. Nothing here needs doing before the volume that justifies it, except the first row, which needs doing regardless.
At today's volume, managed duplex is cheaper, lower-latency, and less work than anything we would host. Self-hosting PersonaPlex is worth doing when we need a voice and a persona that are ours, when audio cannot leave the project, or when a vendor's per-minute rate becomes a negotiating position we do not like. Those are good reasons. Saving money at 100,000 minutes a month is not one of them.
Every figure in this document derives from the constants below. All are US list prices in us-central1 as published in August 2026, before any negotiated discount. The model that produces the tables is in the repository at tools/scale-model.py, so a rate change is a one-line edit and a rerun.