We've written before about why Lexycall calls don't have dead air — the short, natural audio cues that play while the agent is mid-task. This post is the layer underneath that: the actual mechanism that makes those cues possible, and why "just play a sound while you wait" is harder than it sounds once tool calls are involved.
An AI voice agent isn't just a language model talking — it's a language model that stops mid-conversation to do things: check calendar availability, look up an order, create a booking. Each of those is a real network call to a real system, with real latency. A calendar API might respond in 200ms on a good day and well over a second on a bad one.
If the agent waits for that response before saying anything, the caller hears nothing for however long the tool takes. That's the dead-air problem, and it gets worse — not better — the more capable the agent's integrations are, because more integrations mean more tool calls sitting in the middle of more conversations.
The pattern we build on comes from how LiveKit's agent framework models function calling: a tool call isn't a single blocking step, it's a call with distinct states — invoked, running, and completed — and the agent can react to each state separately instead of only to the final result.
That separation is what makes an in-progress message possible. The moment a tool call starts running, the agent already knows it has something to fill the gap with: a short spoken acknowledgment ("let me check that for you") that plays while the request is actually in flight, not a canned line inserted after the fact. The tool call and the spoken filler run concurrently — the agent isn't waiting to speak until the tool finishes, and it isn't waiting to call the tool until it's done speaking.
Lexycall's integrations aren't only built as bespoke API calls per system — some run as MCP (Model Context Protocol) tools, which gives us a consistent way to expose an integration's actions to the agent. The part that mattered for latency was making sure our MCP tool calls follow the same async, stateful pattern as our native ones: a tool call reports that it has started before it reports that it has finished, so the in-progress cue fires off the start event rather than waiting on the response.
Without that, an MCP-backed action would behave differently from a native one — silent while running, sound only at the end — which is exactly the inconsistency that breaks the illusion of a natural conversation. Treating every tool call the same way, regardless of what's implementing it underneath, is what keeps the caller experience consistent as we add more integrations.
Not every tool call is a lookup the agent decides to make on its own — some, like collect_dtmf, wait on the caller instead, capturing a keypad-entered order or tracking number before the agent continues. Same invoked/running/completed pattern, just triggered by the caller's input rather than a background API call.
A single tool call is easy to paper over. The real test is a call that needs two or three of them — checking availability, then booking, then confirming details back into a CRM. Each one is a small wait; stacked together, they're the difference between a call that flows and one that feels like it's stalling every few seconds. Building tool calls as async, stateful operations from the start is what lets that stack of steps still sound like one continuous conversation.
This is also why we treat custom voice AI integrations as more than "connect an API" — the connection working correctly and the connection sounding right on a live call are two different engineering problems, and both have to be solved for the integration to actually be usable on the phone.
Want to hear it on a call with a real integration behind it? Book a free demo.
Join hundreds of teams running their phones on AI — without sounding like one. See a live agent on your own call flow in 30 minutes.