Handoffs or sub-agents?
Quick start
Give each target aname and a description (the model reads the
description to decide when to hand off), then pass them to the triage agent as
handoffs:
transfer_to_<name>
(transfer_to_billing, transfer_to_tech-support). When the model calls it,
the run goes on as the target in the same send() / stream() call.
result.agentName names the agent that produced the final reply.
A target must come from createAgent() and have a name (unique among the
handoffs) and a description (or a description on handoff()); otherwise
createAgent() throws LOUSHO_CONFIG_INVALID. A handoff tool may not share a
name with one of the agent’s tools.
handoff() options
Wrap a target in handoff() to configure it:
Input filters
The target sees the conversation asinputFilter returns it, under its own
system prompt (the system prompt of the agent that handed off is replaced,
never kept). The filter gets { messages, from, to, args }; messages is the
transcript so far without the system prompt, ending with the handoff call and
its result. Two filters are built in:
handoffFilters.removeToolCalls: keeps user and assistant text, drops tool calls and tool results.handoffFilters.lastUserMessage: keeps only the last user message.
result.messages and a
session’s history continue from it. A filter must keep at least one message.
Approvals remembered with once() in the earlier part of the transcript are
dropped at a handoff, so a target’s tools ask again.
What switches and what stays
The agent the run started with is the lead. A target gets none of the lead’s
tools, sub-agents or memory slots; its own memory slots are not
used either (as for a sub-agent). The lead’s
output instruction is added to
every target’s system prompt.
Sessions
In a session the active agent is the one the transcript last handed off to (theto of the last metadata.handoff marker), so the next
session.send() / session.stream() runs that agent:
agent.send() without a session always starts at the lead.
A target hands back with its own handoffs. Since the lead is created after
its targets, give the target an array and add the lead to it afterwards (the
array is read at every run):
handoffs must have its
own name. If the agent a transcript names is no longer reachable, the lead runs.
Approvals and resuming after a handoff
A target’s tool that needs approval pauses the run in the lead’s approval store;agent.approvals.resolve() on the lead continues it as the target. A
checkpointed run (send(message, { sessionId }) or a session with a store)
that crashed after a handoff resumes as the target with agent.resume(id) or
session.resume(). The drift check on resume (onAgentDrift, see
Durable execution)
compares with the target’s saved fingerprint.
When a step has a handoff call next to a call that pauses for approval, the
handoff waits: the run pauses as the agent that handed off, and the handoff
happens once the approval is decided and the step’s other calls ran.
Events
A handoff is reported between the handoff call’stool.start / tool.done
and the target’s first step.start:
handoff carries from, to and toolCallId. A run has one run.start
and one run.done however many handoffs it makes. See the
event schema.
Limits
- Only one handoff per model turn: when a turn calls several handoff tools, the first is honored and the others get a tool error.
maxHandoffs(acreateAgent()option, default 5) caps the handoffs of one run, to stop agents handing a conversation back and forth. A handoff call over it gets a tool error and the agent answers itself. A run resumed after an approval or a crash counts again from zero.- Remote agents (
remoteAgent()) cannot be handoff targets; use them as sub-agents. - A target does not hand back on its own at the end of its turn; give it a handoff to the lead (see Sessions).
The executor API
AgentExecutor.execute() takes handoffs (ResolvedHandoff[]: name,
toolName, description, input, spec(input) resolving the target’s run
configuration and its own handoffs, inputFilter, onHandoff) and
maxHandoffs. createAgent() builds them; pass them yourself only when you
drive the executor directly.