All posts

AgentStream Integration Paths | Connect Voice AI, Flow, ExoML

Saurabh Sharma
Saurabh Sharma
·

 

You have already chosen a Voice AI stack — OpenAI Realtime, ElevenLabs, Gemini Live, Sarvam, Pipecat, LiveKit, Retell, Bolna, or a custom STT to LLM to TTS pipeline. We are not trying to replace it. Our job is to put that bot on a phone call in India.

Most of the confusion in doing that comes from one thing: mixing up media (how audio reaches the bot) with telephony control (who decides when streaming starts, greetings, IVR, handoff). Almost every “why isn’t this working” thread traces back to expecting a feature from the wrong entry.

Reference: AgentStream: what to use when.


One media contract, one entry

Layer What it is
Your Voice AI Unchanged. Your vendor, your prompts, your tools
AgentStream media Bidirectional or listen-only audio between the live call and your bot — usually WebSocket, sometimes SIP into your AI platform
Telephony entry (pick exactly one) Connect Voice AI, or Flow (App Bazaar), or ExoML
StreamKit / SIP connectors Separate entries for an existing PBX or a SIP-native AI platform

The media contract is the same on every entry: we open a stream to your endpoint and speak a documented event flow (CONNECTED, START, MEDIA, marks, DTMF, STOP, plus reverse audio and interrupt semantics for duplex bots).

What changes between entries is who decides when the stream starts, whether you get dashboard applets, and whether your backend owns every mid-call action.

  • Connect Voice AI — outbound shortcut: answer goes to the bot (StreamUrl in the API). No App Bazaar, no ExoML.
  • Flow — dashboard applets (Voicebot, Stream, Passthru, Connect, Gather, Greeting).
  • ExoML — your code reacts to leg events and issues leg actions (start_stream, say/play, and others).

Do not stack them. Connect Voice AI, Flow applets and ExoML are not stages you graduate through. Add depth inside the one you chose.


What to use when

You need Use
Outbound, answer to bot, fixed stream URL Connect Voice AI API
Outbound with greeting, IVR, DTMF, Passthru or bot-to-human Connect Voice AI with Flow API
Logic in code, per-call stream URL, parallel greeting ExoML / Programmable Voice
Inbound with dashboard applets Exophone to App Bazaar flow with Voicebot
Inbound driven by your app Exophone to your event endpoint (ExoML)
Who starts the call?
│
├─ Customer dials (inbound)
│  ├─ Dashboard applets?  → Exophone → App Bazaar → Voicebot
│  └─ Logic in your app?  → Exophone → events → leg actions (ExoML)
│
└─ You dial (outbound)
   ├─ Bot only, fixed StreamUrl?   → Connect Voice AI API
   ├─ Need a flow or handoff?      → Connect Voice AI with Flow API
   └─ Need code-owned leg actions? → ExoML

Inbound is two separate paths. Flow inbound and ExoML inbound are not variations of each other. Do not attach one number to both models.


The question that actually decides it: when can the bot URL change?

Most comparison tables miss this, and it is the distinction that matters.

All three entries open a stream to a URL. What differs is the latest moment that URL can be decided — and that cascades into your deployment model, your tenancy model and your release process:

Entry Where the URL lives Latest moment it can change What that means
Connect Voice AI The dial API request body Per dial request, fixed for that call Fine for one bot fleet behind one hostname. Per-tenant routing happens inside your service, after the socket opens
Flow The Voicebot or Stream applet config Configuration time, without a code deploy Ops can repoint the endpoint during an incident while your dial code stays untouched
ExoML The start-stream action your code issues Per call, with full context in hand You already know the tenant, campaign and caller before choosing the endpoint

Read it as a decision:

  • If routing is static, Connect Voice AI is not a limitation. It is one less moving part.
  • If routing changes for operational reasons and you want that lever outside your release cycle, Flow is the answer.
  • If routing is a function of call context — multi-tenant SaaS, A/B across model versions, cohort rollouts — only ExoML lets you compute it at the moment you have the context.

