handoff moves the conversation to another agent from inside an operation tree.
Use it when the tree decides where the call goes — after an http lookup,
inside an if — rather than when the model does.
transfer.
This operation, or the agent’s handoffs field
Both exist, and they answer different questions.
If the destination is a desk the caller can ask for, put it on the agent’s
handoffs field. That is one line per edge and the model
routes it. Reach for this operation for the procedural case: look the account
up, and if it is enterprise, hand to the enterprise desk.
Naming the target
Exactly one of these, never both and never neither.
Getting it wrong is refused at save:
agent_name cannot be checked that early — the roster is decided by the call
this tool ends up running on, not by the tool — so it comes back as a warning
instead:
context
context decides what the next agent starts with. It takes the same three words
as an agent’s own conversation.context, because it is the same question asked
about the next agent rather than about the next call.
The full policy model — what a summary is and is not, and why “summary is not
privacy” — is in context policies. What follows is
only what is different about writing it here.
summary is a value, not a request
Under context: "summary" a summary is required, and it is the text
itself. There is no second model call: whatever this field resolves to is what
crosses.
That is because this tree runs after the model’s tool call. There is no
argument being written at that moment to take a summary from, so you point the
field at where the text comes from:
{{args.summary}}, declare it on the tool’s json_schema and describe it
properly — it is the model’s job to write it, and the description is the only
instruction it gets:
summary under any other context is an error, not a field that quietly does
nothing:
recent_turns
recent_turns (1–10) is the tail of the conversation that crosses verbatim —
alongside a summary, or in place of one under none. It works exactly as it
does on handoffs[]. Left unset it means “the policy’s own answer”: two turns
under summary, none under none.
Under transcript it is refused rather than ignored, because every turn is
already crossing:
context: "transcript".
message
message is spoken while the target loads. It is queued before the target is
built — loading a target compiles its prompt, its tools and its voice — so the
caller hears “connecting you to billing” during that wait rather than after it.
There is no flag to make it finish first, and it does not need one: queued
speech is drained before the target’s own greeting runs, so the line always
plays to the end.
The target then speaks its own greeting, or opens with a generated line if it
has none. If it has an on_enter hook, that runs first.
Terminal, and capped
handoff is terminal in its chain: nothing may follow it in the top-level list
or inside a branch, and branches do not rejoin. Anything that has to happen on
the way out goes above it.
A single call is capped at 25 handoffs in total — a backstop against a routing
loop between two agents, not a budget you should ever approach. Past it, the
agent tells the caller it cannot pass them on again.
A worked example
Look the account up, and route on what came back. Theif gives you two chains;
each one ends in its own handoff.
In a test run a
handoff is simulated: the
target agent is never loaded or compiled — a test must not build a second agent
as a side effect — and the step reports the resolved target, message, context and
summary. The run reports handed_off: true and stops there, which is what the
operation does to the chain on a real call too.Next
Handoffs
The
handoffs field, where the model does the routing.Context policies
What
transcript, summary and none actually carry.Conditional handoffs
Routing decided by a tool tree, from the team’s point of view.
Transfer
Reaching a person rather than another agent.