Handoffs
Transfer control between specialized agents
Handoffs
A handoff transfers control from one agent to another. Unlike using an agent as a tool (where the parent waits for a result), a handoff completely replaces the current agent — the new agent takes over the conversation.
Basic Handoff
import { Agent } from 'assistme-agent-sdk'
const techSupport = new Agent({
name: 'tech_support',
model: claude('claude-sonnet-4-6'),
instructions: 'You are a technical support specialist. Help users solve technical problems.',
tools: [searchDocs, createTicket],
})
const billing = new Agent({
name: 'billing',
model: claude('claude-sonnet-4-6'),
instructions: 'You are a billing specialist. Help users with payment and subscription issues.',
tools: [lookupAccount, processRefund],
})
const triageAgent = new Agent({
name: 'triage',
model: claude('claude-haiku-4-5'),
instructions: `You are a triage agent. Determine what the user needs and hand off to the right specialist:
- Technical questions → tech_support
- Billing questions → billing
- General questions → answer directly`,
handoffs: [techSupport, billing],
})When triageAgent decides to hand off, it uses a special tool call that the Runner interprets as a control transfer:
const result = await Runner.run(triageAgent, {
messages: [{ role: 'user', content: 'I need to update my credit card' }],
})
// result.status === 'handoff'
// result.handoffTarget === billing
// The billing agent now has the conversationHow Handoffs Work
User: "I need to update my credit card"
│
▼
┌────────────────┐
│ Triage Agent │ Classifies request → billing
│ (Haiku) │
└───────┬────────┘
│ handoff
▼
┌────────────────┐
│ Billing Agent │ Takes over the conversation
│ (Sonnet) │ Has full conversation history
└────────────────┘Key behaviors:
- The new agent receives the full conversation history
- The old agent stops executing
- The stream seamlessly transitions to the new agent
- The handoff reason is logged for observability
Handoff with Context
Pass additional context during a handoff:
const triageAgent = new Agent({
name: 'triage',
model: claude('claude-haiku-4-5'),
instructions: `Classify the user's issue and hand off with context.`,
handoffs: [
{
agent: techSupport,
description: 'Hand off technical issues to tech support',
contextProvider: async (messages) => {
// Enrich the handoff with additional context
const userAccount = await lookupUser(messages)
return {
additionalInstructions: `User account: ${userAccount.id}, Plan: ${userAccount.plan}`,
}
},
},
],
})Circular Handoffs
Agents can hand off back and forth. This is useful for workflows where the user's needs evolve:
const salesAgent = new Agent({
name: 'sales',
instructions: 'Help users choose the right plan. Hand off to billing for payment.',
handoffs: [billingAgent],
})
const billingAgent = new Agent({
name: 'billing',
instructions: 'Process payments. Hand off to sales if user wants to change plans.',
handoffs: [salesAgent],
})Caution: Set maxTurns to prevent infinite handoff loops.
Handoffs vs. Agent-as-Tool
| Handoff | Agent-as-Tool | |
|---|---|---|
| Control | Transfers completely | Parent retains control |
| Context | New agent gets full conversation | Sub-agent gets isolated context |
| Return | New agent responds to user | Result returns to parent agent |
| Token efficiency | Good (no supervisor overhead) | Better (sub-agent context is smaller) |
| Use case | Routing to specialists | Delegating subtasks |
// Handoff: billing agent takes over
const triage = new Agent({
handoffs: [billingAgent], // Control transfers to billing
})
// Agent-as-Tool: supervisor uses billing as a tool
const supervisor = new Agent({
tools: [
billingAgent.asTool({ name: 'check_billing' }), // Supervisor stays in control
],
})Streaming Through Handoffs
Streams seamlessly continue across handoffs:
const stream = Runner.stream(triageAgent, { messages })
for await (const event of stream) {
if (event.type === 'handoff') {
console.log(`Transferred from ${event.from} to ${event.to}`)
// Stream continues with the new agent
}
if (event.type === 'text_delta') {
// Works across agent boundaries
process.stdout.write(event.delta)
}
}Multi-Level Handoffs
Chain handoffs for complex routing:
const frontDesk = new Agent({
name: 'front_desk',
handoffs: [salesTeam, supportTeam],
})
const salesTeam = new Agent({
name: 'sales_team',
handoffs: [enterpriseSales, smbSales],
})
const enterpriseSales = new Agent({
name: 'enterprise_sales',
// No handoffs — this is a leaf agent
tools: [crm, scheduler, contractGenerator],
})Best Practices
-
Use cheap models for triage — Routing decisions don't need powerful models. Haiku-class models are fast and accurate for classification.
-
Provide clear handoff descriptions — The triage agent decides based on the
descriptionfield. Make them specific and unambiguous. -
Pass context on handoff — Don't make the specialist re-discover information the triage agent already found.
-
Avoid deep handoff chains — Each handoff adds latency. If you need 4+ handoffs, consider a supervisor pattern instead.
-
Log handoff decisions — Track which agents get handed off to and why. This reveals routing accuracy and optimization opportunities.