Teams that pick Connect Voice AI and later need per-tenant routing usually end up building a dispatcher behind one hostname. That works fine. Choose it deliberately rather than discovering it in month three.

Custom parameters are the companion primitive: tenant, campaign and session identifiers ride along with the stream, so your bot knows who it is serving without a lookup on the critical path.


What each path includes

Path 1 — Connect Voice AI (outbound, bot only)

You run reminders, surveys or collections. Every answered call is the bot. You do not need Exotel-side IVR or agent transfer.

Your API dials the customer. On answer we open a bidirectional WebSocket to the StreamUrl from the API body.

You get: the fastest path from answer to bot; a clean design when the bot is the whole experience.

You do not get: App Bazaar applets (Greeting, Gather, Passthru, Connect handoff); an Exotel-side disclosure before the bot; leg actions or leg events.

Your API → dial → answer → WebSocket ↔ bot

If requirements change, redesign on Connect plus Flow, or ExoML. You cannot enable Passthru on Connect Voice AI.

Path 2 — Connect Voice AI with Flow (outbound plus applets)

You still dial from your API, but after answer you need a journey: disclosure, DTMF consent, bot conversation, then human handoff or a webhook into CRM.

The call’s URL points at an App Bazaar flow. Applets run after answer. When the flow reaches Voicebot or Stream, we open the WebSocket. The stream URL lives in the applet, not the API body.

Applet Job
Greeting / Play Disclosure or welcome before the bot
Gather DTMF before the bot
Voicebot Bidirectional stream
Stream Listen-only assist or transcription
Passthru Pre/post webhook; a synchronous response can steer the next applet
Connect Bot to human, SIP or group handoff

Common compositions: Greeting to Voicebot · Gather to Voicebot · Voicebot to Connect · Voicebot to Passthru.

The Connect applet is a routing engine rather than a dial verb: number, agent group, SIP URI or trunk; dynamic primary and fallback URLs at call time; a transition graph for ended, no-answer and never-dialled; custom SIP headers for screen-pop; Sticky Agent for returning callers; and a callback queue when agents are busy.

Path 3 — ExoML legs (outbound and inbound, code-first)

You are building a Voice AI platform or a complex backend. Stream URL, filler audio and branching belong in your service, not a dashboard graph.

Outbound: create a customer leg from your API, receive leg events, issue actions. Inbound: attach the Exophone to your event endpoint; your application is the flow.

You get: leg lifecycle events (connecting, ringing, answered, stream started, and others); actions including start stream, say/play, stop say/play, hangup and bridge; a per-call stream URL; and the parallel greeting pattern.

You do not get: Passthru or Connect applets. Build the equivalent CRM notification and dial behaviour with your own HTTP plus leg actions.

The trade: the most control, and you own the call state machine — testing it, and getting paged for it. Right for a platform team, overkill for a single support line.


Why a document returned at call start runs out of room

Classic programmable voice returns a document when the call starts and executes it top to bottom. That suits IVR and fits agents poorly, for one structural reason:

A document evaluated at call start cannot react to an event that has not happened yet.

Everything interesting about a voice agent happens after that evaluation — starting or stopping a stream, playing a filler while the model warms, swapping models based on what the caller just said, adding a silent supervisor leg, handing off with context, dropping one participant without ending the call.

Each depends on live state. The document model can only approximate it by fetching a new document: a round trip, a gap in the audio, and a state machine on both sides.

The real question is who holds the call state machine. Flow holds it for you. ExoML hands it to your service. Connect Voice AI removes the need for one. All three are reasonable. Owning two at once is not.


Side by side

Connect Voice AI Connect + Flow ExoML Inbound Flow Inbound ExoML
Direction Out Out Out + In In In
App Bazaar No Yes No Yes No
Stream URL set in API body Applet Start-stream action Applet Start-stream action
URL changes without a deploy No Yes Yes (computed) Yes Yes (computed)
Filler while the bot warms Bot-owned Play applet Say/play, stopped on stream start Bot-owned Same pattern
Passthru / Connect applets No Yes No Yes No
Who owns call state Nobody needs to The dashboard Your service The dashboard Your service
Best for Simple outbound bot Multi-step outbound Code-first control Dashboard inbound Code-first inbound

