The Demo Worked. The Launch Did Not.
You have built a voice AI agent. It collects intake information, qualifies leads, handles support calls — whatever the use case. You have wired it to a real +91 inbound number so callers can reach it from any Indian phone. You placed a test call. The agent answered, spoke, handled the conversation cleanly. The demo was a success.
Then real traffic arrived.
Not a flood — just a normal Tuesday. Fifteen, twenty callers in a window. Some of them hit the line at the same time. And things fell apart in ways you had not planned for.
One caller got dead air. Another stayed ringing until they gave up. A third somehow reached a half-started session that was already mid-conversation with someone else. Your media server was still processing a previous call when a new one arrived, and neither call got a clean experience. Your logs showed attempts. They did not show you which ones were served and which were silently dropped.
This is the part of voice infrastructure that nobody talks about in the tutorials.
Telecom Is Different From HTTP
When you build a web API, concurrency is handled for you. Your server spins up threads or async workers. Requests queue. You add more instances. The model is stateless, and horizontal scale is the answer to almost every load problem.
Voice calls are stateful. A call in progress holds a live media channel open for its entire duration. If a caller is talking for three minutes, that channel is occupied for three minutes — and anything that needs to serve the next call needs its own channel. You cannot reuse the channel from a call that has not ended. You cannot buffer audio from ten callers into one processing slot.
This means the number of callers you can serve simultaneously is a hard upper bound set by the capacity you have allocated. Unlike web requests, over-demand does not queue gracefully by default. It fails — usually badly: silent drops, partial audio, or sessions that start and then stall.
The harder truth for Indian telephony specifically: carrier traffic in India can be bursty in ways that are hard to predict from development-environment testing. A number shared in a broadcast message, a WhatsApp forward, or a campaign can receive dozens of simultaneous inbound calls in seconds. Your agent framework was probably tested one call at a time.
The Three Failure Modes You Need to Prevent
1. Unlimited concurrent sessions that exhaust capacity
If there is no cap on how many simultaneous calls your number will attempt to handle, a spike will try to spin up as many sessions as calls are arriving. Each session needs compute, a media channel, and potentially a model inference slot. These are finite. The system does not degrade gracefully — it falls over, and every caller in that window gets a bad experience.
The fix is a per-number or per-tenant concurrency limit enforced before you try to establish a session. Not after the call is picked up. Before. If you are already at your limit, the new call needs a decision: busy signal, queue position, or fallback — not a half-started session.
2. No visibility into what was rejected
If your concurrency guard silently drops calls, you have a problem you cannot measure. You will not know whether your limit is set correctly, or whether a campaign drove more simultaneous traffic than expected, or have evidence for a capacity conversation.
Every rejected call — whether rejected because of a concurrent session limit, because a monthly usage quota is exhausted, or because the agent endpoint was unreachable — should produce a named, queryable event in your usage ledger, not just a line in server logs. You want to see rejected — at concurrency limit alongside connected and understand the real demand shape.
3. Runaway sessions when your agent goes silent
Your agent endpoint will occasionally be unreachable — a deployment in progress, a model provider timing out, a webhook taking longer than expected. When that happens, the call should not sit in dead air waiting for a response that is not coming.
A well-designed voice platform enforces a decision timeout on any route. If your control endpoint does not return within a bounded window, the platform executes the configured fallback — forward to a mobile number, play a message, or hang up cleanly — instead of leaving the caller on an open channel indefinitely. An open, unserviced channel is a cost event and a bad caller experience. The fallback path is not an edge case; it is a first-class part of the design.
How Concurrency Control Actually Works in Practice
When we built this layer into a real production voice platform, the decisions that mattered most were not the obvious ones.
What "one concurrent call" means. A call handled by a bridged AI agent involves more than one raw channel in the media server: the inbound caller leg and the leg connected to the AI endpoint. Metering raw channels would double-count. The customer-facing limit should be expressed in terms of concurrent call sessions — one active customer conversation — not raw media channels. That distinction matters when you explain a limit or a rejection to a customer.
Where the limit is enforced. An in-process counter in a single server instance breaks down if the server restarts, if you run multiple instances, or if a race lets two sessions start at the same moment. The right primitive is an atomic, database-backed lease: before any expensive work happens (routing, session establishment, model connections), the platform atomically claims a slot. If the slot cannot be claimed because the limit is reached, the call gets a busy response immediately and the rejection is metered. The lease is released when the call ends, and it carries a time-to-live so abandoned sessions from crashed processes do not lock slots indefinitely.
One control plane. A platform-level guard is necessary but not sufficient if the underlying media layer can receive calls that bypass the platform. Every customer-facing number should enter the platform before any bridging begins — a number that bypasses the control plane also bypasses the metering, the limits, and the fallback. Treat the application-layer guard as the source of truth and the media-layer guard as a safety net, not the reverse.
Monthly Quotas Are Not the Same as Concurrency Limits
These two controls protect different things.
A concurrency limit answers: how many callers can be in an active session right now? This is about peak capacity and preventing simultaneous overload.
A monthly usage quota answers: how many total minutes has this tenant consumed this billing period? This is about cost control and fair use.
A well-designed platform checks both before accepting a call. A call that arrives when you have no concurrent sessions in use, but you have exhausted your monthly minute allowance, should still be rejected — and logged as rejected — quota exhausted, not rejected — at concurrency limit. The distinction matters for support, usage reports, and billing evidence.
The Fallback Is Not Optional
One of the clearest lessons from running real voice traffic: the reliability of your fallback path is as important as the reliability of your primary agent path.
Developers focus on the happy path — caller arrives, agent answers, conversation happens, session ends. The fallback number or clean hangup feels like a minor config detail. In production it is load-bearing: it handles every call during a deployment, every model-provider timeout, every call that exceeds your concurrency limit, and every session that exceeds a maximum duration. If the fallback is missing, those callers get dead air. If it is a mobile number with voicemail, at least they leave a message.
Concurrency-aware systems should also enforce a maximum session duration as a backstop. A call that connects and then gets stuck — agent in a loop, caller walked away, a model call that never returned — should be terminated after a configured maximum. The cost of an hour-long open channel is real, and the caller is not being served anyway.
What This Looks Like in Production
The system we built tracks usage across the full call lifecycle with named events — a call attempted event, a rejected — at concurrency limit event, a rejected — quota exhausted event, a connected event, and a billable minute event on completion. Those events, not just raw call logs, are what let you see actual demand versus served demand, understand whether your limits are correctly sized, and produce evidence for capacity decisions. The authoritative billing source is still the call detail record from the media layer, but the event ledger is what makes real-time metering and per-call-path diagnostics possible.
The Platform Abstraction You Actually Need
What developers building voice AI agents for the Indian market actually need is not raw telephony access. It is a control layer that handles the hard parts so the agent framework can focus on conversation: a dedicated Indian business number, inbound routing controlled by API, a concurrency and quota guard that operates before any session work, named usage events for every outcome, a configurable fallback for every failure mode, and a session duration cap as a backstop.
The number is the easy part. The infrastructure around it — the metering, the limits, the fallback, the usage ledger — is what makes it viable for real traffic.
Boni Voice is the platform we built around this model — an Indian business number with programmable inbound routing, concurrency controls, usage metering, and fallback configuration, currently in assisted beta.
This reflects our own engineering decisions building a multi-tenant voice platform for the Indian market. Specific limit values and plan terms change as the product matures.
Top comments (0)