Learn how to build multi-agent systems where specialized agents collaborate on complex tasks.
Overview
Handoffs enable conversations to transfer between specialized agents. A triage agent routes requests to experts, each handling specific domains like billing, technical support, or account management. This pattern improves response quality by leveraging specialized knowledge and clear task boundaries.
Key benefits:
- Specialization: Each agent focuses on its expertise
- Maintainability: Modify specialist agents independently
- Scalability: Add new specialists without changing existing agents
- Clarity: Clean separation of responsibilities
Creating a Simple Handoff
Define a specialist agent with a handoffDescription, then configure the triage agent to hand off to it:
// Create a specialist agent for technical support
Agent<UnknownContext, TextOutput> supportAgent =
Agent.<UnknownContext, TextOutput>builder()
.name("Technical Support")
.instructions(
"You are a technical support specialist. "
+ "Help users troubleshoot technical issues, provide clear solutions, "
+ "and explain technical concepts in simple terms.")
.handoffDescription(
"Hands off technical support and troubleshooting questions to this agent")
.build();
// Create a triage agent that can hand off to the specialist
Agent<UnknownContext, TextOutput> triageAgent =
Agent.<UnknownContext, TextOutput>builder()
.name("Triage Agent")
.instructions(
"You are a triage agent. Your ONLY job is to transfer users to specialists. "
+ "For ANY technical problem, crash, or bug, you MUST call transfer_to_Technical_Support. "
+ "DO NOT try to help directly. ALWAYS transfer.")
.handoffs(List.of(supportAgent))
.build();
// Run with a technical question - triggers automatic handoff
RunResult<UnknownContext, ?> result = Runner.run(
triageAgent,
"My application keeps crashing when I click the save button. How do I fix this?"
);
System.out.println(result.getFinalOutput());
// Output from Technical Support specialist: "Let's troubleshoot this crash..."
How Handoffs Work
- Registration: Triage agent registers specialists via
.handoffs(List.of(...)) - Tool Generation: SDK generates
transfer_to_<AgentName>functions automatically - Agent Decision: Triage agent calls the appropriate transfer function
- Execution Transfer: Specialist agent takes over and handles the request
- Final Response: User receives the specialist's response
The SDK handles all transfer mechanics automatically. You define specialists and routing logic through instructions.
Multiple Specialists
Configure a triage agent to route to multiple specialists:
// Create specialized agents
Agent<UnknownContext, TextOutput> billingAgent =
Agent.<UnknownContext, TextOutput>builder()
.name("Billing Specialist")
.instructions("You are a billing specialist. Help users with payment issues.")
.handoffDescription("Handles billing, payments, refunds, and invoice questions")
.build();
Agent<UnknownContext, TextOutput> technicalAgent =
Agent.<UnknownContext, TextOutput>builder()
.name("Technical Support")
.instructions("You are a technical support specialist. Help with bugs and crashes.")
.handoffDescription("Handles technical issues, bugs, and troubleshooting")
.build();
Agent<UnknownContext, TextOutput> accountAgent =
Agent.<UnknownContext, TextOutput>builder()
.name("Account Manager")
.instructions("You are an account manager. Help with account settings.")
.handoffDescription("Handles account management and settings")
.build();
// Create triage agent with multiple handoff options
Agent<UnknownContext, TextOutput> triageAgent =
Agent.<UnknownContext, TextOutput>builder()
.name("Customer Service Triage")
.instructions("""
You are a triage agent. Your ONLY job is to transfer to specialists.
DO NOT help directly. You MUST call the appropriate transfer function:
- For billing, payments, refunds: call transfer_to_Billing_Specialist
- For technical issues, bugs, crashes: call transfer_to_Technical_Support
- For account settings, profile: call transfer_to_Account_Manager
ALWAYS transfer. DO NOT answer questions yourself.
""")
.handoffs(List.of(billingAgent, technicalAgent, accountAgent))
.build();
// Test different question types
RunResult<UnknownContext, ?> result1 = Runner.run(
triageAgent,
"I was charged twice for my subscription this month"
);
// Routes to Billing Specialist
RunResult<UnknownContext, ?> result2 = Runner.run(
triageAgent,
"How do I change my email address?"
);
// Routes to Account Manager
RunResult<UnknownContext, ?> result3 = Runner.run(
triageAgent,
"The app crashes every time I try to export data"
);
// Routes to Technical Support
The triage agent analyzes each question and selects the appropriate specialist automatically.
Handoff Description
The handoffDescription explains when to use each specialist. This description is presented to the triage agent as part of the transfer function documentation:
Agent<UnknownContext, TextOutput> specialist =
Agent.<UnknownContext, TextOutput>builder()
.name("Billing Specialist")
.instructions("Handle billing questions...")
.handoffDescription("Handles billing, payments, refunds, and invoice questions")
.build();
Writing Good Handoff Descriptions
- Be specific about the specialist's domain
- List key topics or question types
- Use clear, action-oriented language
- Avoid overlap between specialists
- Keep descriptions concise (1-2 sentences)
Tracking Handoffs
Monitor handoff execution using RunResult:
RunResult<UnknownContext, ?> result = Runner.run(triageAgent, "My question...");
// Count handoffs
long handoffCount = result.getNewItems().stream()
.filter(item -> item instanceof RunHandoffOutputItem)
.count();
System.out.println("Handoffs executed: " + handoffCount);
// Get handoff details
result.getNewItems().stream()
.filter(item -> item instanceof RunHandoffOutputItem)
.map(item -> (RunHandoffOutputItem) item)
.forEach(handoff -> {
System.out.println("From: " + handoff.getFromAgent());
System.out.println("To: " + handoff.getToAgent());
});
Track handoffs for:
- Debugging routing logic
- Analyzing specialist utilization
- Monitoring conversation flow
- Improving triage instructions
Triage Agent Patterns
Direct Transfer Pattern
Triage agent immediately transfers without responding:
Agent<UnknownContext, TextOutput> triageAgent =
Agent.<UnknownContext, TextOutput>builder()
.name("Triage")
.instructions("""
You are a triage agent. Your ONLY job is to transfer to specialists.
DO NOT answer questions. DO NOT provide information.
Immediately call the appropriate transfer function.
""")
.handoffs(List.of(specialist1, specialist2, specialist3))
.build();
Use when: You want specialists to handle all interactions.
Greeting + Transfer Pattern
Triage agent greets briefly, then transfers:
Agent<UnknownContext, TextOutput> triageAgent =
Agent.<UnknownContext, TextOutput>builder()
.name("Triage")
.instructions("""
You are a triage agent. Greet the user briefly, then transfer:
1. Acknowledge their question in one sentence
2. Immediately call the appropriate transfer function
DO NOT attempt to answer. Let specialists handle all questions.
""")
.handoffs(List.of(specialist1, specialist2, specialist3))
.build();
Use when: You want a friendly greeting before specialist engagement.
Smart Routing Pattern
Triage agent clarifies ambiguous requests before transferring:
Agent<UnknownContext, TextOutput> triageAgent =
Agent.<UnknownContext, TextOutput>builder()
.name("Triage")
.instructions("""
You are a triage agent. For clear requests, transfer immediately.
For ambiguous requests, ask ONE clarifying question, then transfer.
Never provide answers - specialists handle all questions.
""")
.handoffs(List.of(specialist1, specialist2, specialist3))
.build();
Use when: Questions might be ambiguous and benefit from clarification.
Nested Handoffs
Specialists can themselves have handoffs to create hierarchical routing:
// Level 2: Sub-specialists
Agent<UnknownContext, TextOutput> refundSpecialist = /* ... */;
Agent<UnknownContext, TextOutput> invoiceSpecialist = /* ... */;
// Level 1: Domain specialist with sub-specialists
Agent<UnknownContext, TextOutput> billingAgent =
Agent.<UnknownContext, TextOutput>builder()
.name("Billing Specialist")
.instructions("""
You are a billing specialist. For general billing questions, answer directly.
For refunds, call transfer_to_Refund_Specialist.
For invoice issues, call transfer_to_Invoice_Specialist.
""")
.handoffDescription("Handles billing, payments, and financial questions")
.handoffs(List.of(refundSpecialist, invoiceSpecialist))
.build();
// Level 0: Triage
Agent<UnknownContext, TextOutput> triageAgent =
Agent.<UnknownContext, TextOutput>builder()
.name("Triage")
.instructions("Route to appropriate specialist...")
.handoffs(List.of(billingAgent, /* other specialists */))
.build();
This creates a three-level hierarchy: Triage → Billing → Refund/Invoice specialists.
Session Management with Handoffs
Use sessions to maintain context across handoffs:
Session session = new MemorySession("customer-123");
RunConfig config = RunConfig.builder().session(session).build();
// First interaction - routes to billing
RunResult<UnknownContext, ?> result1 = Runner.run(
triageAgent,
"I was charged incorrectly",
config
);
// Follow-up question - specialist remembers context
RunResult<UnknownContext, ?> result2 = Runner.run(
triageAgent,
"Can you refund the extra charge?",
config
);
// Context is preserved across handoffs
Specialists access full conversation history, including interactions before the handoff.
Best Practices
Specialist Design
- Narrow Scope: Each specialist handles one clear domain
- Complete Instructions: Provide comprehensive guidance for the specialist's area
- Avoid Overlaps: Clearly distinguish between specialist domains
- Test Coverage: Verify all question types route correctly
- Clear Boundaries: Use handoff descriptions to define scope explicitly
Triage Instructions
- Explicit Routing: Clearly specify which specialist handles what
- Prevent Answering: Instruct triage not to answer questions directly
- Function Names: Reference exact transfer function names in instructions
- Edge Cases: Address ambiguous scenarios explicitly
- Mandatory Transfers: Use strong language like "MUST transfer" or "ALWAYS call"
Performance
- Minimize Turns: Design for single-handoff scenarios
- Session Reuse: Use sessions to reduce context repetition
- Monitor Costs: Track token usage across multi-agent conversations
- Test Routing: Validate triage logic with representative questions
- Measure Accuracy: Monitor misrouted questions
Common Pitfalls
- Triage Answering: Triage agents trying to help instead of transferring
- Unclear Boundaries: Overlapping specialist domains causing confusion
- Missing Descriptions: Handoff descriptions that don't guide routing
- Excessive Nesting: Too many handoff levels increasing complexity
- Context Loss: Not using sessions for multi-turn handoff conversations
When to Use Handoffs
Use handoffs when:
- Different questions require different expertise
- You want modular, independent agent development
- Routing logic can be clearly defined
- You need to scale specialists independently
Don't use handoffs when:
- A single agent can handle all scenarios
- Questions require seamless context across all topics
- Handoff overhead outweighs specialization benefits
- Routing logic is too complex or ambiguous
Next Steps
- Run Context - Custom context and tool approval across handoffs
- Sessions - Maintain conversation memory through handoffs
- Tools - Add specialized tools to specialist agents
- Tracing - Monitor handoff execution in detail
Additional Resources
- AgentHandoffExample.java - Complete handoff examples
- API Reference - Complete Javadoc documentation