AgentSpec, with its own prompt, tools, tasks, model and voice, and all of them share one CallState.
In the salon tutorial, Hand off complaints builds this step by step.
Hand the call to another agent
Give the model a handoff tool. Its handler callshand_off.
- The
announceline plays first, and the handoff waits for it. The caller cannot cut it off. - On LiveKit, build the next agent on
self.stateand withouttelemetry. On Pipecat, pass its spec, andUnmuteFlowsswaps the prompt, tools, tasks and model. - The next agent replies first. Name each agent’s handoff tools in its spec’s
handoffs, so that first reply cannot hand the call straight back.
What the next agent hears
Pass aHandoffContext as context. With none, the next agent hears what was said, without tool calls.
- Calls to tools the next agent does not have are dropped, with their results.
- The
announceline is the last thing the next agent hears, except withsummaryandreset. - On a realtime model, only
fullworks. See Realtime models.
Summarize the call for the next agent
- The summary is asked for while the
announceline plays, so it does not hold up the handoff. summarizeris anyModelCall. Without one, the spec’ssummarizeranswers, else the model of the agent that hands off.- A summary that fails, comes back empty, or takes longer than 8 seconds is dropped. The next agent then hears
messages.
Give the next agent its own model and voice
Give the spec amodel, and pass the agent its own voice.
FRONT_VOICE and BILLING_VOICE are two Fish Audio voice ids on SLNG. SLNG_KEY, SLNG_HOST and WORLD_PART come from your environment, as in the quickstart.
- The
announceline plays in the old voice. Then the new model and voice take over. - An agent whose spec has no
modelruns on the call’s first model. The same holds for the voice. - On Pipecat, a spec with a
modelneeds aModelCallLLMService, and the voices go through aVoiceSwitcher.
Open the next agent on a tool
The next agent’s first reply comes from the model. When it must start with something code already knows, run a tool in code instead. Passopens_with:
- The tool runs through the next agent’s
ToolRegistry, so itsassignrows save values. - Its line is the first of the opening’s
saylines that fills, else the registry’sToolReplyfor the result. - When a line fills, it is the agent’s first reply. The tool call and its result join the history, and no model request is made.
- When no line fills, or the tool is refused or fails, the model replies first as before.
argumentsmaps tool parameters to State values, as on a task step’s opening. The tool does not run while one of them is unset.- A realtime model refuses
opens_with: it keeps the history on its server.
A handoff from a task step
A task can offer the agent’s handoff tools to its steps:- The handoff tool must be one of the agent’s own tools: a
native_toolsentry, or a LiveKit@function_toolmethod. - When the step’s model calls it, the step ends with
task.handed_off, and nothing is saved. - The next agent hears the step’s own chat, shaped by the handoff’s
context.
Hand off from a plain agent
You can hand off withoutUnmuteAgent or tasks.
- On LiveKit,
shape_chat_ctxgives the next agent the history the library keeps. It does not say a line or write a summary. - On Pipecat, a spec with only a prompt is enough for a full handoff.
Where to go next
Handoffs reference
Every argument, history rule, helper and error.
Tasks
Steps and groups that offer or run a handoff.
LiveKit
What
UnmuteAgent does on a LiveKit session.Pipecat
What
UnmuteFlows does in a Pipecat pipeline.Unmute handoffs
How
agent.yaml declares a handoff.