· 5 min read
Handoffs are the mechanism that turns a collection of independent agents into a coordinated team. When one agent transfers work to another — passing along context, intermediate results, and instructions — the system can handle tasks that no single agent could manage alone. A research agent gathers data, then hands off to an analysis agent that produces insights, which in turn hands off to a report agent that formats the final output.
The Claude Agent SDK supports handoffs as a first-class concept. You define handoff targets and conditions directly in agent configuration, and the SDK manages the context transfer when a handoff occurs. This is more structured than ad-hoc approaches where you manually concatenate outputs and feed them to the next agent — the SDK preserves conversation context and provides clear transition points.
Getting handoffs right is critical because they are the most common failure point in multi-agent systems. A handoff that drops important context, routes to the wrong agent, or creates an infinite loop between agents can render an otherwise well-designed system unusable.
Use explicit handoffs when:
Avoid handoffs when:
The simplest handoff pattern uses the SDK's built-in handoff configuration.
import { Agent } from "@anthropic-ai/agent-sdk";
const billingAgent = new Agent({
name: "billing-specialist",
model: "claude-sonnet-4-20250514",
instructions: `You handle billing inquiries: invoices, payment methods,
subscription changes, and refund requests. You have access to the
billing system and can make changes to customer accounts.
If the customer has a technical issue unrelated to billing, hand off
to the technical support agent.`,
tools: [invoiceTool, paymentTool, subscriptionTool],
handoffs: [
{
agent: techSupportAgent,
condition: "Customer has a technical issue, not a billing question",
},
{
agent: escalationAgent,
condition: "Customer is requesting manager review or is dissatisfied with resolution",
},
],
});
const techSupportAgent = new Agent({
name: "tech-support",
model: "claude-sonnet-4-20250514",
instructions: `You handle technical issues: bugs, configuration problems,
integration questions, and feature usage guidance. You can access
system logs and documentation.
If the customer has a billing question, hand off to the billing specialist.`,
tools: [logSearchTool, docSearchTool, systemStatusTool],
handoffs: [
{
agent: billingAgent,
condition: "Customer has a billing or payment question",
},
{
agent: escalationAgent,
condition: "Issue requires engineering team involvement",
},
],
});
A common pattern is a lightweight triage agent that classifies requests and routes them immediately.
const triageAgent = new Agent({
name: "triage-router",
model: "claude-sonnet-4-20250514",
instructions: `You are the first point of contact. Your only job is to
understand the customer's request and route them to the right specialist.
Routing rules:
1. Billing, invoices, payments, subscriptions -> billing-specialist
2. Bugs, errors, technical configuration -> tech-support
3. Product questions, feature requests, how-to -> product-expert
4. Account deletion, data export, compliance -> account-manager
Ask ONE clarifying question if the category is ambiguous.
Never attempt to solve the problem yourself.`,
handoffs: [
{ agent: billingAgent, condition: "Billing or payment related inquiry" },
{ agent: techSupportAgent, condition: "Technical issue or bug report" },
{ agent: productExpert, condition: "Product question or feature request" },
{ agent: accountManager, condition: "Account management or compliance" },
],
});
For complex workflows, you may need to explicitly structure what context transfers between agents.
interface HandoffContext {
originalQuery: string;
previousAgentName: string;
previousAgentFindings: string;
handoffReason: string;
metadata: Record<string, unknown>;
}
function buildHandoffMessage(context: HandoffContext): string {
return [
`## Handoff from ${context.previousAgentName}`,
`**Original request:** ${context.originalQuery}`,
`**Reason for handoff:** ${context.handoffReason}`,
`**Previous findings:**`,
context.previousAgentFindings,
`**Additional context:** ${JSON.stringify(context.metadata)}`,
].join("\n\n");
}
async function sequentialHandoff(query: string) {
// Stage 1: Research
const researchResult = await researchAgent.run(query, { maxTurns: 10 });
// Stage 2: Analysis with structured handoff context
const analysisInput = buildHandoffMessage({
originalQuery: query,
previousAgentName: "research-agent",
previousAgentFindings: researchResult.output,
handoffReason: "Research complete, ready for analysis",
metadata: { sourcesFound: 5, confidenceLevel: "high" },
});
const analysisResult = await analysisAgent.run(analysisInput, { maxTurns: 10 });
// Stage 3: Report with full chain context
const reportInput = buildHandoffMessage({
originalQuery: query,
previousAgentName: "analysis-agent",
previousAgentFindings: analysisResult.output,
handoffReason: "Analysis complete, ready for report generation",
metadata: { findingsCount: 8, recommendationsCount: 3 },
});
return reportAgent.run(reportInput, { maxTurns: 8 });
}
Guard against agents handing back and forth indefinitely by tracking handoff depth.
async function runWithHandoffLimit(
agent: Agent,
input: string,
maxHandoffs: number = 5,
currentDepth: number = 0
): Promise<{ output: string; handoffCount: number }> {
if (currentDepth >= maxHandoffs) {
const fallbackAgent = new Agent({
name: "fallback",
model: "claude-sonnet-4-20250514",
instructions: `The conversation has been routed ${maxHandoffs} times
without resolution. Provide the best possible answer based on the
conversation history, and note any limitations.`,
});
const result = await fallbackAgent.run(input, { maxTurns: 5 });
return { output: result.output, handoffCount: currentDepth };
}
const result = await agent.run(input, { maxTurns: 10 });
if (result.handoff) {
return runWithHandoffLimit(
result.handoff.targetAgent,
result.handoff.message,
maxHandoffs,
currentDepth + 1
);
}
return { output: result.output, handoffCount: currentDepth };
}
Handoffs are the connective tissue in every multi-agent coordination pattern:
In Sequential Pipelines, handoffs transfer work forward through stages. Each handoff should include a structured summary of the previous stage's output, not the raw conversation history. This prevents context windows from filling up in later stages.
In Router patterns, the triage agent's handoff decision is the most critical single decision in the system. A misroute sends the customer to an agent that cannot help, creating frustration and wasting time. Invest heavily in triage prompt engineering and test with edge cases.
In Hierarchical patterns, a manager agent delegates to specialists and receives their results. The handoff in both directions matters — the delegation message must be specific enough for the specialist, and the result message must be structured enough for the manager to synthesize.
Summarize, do not forward raw history. When handing off between agents, send a structured summary of findings and context rather than the entire conversation transcript. This keeps context windows manageable and prevents the receiving agent from being confused by irrelevant turns.
Make handoff conditions mutually exclusive. If two handoff conditions can match the same input, the agent will pick one unpredictably. Define clear boundaries and handle ambiguous cases with a specific rule (e.g., "when in doubt, route to the general support agent").
Track handoff chains for debugging. Log every handoff with the source agent, target agent, reason, and transferred context. When a user reports a bad outcome, the handoff chain is usually the first thing you need to examine.
Test the full handoff cycle. Do not just test individual agents in isolation. Test scenarios that require multiple handoffs end-to-end, including cases where the first agent misclassifies and the second agent must re-route.
Set a hard cap on handoff depth. Without a limit, two agents with overlapping responsibilities can bounce requests between each other indefinitely. A maximum of 3-5 handoffs covers legitimate use cases; anything beyond that indicates a routing problem.