WebSocket or SIP: what changes for you

Both get audio to the same agent. They put the responsibility boundary in different places.

Concern WebSocket path SIP path
Who sits in the media path Your process. Every frame passes through code you deploy Your AI platform. It terminates media; your app sits above it
Media timing and jitter Yours. Your event loop, GC pauses and scheduling become audio quality Handled by the platform’s media stack
Format negotiation Configured; we convert between the phone side and your requested rate Negotiated at session setup by SIP signalling
What a deploy does Restarts processes carrying live audio Usually leaves media untouched
Where you debug quality Your logs, close codes, process metrics Your platform’s session records and SIP signalling
Best fit Custom pipelines, frame-level control SIP-native platforms you did not build

Choose WebSocket when the agent runtime is yours and you want frame-level control. Choose SIP when a platform already owns media and you would only be reimplementing its jitter handling less well.

If your provider wants a SIP trunk (LiveKit, ElevenLabs Agents, Pipecat via Daily, Bolna, Retell and similar), use Exotel SIP trunking with the matching connector guide in the Voice AI Ecosystem docs. That is not the same as configuring a Voicebot applet URL.

If a PBX already runs the floor — Avaya, Genesys, Ameyo or homegrown — do not force Connect Voice AI into that story. Use StreamKit Cloud: managed SIP to WebSocket and SIP to SIP, modes for full voicebot, agent-assist, monitored bot and post-call notify, multi-bot routing on one trunk, transfer back to a live agent with context headers, encrypted transport, India-hosted media options and a sandbox. The PBX stays as it is and Voice AI becomes another SIP endpoint.


Bringing your own AI stack

Clone github.com/exotel/Agent-Stream, set keys, and place a Connect Voice AI test call for the fastest outbound proof.

Maintained bridges cover major realtime and pipeline stacks (OpenAI Realtime, ElevenLabs, Gemini Live, Sarvam, Pipecat, Dograh, plus mix-and-match STT/LLM/TTS recipes). Pipecat has an Exotel serializer, so you do not write your own framing.


Evaluating this without mis-buying

If you score AgentStream as one long feature list, a third of your rows will be mutually exclusive. Score in two passes:

Pass 1 — media. Weight this heavily; it applies to every call you will make. Latency figure and whether the vendor states its measurement boundary. Sample rate handling. Interrupt and turn-end signalling. Failure observability. Concurrency visibility. Residency.

Pass 2 — exactly one entry, the one matching how your team ships. Where the bot URL is decided. Who owns the call state machine. What handoff looks like. What ops can change without a deploy.

Then stop. A row that only exists on an entry you did not choose is not a gap — it is a different configuration, and counting it against a vendor produces a score that predicts nothing about your build.


Four architectures

Outbound collections, simple. Connect Voice AI. Every answered call is the bot. Your bot owns first audio, so cold starts are audible.

Support line with disclosure and human escape. Flow — Greeting, Voicebot, Connect. Disclosure and handoff without owning call state in code.

Multi-tenant Voice AI SaaS. ExoML — per-call stream URL, with optional parallel say/play while the tenant bot connects. Routing lives where tenancy already lives.

Bank PBX stays, bot is new. StreamKit. SIP from the PBX, no replacement project.


Common mistakes

Mistake Fix
“We’ll start with Connect Voice AI and add Passthru later” Passthru is a Flow applet. Move the design to Connect plus Flow
Mixing App Bazaar and ExoML on one inbound number Choose Flow or ExoML inbound
Expecting a Voicebot applet to work for a SIP-native platform Use the SIP trunk connectors
Treating StreamKit as an extra applet It is a separate entry for a call that already lives on a PBX
Designing a hybrid across two entries Add depth inside one

In short

Keep your Voice AI provider. The media link is the same on every path. Choose exactly one telephony entry — and let the bot-URL question decide which — then add complexity inside it.

Next: running this in production in India.


Related reading