Add voice to an external agent
An agent that runs on your own infrastructure can answer browser voice calls
through Runtype. Register it as an external agent, turn on voice, and start
calls with the Runtype browser voice client.
Runtype handles the voice side of the call: microphone audio, speech-to-text, turn-taking and interruptions, text-to-speech, and the saved conversation. Your agent only receives text and returns text. It never handles audio.
How a voice turn reaches your agent
When the caller finishes speaking, Runtype transcribes the speech and checks that the caller owns the conversation. It then calls your agent with the new transcript and appends both the user message and your agent’s reply to the saved conversation.
What your agent receives depends on its protocol. The following table
compares the two protocols:
An A2A agent receives no earlier turns, so it must keep its own history under
the contextId. Runtype reads capabilities.streaming from the agent card
that it cached when you registered the agent. After you turn on streaming in
your agent card, send POST /v1/agents/{id}/refresh-agent-card to update the
cached copy.
For a runtype-stream agent, Runtype speaks only text from your agent’s own
execution. Text inside a tool call isn’t spoken. If your execution_complete
frame sets finalOutput, Runtype uses it as the reply text in the transcript
and the saved conversation. If your agent streams no text, Runtype speaks
finalOutput.
Before you begin
Prepare the following:
- An agent endpoint that Runtype can call over public HTTPS, using the
runtype-streamprotocol or A2A. For the endpoint rules and theexternalConfigfields, see Bring your own agent. - A Runtype API key that can create and update agents.
- A client token that can access the agent, for the browser. For more information, see Client tokens and domain restrictions.
- If your endpoint requires authentication, a managed secret that holds its credential. For more information, see Managing secrets.
Register the agent with voice turned on
Send POST /v1/agents with agentType: "external", your externalConfig, and
a config.voice block. The following request registers a runtype-stream
agent that uses Cloudflare speech:
Replace the following:
YOUR_API_KEY: your Runtype API key.https://agents.example.com/dispatch: your agent’s endpoint.SUPPORT_AGENT_TOKEN: the name of the managed secret that holds your agent’s bearer token.
The MCP create_agent tool takes the same values as agent_type,
external_config, and config.voice. To turn on voice for an agent that you
already registered, send PUT /v1/agents/{id} or use the MCP update_agent
tool with the same config.voice block. An update replaces the whole voice
block, so send every field that you want to keep.
The voice block takes the following settings:
enabled: Set totrueto accept voice calls. A call to an agent without it returns HTTP403withAgent does not support voice.interruptionMode:none(the caller can’t interrupt a reply),cancel(the caller stops a reply from the client), orbarge-in(the caller interrupts by speaking). It defaults tobarge-infor Cloudflare speech andnonefor ElevenLabs speech.speech: Setmodetopipelineand use the same provider,cloudflareorelevenlabs, fortranscriberandsynthesizer. Without aspeechblock, the agent uses ElevenLabs speech.
Runtype resolves each {{secret:NAME}} reference on the server, in the scope
of the account that owns the agent, at the time of each call. The browser and
the client token never carry the credential. If a resolved credential appears
in your agent’s reply or error, Runtype removes it before it speaks, saves, or
logs the text.
Start a voice call
Start calls with the @runtypelabs/voice client. For the install command, the
React hook, and the call controls, see
Build a custom voice interface.
An external agent answers only on a call that saves its conversation:
- Cloudflare speech: Every call saves its conversation. You don’t need extra options.
- ElevenLabs speech: A call saves its conversation only when the client
passes a
visitorTokenor asessionId. Without one, your agent isn’t called, and the caller hearsVoice mode does not yet support external agents.
The following hook passes a visitor token for an ElevenLabs call:
The visitor token is the visitor.token value that
POST /v1/client/init returns. For how to request and store it, see
Conversation history in an embedded widget.
Keep replies responsive
The caller hears nothing until your agent returns text. We recommend that you
use a runtype-stream endpoint, or an A2A agent whose card advertises
streaming, so speech starts with the first sentence. An A2A agent without
streaming makes the caller wait for the whole reply.
Write replies to be spoken. Keep them short, and leave out Markdown, tables, and long lists.
Interruptions, timeouts, and failures
When the caller interrupts a reply, cancels it, or hangs up, Runtype cancels the request to your agent. It discards any output that arrives later, so that output never reaches the next turn. The saved conversation keeps the part of the reply that streamed before the interruption. When the voice client reports playback progress, Runtype trims the saved reply to the part the caller heard. Runtype records the run as cancelled.
timeoutMs (default 30000) and retryCount (default 3) apply as they do for
other calls to your agent. The timeout covers the whole reply, so raise it for
turns that take longer than 30 seconds. Runtype retries a request only when it
never reached your agent, and it never replays text the caller already heard.
If your agent fails or doesn’t finish within timeoutMs, the voice client
receives the error Voice processing error and the call ends. Runtype records
the run as failed.
Usage and runs
Each turn counts toward your voice execution limits, like any other voice turn. Speech-to-text and text-to-speech are metered as for any other voice call. The run shows no model cost for the work that your agent does.
Each turn appears as a run with the surface type voice. For more
information, see Working with runs.
Configurations that voice refuses
Runtype checks the agent and the conversation before the turn counts toward
your voice execution limits. If voice can’t honor a setting, the caller hears
Sorry, I encountered an error: followed by the reason. The following table
lists each refused configuration and the reason that the caller hears:
For the entry points that can send an identity assertion, see Send the assertion to an external agent.
The following calls also don’t run an external agent:
- A call that sends an identity proof: The connection fails with HTTP
422andThis agent type does not support voice identity proofs. - GPT-Live:
speech.mode: "speech_to_speech"doesn’t run external agents. Use thepipelinemode. - A call that saves no conversation: An ElevenLabs call without a
visitorTokenorsessionIdanswers withVoice mode does not yet support external agents., as described in Start a voice call.
Next steps
- Bring your own agent: register an external agent and implement a
runtype-streamendpoint. - Build a custom voice interface: add call controls with the React hook.
- Conversation history in an embedded widget: share one conversation between text and voice.
- Working with runs: inspect each voice turn.