ToolReply speaks for a tool result. A FinishRule with confirm lines asks before a tool runs.
Quickstart
agent.py
book_slot with {"slot": "2026-10-16"}. The tool does not run. The caller hears “Shall I book Friday, October 16?” On a yes, the model calls it again, the tool runs, and the caller hears “Booked for Friday, October 16.”
Why use unmute for this
A booking tool should run on what the caller heard, not on what the model guessed. Code asks the question, checks that the caller heard it, and runs the tool only on the same input. The line is word for word, the same on both frameworks.Say a fixed reply to a tool result
AToolReply is a line code speaks when one of the step’s tools returns a matching result.
list_times returns {"found": true, "times": "10:00, 11:30 or 14:00"}, the
caller hears “I have 10:00, 11:30 or 14:00. Which suits you?” The model is asked
for no reply. Use a ToolReply for a line that must come out word for word, like
the times on offer.
- The model calls one of the step’s tools. The tool returns a result.
- Each
ToolReplyof that tool is checked in order. The first whosewhenandstatematch is used. - The line is spoken. The model reads the result, but is asked for no reply.
- When none matches, the model replies as usual.
- Both frameworks speak the line, so the call sounds the same.
- A line fills from the tool’s result and the State.
{{result.times}}reads a field of the result.{{slot}}reads a State value. - A result value is said by its output field’s JSON Schema
format, in the call’s language, as a confirm line says an input. Adatefield reads “tomorrow, October 16”. whenmatches result fields.statematches State values, likestate={"vip": [True]}. A reply with neither is spoken for every result of the tool.- The plan checks each
{{result.<field>}}andwhenfield against the tool’s output model. A field the tool does not return fails when the plan is built. - When a line cannot fill, because a field is missing or empty, it is skipped. The next line is tried.
- When no line fills, the model replies as usual.
- With several lines, the next call says the next one, so the agent does not repeat itself.
- A question the caller must answer before a tool runs belongs in
FinishRule.confirm, not here.
ToolReply rows on its ToolRegistry. See
Save a result and speak it.
Why it works this way. A model may reword a list of times. A line from code comes out word for word, and the model is not asked for a reply.
Best practice.
- Use a
ToolReplyfor a line that must come out word for word. - Give several lines when the same reply may repeat, so the agent does not repeat itself.
Ask before a closing tool runs
AFinishRule ends a step by itself when its tool returns a result the rule calls a success. Its confirm lines ask the caller first.
- The model calls the closing tool. Code does not run it.
- Code fills a
confirmline from that call’s input and says it. No model reply is asked for. - The model reads that the tool has not run, and waits for the caller.
- On a yes, the model calls the tool again with the same input. The tool runs.
- If the caller changes something, the model calls it with the new input. Code asks again, with the new values.
- A successful result ends the step. The
saylines are the receipt.
confirmtakes up to five lines. They may use{{input.<field>}},{{variable}}and a field of a shape in the State, like{{account.amount_words}}. A path that does not exist fails when the agent is built. A line that cannot fill is skipped. When none can, the tool is refused and the model is told what to get from the caller.confirmbelongs to the tool. Every rule of that tool must give the same lines.- The tool runs only when the caller heard the line. A line cut off after it said every value it holds counts as heard, where the adapter knows the words that played. LiveKit knows them. A line cut off sooner is asked again.
- The input must be the same as the one asked about, and so must the tool’s locked values. Any other input, or a locked value that changed since, like a report the caller corrected, is asked again.
- An input is said by its JSON Schema
format, in the call’s language. Adateis named from today: “today, October 8”, “tomorrow, October 9”, “Friday, October 16”. So put a date after “for” or “to”, never after “on”. Anenumvalue is said as words:hair-coloris “hair color”. - A step may open on a closing tool that has
confirmlines. See Open a step with a tool. The opening asks the line about State values. It is skipped when every value is one a step confirms and is already confirmed. A tool with noargumentsalways asks.
- Put a date after “for” or “to” in a line, never after “on”.
- Give every rule of one tool the same
confirmlines.
Open a step with a tool
AnOpeningTool is a tool a step runs in code as it opens, before any model request. Its arguments map a tool parameter to the State value it takes. The tool runs only when every value is set. A parameter the tool’s schema declares and does not require is left out of the call when its value is not set.
say line fills, the step speaks it and waits for the caller. No model
request is made. Use a tool that only reads, because the model may call it again.
A tool the step ends on may open it too, when it has confirm lines. Then code
asks the confirm line about the State values and waits for the caller’s yes. Give no say for a tool with confirm lines. The first say line that fills is spoken.
What a step can offer
A step that reads a prefetch lookup
ATaskInput is something a step reads at the end of its prompt. Its kind is memory (a State value), input (a fixed text, in value, up to 4000 characters) or tool (what a prefetch lookup returned, recorded by name).
- A
toolinput must name a prefetchLookup. The adapter checks this when the agent is built with the call’sCallHooks. A name noLookupruns raisesValueError, and so does anytoolinput when the hooks have no prefetch. An agent built without hooks is not checked. A tool lookup may have an emptyassign.CallHooksrecords its result as JSON incall.results, by entry name. - Give the adapter
call=call. It passescall.resultsto the steps, so a step reads the same text at the end of its prompt. In your own host, passcall.resultstoCallTasks(call_start=...). - Until the tool has finished, the step reads that the input is not available yet.
- See Prefetch and The call’s life.
A step that hands off
TaskSpec.handoffs names handoff tools of the agent that the step offers too.
The step’s opening reply leaves them out. They come back once the caller speaks
in the step. Calling one ends the step and its task or group, and gives the call
to the other agent. See Handoffs.
A step that offers the agent’s native tools
- On LiveKit, a toolset is named by its id. The tools come from
UnmuteAgent(native_tools=...). - On Pipecat, the names are
FunctionSchemas inUnmuteFlows(native_tools=...). - An unknown name raises when the plan is built.
- A name may not also be a tool or a handoff of the same task.
- A step gets only the native tools it lists.
A task on its own model
TaskSpec.model names the model a task’s steps run on. The agent’s spec maps the
name to a model.
- On LiveKit,
AgentSpec(task_models=...)maps each name to aModelCall. A LiveKit LLM plugin goes in asLiveKitModel(plugin). A step of that task sends its requests there. The main agent keeps its own model. - A name
task_modelsdoes not hold raisesValueErrorwhen the spec is built. - On Pipecat,
UnmuteFlowsrefuses a task with a model when it is built. Every step runs on the agent’s LLM service there, as in Unmute.
Troubleshooting
The model books before the caller agreed
The closing tool has noconfirm line, so code does not ask first.
Fix: add confirm lines to the FinishRule of the tool.
A confirm line is skipped and the tool is refused
Noconfirm line could fill from the call’s input.
Fix: write the lines with {{input.<field>}} for fields the tool takes, and check the names.
A tool input raises ValueError when the agent is built
A tool input names a lookup that no prefetch Lookup runs, or the hooks have no prefetch.
Fix: add the Lookup to Prefetch, and build the agent with the call’s CallHooks.
In the salon tutorial: Book step by step.
Every argument: Tasks reference.
Where to go next
What the model reads
The small results a task sends to the model.
Tools
The tools a step lists in
tools.Handoffs
Give the call to another agent.