Handoffs are the essential mechanism that controls how agents interact and pass control to each other in a group chat. If tools represent what agents can do and context variables represent what they know, handoffs determine where they go next.
Introduction to Handoffs
Handoffs define the paths a conversation can take through your multi-agent system. They allow you to create sophisticated workflows where the right agent handles each part of a conversation at the right time.
Why Handoffs Matter
In any multi-agent system, you need to carefully control which agent speaks when.
Handoffs provide this control by:
- Directing the conversation to the most appropriate agent for each situation
- Creating predictable workflows with clear transitions
- Enabling complex decision trees based on conversation state
- Allowing dynamic adaptation to user inputs
The Hospital Triage Analogy
Extending our hospital emergency room analogy, when a patient enters the ER, they first see a triage nurse who assesses their condition. The triage nurse then makes a critical decision:
- For cardiac symptoms, transfer immediately to the cardiologist
- For respiratory issues, send to the pulmonologist
- For minor injuries, direct to the general practitioner
- For severe trauma, rush to the trauma surgeon
Each medical professional follows specific “handoff protocols” that determine:
- When they need to transfer a patient
- Who they should transfer to
- What information must be included in the handoff
These protocols ensure the patient receives the right care at the right time. In AG2, handoffs serve the same purpose for your conversational agents.
Core Handoff Concepts
Each agent in AG2 has a handoffs
attribute that manages transitions from that agent. The handoffs attribute is an instance of the Handoffs
class, which provides methods for defining when and where control should pass:
Transition Targets
When defining a handoff, you specify a transition target - the destination where control should go. AG2 provides several types of transition targets:
Here’s how you might use transition targets:
ReplyResults and Transitions
Each tool function can return a ReplyResult
that specifies a transition target:
This creates a powerful mechanism where tools can determine the conversation flow based on their results.
Types of Handoffs
AG2 offers four main ways to define handoffs:
- LLM-based conditions: Transitions based on the language model’s analysis of messages
- Context-based conditions: Transitions based on values in context variables
- After-work behavior: Default transition when no LLM or context conditions are met and no tools are called
- Explicit handoffs from tools: Direct transitions specified by tool return values
Let’s explore each type in our triage example.
LLM-Based Conditions
LLM-based conditions use the language model to determine when a transition should occur. This is useful when the decision depends on understanding the meaning of messages:
In this example, the triage_agent
uses the LLM to evaluate each condition prompt against the messages in the conversation. If it determines that the user query is about a technical issue, control passes to the tech agent.
Context-Based Conditions
Context-based conditions transition based on the values of context variables. This is ideal for decisions that depend on the system’s state rather than message content:
This example transitions to an escalation agent when the issue_severity
context variable is 8 or higher.
After-Work Behavior
After-work behavior defines the default transition that occurs after an agent completes its work and no specific condition is met:
In this example, control returns to the user after the tech agent finishes speaking, unless another condition routes it elsewhere.
Tools can specify transitions directly in their return values:
This gives you the most direct control over transitions based on tool execution results.
Configuring Handoffs for Agents
Let’s build a tech support system to demonstrate different types of handoffs and how they work together to create a seamless user experience.
Enhancing our Support System with Handoffs
We will enhance our support system with multiple specialized agents that handle different aspects of customer service:
- A triage agent that analyzes initial queries
- A general support agent for non-technical questions
- Specialists for different device types (computers and smartphones)
- An advanced troubleshooting agent for persistent issues
This example demonstrates how a real-world support system might use handoffs to route customers efficiently.
Setting Up the Initial Structure
First, let’s create our context variables and agents:
from typing import Annotated
from autogen import ConversableAgent, LLMConfig
from autogen.agentchat import initiate_group_chat
from autogen.agentchat.group.patterns import AutoPattern
from autogen.agentchat.group import (
ContextVariables, ReplyResult, AgentTarget,
OnCondition, StringLLMCondition,
OnContextCondition, ExpressionContextCondition, ContextExpression,
RevertToUserTarget
)
support_context = ContextVariables(data={
"query_count": 0,
"repeat_issue": False,
"previous_solutions": [],
"issue_type": "",
"issue_subtype": "",
})
llm_config = LLMConfig(api_type="openai", model="gpt-4o-mini")
with llm_config:
triage_agent = ConversableAgent(
name="triage_agent",
system_message="""You are a support triage agent. Your role is to:
1. Determine if a query is technical or general
2. Use the classify_query function to route appropriately
Do not attempt to solve issues yourself - your job is proper routing."""
)
general_agent = ConversableAgent(
name="general_agent",
system_message="""You are a general support agent who handles non-technical questions.
If the user is a premium customer (check account_tier context variable),
you should transfer them directly to the premium support agent.
Otherwise, provide helpful responses to general inquiries."""
)
tech_agent = ConversableAgent(
name="tech_agent",
system_message="""You are a technical support agent who handles the initial assessment
of all technical issues.
If the user is a premium customer (check account_tier context variable),
you should transfer them directly to the premium support agent.
Otherwise, determine if the issue is related to:
- Computer issues (laptops, desktops, PCs, Macs)
- Smartphone issues (iPhones, Android phones, tablets)
And route to the appropriate specialist."""
)
computer_agent = ConversableAgent(
name="computer_agent",
system_message="""You are a computer specialist who handles issues with laptops, desktops,
PCs, and Macs. You provide troubleshooting for hardware and software issues specific to
computers. You're knowledgeable about Windows, macOS, Linux, and common computer peripherals.
For first-time issues, provide a solution directly.
If a user returns and says they tried your solution but are still having the issue,
use the check_repeat_issue function to escalate to advanced troubleshooting. Do not provide a solution
yourself for returning users, simply route it to advanced troubleshooting."""
)
smartphone_agent = ConversableAgent(
name="smartphone_agent",
system_message="""You are a smartphone specialist who handles issues with mobile devices
including iPhones, Android phones, and tablets. You're knowledgeable about iOS, Android,
mobile apps, battery issues, screen problems, and connectivity troubleshooting.
For first-time issues, provide a solution directly.
If a user returns and says they tried your solution but are still having the issue,
use the check_repeat_issue function to escalate to advanced troubleshooting. Do not provide a solution
yourself for returning users, simply route it to advanced troubleshooting"""
)
advanced_troubleshooting_agent = ConversableAgent(
name="advanced_troubleshooting_agent",
system_message="""You are an advanced troubleshooting specialist who handles complex,
persistent issues that weren't resolved by initial solutions. You provide deeper
diagnostic approaches and more comprehensive solutions for difficult technical problems."""
)
Now let’s define the tool functions that enable our agents to make routing decisions and update the conversation state:
Configuring LLM-Based Handoffs
Our first type of handoff uses the LLM to analyze messages and route based on content. The tech agent uses this to route device-specific issues:
With these LLM-based handoffs, the tech agent analyzes the user’s message to identify if they’re discussing a computer or smartphone issue, then routes accordingly. The LLM evaluates the prompt against the conversation content to make this decision.
Configuring Context-Based Handoffs
Context-based handoffs make decisions based on the state of the conversation rather than message content. We use these to detect when a customer returns with the same issue:
These context-based handoffs check if repeat_issue
is set to True
in the context variables. When a user says their issue wasn’t solved, the check_repeat_issue
tool sets this flag, and the handoff condition triggers a transfer to advanced troubleshooting.
Setting Default After-Work Behavior
Finally, we set the default behavior for when agents complete their work without a specific handoff triggering:
This ensures that after these agents finish helping a customer, control returns to the user, who can ask another question or end the conversation.
Putting It All Together
Now we can set up the conversation pattern and run the chat:
By combining different handoff types, we’ve created a support system that can:
- Route based on message content (LLM-based handoffs)
- Route based on conversation state (context-based handoffs)
- Provide fallback behaviors when no conditions are met (after-work)
- Use tools to update context and influence routing
Complete Example
???+ info “Complete Code Example”
from typing import Annotated
from autogen import ConversableAgent, LLMConfig
from autogen.agentchat import initiate_group_chat
from autogen.agentchat.group.patterns import AutoPattern
from autogen.agentchat.group import (
ContextVariables, ReplyResult, AgentTarget,
OnCondition, StringLLMCondition,
OnContextCondition, ExpressionContextCondition, ContextExpression,
RevertToUserTarget
)
support_context = ContextVariables(data={
"query_count": 0,
"repeat_issue": False,
"previous_solutions": [],
"issue_type": "",
"issue_subtype": "",
})
llm_config = LLMConfig(api_type="openai", model="gpt-4o-mini")
with llm_config:
triage_agent = ConversableAgent(
name="triage_agent",
system_message="""You are a support triage agent. Your role is to:
1. Determine if a query is technical or general
2. Use the classify_query function to route appropriately
Do not attempt to solve issues yourself - your job is proper routing."""
)
general_agent = ConversableAgent(
name="general_agent",
system_message="""You are a general support agent who handles non-technical questions.
If the user is a premium customer (check account_tier context variable),
you should transfer them directly to the premium support agent.
Otherwise, provide helpful responses to general inquiries."""
)
tech_agent = ConversableAgent(
name="tech_agent",
system_message="""You are a technical support agent who handles the initial assessment
of all technical issues.
If the user is a premium customer (check account_tier context variable),
you should transfer them directly to the premium support agent.
Otherwise, determine if the issue is related to:
- Computer issues (laptops, desktops, PCs, Macs)
- Smartphone issues (iPhones, Android phones, tablets)
And route to the appropriate specialist."""
)
computer_agent = ConversableAgent(
name="computer_agent",
system_message="""You are a computer specialist who handles issues with laptops, desktops,
PCs, and Macs. You provide troubleshooting for hardware and software issues specific to
computers. You're knowledgeable about Windows, macOS, Linux, and common computer peripherals.
For first-time issues, provide a solution directly.
If a user returns and says they tried your solution but are still having the issue,
use the check_repeat_issue function to escalate to advanced troubleshooting. Do not provide a solution yourself for returning users, simply route it to advanced troubleshooting."""
)
smartphone_agent = ConversableAgent(
name="smartphone_agent",
system_message="""You are a smartphone specialist who handles issues with mobile devices
including iPhones, Android phones, and tablets. You're knowledgeable about iOS, Android,
mobile apps, battery issues, screen problems, and connectivity troubleshooting.
For first-time issues, provide a solution directly.
If a user returns and says they tried your solution but are still having the issue,
use the check_repeat_issue function to escalate to advanced troubleshooting. Do not provide a solution yourself for returning users, simply route it to advanced troubleshooting"""
)
advanced_troubleshooting_agent = ConversableAgent(
name="advanced_troubleshooting_agent",
system_message="""You are an advanced troubleshooting specialist who handles complex,
persistent issues that weren't resolved by initial solutions. You provide deeper
diagnostic approaches and more comprehensive solutions for difficult technical problems."""
)
def classify_query(
query: Annotated[str, "The user query to classify"],
context_variables: ContextVariables
) -> ReplyResult:
"""Classify a user query and route to the appropriate agent."""
context_variables["query_count"] += 1
technical_keywords = ["error", "bug", "broken", "crash", "not working", "shutting down",
"frozen", "blue screen", "won't start", "slow", "virus"]
if any(keyword in query.lower() for keyword in technical_keywords):
return ReplyResult(
message="This appears to be a technical issue. Let me route you to our tech support team.",
target=AgentTarget(tech_agent),
context_variables=context_variables
)
else:
return ReplyResult(
message="This appears to be a general question. Let me connect you with our general support team.",
target=AgentTarget(general_agent),
context_variables=context_variables
)
def check_repeat_issue(
description: Annotated[str, "User's description of the continuing issue"],
context_variables: ContextVariables
) -> ReplyResult:
"""Check if this is a repeat of an issue that wasn't resolved."""
context_variables["repeat_issue"] = True
context_variables["continuing_issue"] = description
return ReplyResult(
message="I understand that your issue wasn't resolved. Let me connect you with our advanced troubleshooting specialist.",
target=AgentTarget(advanced_troubleshooting_agent),
context_variables=context_variables
)
triage_agent.functions = [classify_query]
computer_agent.functions = [check_repeat_issue]
smartphone_agent.functions = [check_repeat_issue]
tech_agent.handoffs.add_llm_conditions([
OnCondition(
target=AgentTarget(computer_agent),
condition=StringLLMCondition(prompt="Route to computer specialist when the issue involves laptops, desktops, PCs, or Macs."),
),
OnCondition(
target=AgentTarget(smartphone_agent),
condition=StringLLMCondition(prompt="Route to smartphone specialist when the issue involves phones, mobile devices, iOS, or Android."),
)
])
tech_agent.handoffs.set_after_work(RevertToUserTarget())
computer_agent.handoffs.add_context_conditions([
OnContextCondition(
target=AgentTarget(advanced_troubleshooting_agent),
condition=ExpressionContextCondition(
expression=ContextExpression("${repeat_issue} == True")
)
)
])
smartphone_agent.handoffs.add_context_conditions([
OnContextCondition(
target=AgentTarget(advanced_troubleshooting_agent),
condition=ExpressionContextCondition(
expression=ContextExpression("${repeat_issue} == True")
)
)
])
advanced_troubleshooting_agent.handoffs.set_after_work(RevertToUserTarget())
general_agent.handoffs.set_after_work(RevertToUserTarget())
user = ConversableAgent(name="user", human_input_mode="ALWAYS")
pattern = AutoPattern(
initial_agent=triage_agent,
agents=[
triage_agent,
tech_agent,
computer_agent,
smartphone_agent,
advanced_troubleshooting_agent,
general_agent
],
user_agent=user,
context_variables=support_context,
group_manager_args = {"llm_config": llm_config},
)
result, final_context, last_agent = initiate_group_chat(
pattern=pattern,
messages="My laptop keeps shutting down randomly. Can you help?",
max_rounds=15
)
Example Output
If you run the complete example, you should see a conversation flow similar to this:
Next Steps: Putting Everything Together with Patterns
You’ve already seen AutoPattern
in action in this example, where it allows the system to dynamically select agents based on conversation context. In the next section, you’ll explore the full range of orchestration patterns available in AG2.
In the upcoming “Patterns” section, you’ll discover:
- How different pattern types (DefaultPattern, RoundRobinPattern, RandomPattern, etc.) offer alternative approaches to agent selection and conversation flow
- When to choose each pattern type based on your specific use case requirements
- How to customize patterns with additional configuration options
- Advanced techniques for combining patterns with handoffs, context variables, and tools
The patterns section completes your understanding of AG2’s orchestration capabilities, showing how these powerful components work together to create intelligent, responsive multi-agent systems for complex real-world tasks.