Documentation
Handoffs
Transfer the current run from one agent to another. Conversation stays; the specialist’s instructions and tools take over.
What they are
A handoff transfers the *current run* from one agent to another. The conversation so far is kept. The specialist’s instructions and tools take over. It is not a nested run() that throws away context.
Use when one agent should not do everything: a triage bot for “where is my package?” vs a billing agent for “double charge”.
Do not use handoffs for a simple tool call. If billing is one HTTP API, make a refund tool. Handoffs are for a different *policy and tool set*.
How to wire them
Build the specialist first, then pass it into the parent:
const billing = Agent.builder()
.name("billing")
.instructions("You resolve invoices and charges. Be precise about amounts.")
.apiKey(key)
.tools([lookupInvoice, issueCredit])
.build();
const triage = Agent.builder()
.name("triage")
.instructions(
"Greet the user. If the issue is billing or invoices, transfer to billing. Otherwise help yourself.",
)
.apiKey(key)
.handoffs([billing])
.maxHandoffs(3)
.build();
const result = await triage.run("I was charged twice for March.");
console.log(result.handoffs);
console.log(result.agentName);
console.log(result.output);The model on triage sees a tool named transfer_to_billing with a reason argument. Shirube executes the transfer; you do not call billing.run() yourself.
Loop prevention
| Guard | Behavior |
|---|---|
maxHandoffs (default 4) | Further transfers are refused; the current agent is told why and must answer |
| Visited set | The same agent cannot be entered twice in one run (A → B → A is blocked) |
Blocked handoffs are not thrown as user-facing errors. They become a system note so the model can finish.
Observability
result.handoffs— strings liketriage->billing- Trace event
type: "handoff" - Runtime event
handoff.started(from,to,reason)
Use these in logs when a ticket “suddenly” changed personality.