0
# Handoffs
1
2
Handoffs enable agent-to-agent delegation in multi-agent workflows. They are implemented as specialized tool calls that transfer control from one agent to another, with support for input filtering, history management, and custom handoff logic.
3
4
## Capabilities
5
6
### Handoff Class
7
8
Configuration for agent handoffs with customizable behavior.
9
10
```python { .api }
11
class Handoff[TContext, TAgent]:
12
"""
13
Agent handoff configuration.
14
15
Type Parameters:
16
- TContext: Type of context object
17
- TAgent: Type of target agent
18
19
Attributes:
20
- tool_name: str - Tool name for handoff
21
- tool_description: str - Tool description for LLM
22
- input_json_schema: dict[str, Any] - Input schema
23
- on_invoke_handoff: Callable - Invocation function
24
- agent_name: str - Target agent name
25
- input_filter: HandoffInputFilter | None - Input filter function
26
- nest_handoff_history: bool | None - History nesting override
27
- strict_json_schema: bool - Use strict JSON schema mode
28
- is_enabled: bool | Callable - Enabled state or function
29
"""
30
31
def get_transfer_message(agent: Agent) -> str:
32
"""
33
Get transfer message for handoff.
34
35
Parameters:
36
- agent: Target agent
37
38
Returns:
39
- str: Transfer message
40
"""
41
42
@classmethod
43
def default_tool_name(agent: Agent) -> str:
44
"""
45
Get default tool name for agent.
46
47
Parameters:
48
- agent: Agent to generate name for
49
50
Returns:
51
- str: Default tool name
52
"""
53
54
@classmethod
55
def default_tool_description(agent: Agent) -> str:
56
"""
57
Get default description for agent.
58
59
Parameters:
60
- agent: Agent to generate description for
61
62
Returns:
63
- str: Default description
64
"""
65
```
66
67
### Handoff Function
68
69
Helper function to create handoffs from agents.
70
71
```python { .api }
72
def handoff(
73
agent: Agent,
74
*,
75
tool_name_override: str | None = None,
76
tool_description_override: str | None = None,
77
on_handoff: Callable | None = None,
78
input_type: type | None = None,
79
input_filter: HandoffInputFilter | None = None,
80
nest_handoff_history: bool | None = None,
81
is_enabled: bool | Callable = True
82
) -> Handoff[TContext, Agent[TContext]]:
83
"""
84
Create handoff from agent.
85
86
Parameters:
87
- agent: Target agent for handoff
88
- tool_name_override: Custom tool name (default: "transfer_to_{agent_name}")
89
- tool_description_override: Custom description
90
- on_handoff: Callback when handoff occurs
91
- input_type: Type for handoff input parameters
92
- input_filter: Function to filter/transform handoff input
93
- nest_handoff_history: Whether to nest history in single message
94
- is_enabled: Whether handoff is enabled or function to determine
95
96
Returns:
97
- Handoff: Configured handoff object
98
"""
99
```
100
101
Usage example:
102
103
```python
104
from agents import Agent, handoff
105
106
support_agent = Agent(
107
name="Support Agent",
108
instructions="Handle customer support requests."
109
)
110
111
sales_agent = Agent(
112
name="Sales Agent",
113
instructions="Handle sales inquiries."
114
)
115
116
# Simple handoff
117
triage_agent = Agent(
118
name="Triage Agent",
119
instructions="Route to appropriate agent.",
120
handoffs=[support_agent, sales_agent]
121
)
122
123
# Custom handoff with callback
124
def on_transfer_to_sales(ctx, agent, input_data):
125
print(f"Transferring to sales with: {input_data}")
126
127
custom_handoff = handoff(
128
sales_agent,
129
tool_name_override="escalate_to_sales",
130
tool_description_override="Escalate to sales team for complex inquiries",
131
on_handoff=on_transfer_to_sales
132
)
133
134
triage_agent = Agent(
135
name="Triage Agent",
136
handoffs=[support_agent, custom_handoff]
137
)
138
```
139
140
### Handoff Input Data
141
142
Data structure containing handoff context and history.
143
144
```python { .api }
145
class HandoffInputData:
146
"""
147
Input data for handoffs.
148
149
Attributes:
150
- input_history: str | tuple[TResponseInputItem, ...] - Input history
151
- pre_handoff_items: tuple[RunItem, ...] - Items before handoff
152
- new_items: tuple[RunItem, ...] - New items including handoff
153
- run_context: RunContextWrapper | None - Run context
154
"""
155
156
def clone(**kwargs) -> HandoffInputData:
157
"""
158
Create modified copy.
159
160
Parameters:
161
- **kwargs: Fields to override
162
163
Returns:
164
- HandoffInputData: New instance with changes
165
"""
166
```
167
168
### Handoff Input Filter
169
170
Function type for filtering handoff inputs.
171
172
```python { .api }
173
HandoffInputFilter = Callable[
174
[HandoffInputData],
175
MaybeAwaitable[HandoffInputData]
176
]
177
```
178
179
Usage example:
180
181
```python
182
from agents import Agent, handoff, HandoffInputData
183
184
async def filter_sensitive_data(data: HandoffInputData) -> HandoffInputData:
185
"""Remove sensitive information before handoff."""
186
# Filter history
187
filtered_history = [
188
item for item in data.input_history
189
if not contains_sensitive_info(item)
190
]
191
return data.clone(input_history=tuple(filtered_history))
192
193
specialist_agent = Agent(
194
name="Specialist",
195
instructions="Handle complex cases."
196
)
197
198
filtered_handoff = handoff(
199
specialist_agent,
200
input_filter=filter_sensitive_data
201
)
202
203
main_agent = Agent(
204
name="Main Agent",
205
handoffs=[filtered_handoff]
206
)
207
```
208
209
### History Management
210
211
Functions for managing conversation history during handoffs.
212
213
```python { .api }
214
def nest_handoff_history() -> list[TResponseInputItem]:
215
"""
216
Nest handoff history into single message.
217
218
Wraps the entire conversation history in a single message
219
to reduce token usage and improve context management.
220
221
Returns:
222
- list[TResponseInputItem]: Nested history
223
"""
224
225
def default_handoff_history_mapper() -> list[TResponseInputItem]:
226
"""
227
Default history mapper for handoffs.
228
229
Returns:
230
- list[TResponseInputItem]: Mapped history
231
"""
232
```
233
234
Type alias:
235
236
```python { .api }
237
HandoffHistoryMapper = Callable[
238
[list[TResponseInputItem]],
239
list[TResponseInputItem]
240
]
241
```
242
243
Usage example:
244
245
```python
246
from agents import Agent, RunConfig, nest_handoff_history
247
248
# Global configuration
249
config = RunConfig(
250
nest_handoff_history=True,
251
handoff_history_mapper=nest_handoff_history
252
)
253
254
# Run with history management
255
result = Runner.run_sync(
256
triage_agent,
257
"I need help",
258
run_config=config
259
)
260
261
# Per-handoff configuration
262
specialist_handoff = handoff(
263
specialist_agent,
264
nest_handoff_history=True
265
)
266
```
267
268
### Conversation History Wrappers
269
270
Global functions for managing conversation history presentation.
271
272
```python { .api }
273
def get_conversation_history_wrappers() -> tuple[str, str]:
274
"""
275
Get conversation history wrappers.
276
277
Returns:
278
- tuple[str, str]: Opening and closing wrapper strings
279
"""
280
281
def set_conversation_history_wrappers(opening: str, closing: str) -> None:
282
"""
283
Set conversation history wrappers.
284
285
Parameters:
286
- opening: Opening wrapper string
287
- closing: Closing wrapper string
288
"""
289
290
def reset_conversation_history_wrappers() -> None:
291
"""Reset conversation history wrappers to defaults."""
292
```
293
294
Usage example:
295
296
```python
297
from agents import set_conversation_history_wrappers, reset_conversation_history_wrappers
298
299
# Customize history presentation
300
set_conversation_history_wrappers(
301
opening="<conversation_history>",
302
closing="</conversation_history>"
303
)
304
305
# Run agents with custom wrappers
306
result = Runner.run_sync(agent, "Hello")
307
308
# Reset to defaults
309
reset_conversation_history_wrappers()
310
```
311
312
## Advanced Handoff Patterns
313
314
### Conditional Handoffs
315
316
Enable or disable handoffs based on context.
317
318
```python
319
from agents import Agent, handoff
320
321
def should_enable_expert(ctx):
322
"""Enable expert handoff only for premium users."""
323
return ctx.context.user.tier == "premium"
324
325
expert_agent = Agent(
326
name="Expert Agent",
327
instructions="Provide expert assistance."
328
)
329
330
expert_handoff = handoff(
331
expert_agent,
332
is_enabled=should_enable_expert
333
)
334
335
agent = Agent(
336
name="Assistant",
337
handoffs=[expert_handoff]
338
)
339
```
340
341
### Typed Handoff Inputs
342
343
Use Pydantic models for structured handoff parameters.
344
345
```python
346
from agents import Agent, handoff
347
from pydantic import BaseModel
348
349
class EscalationInput(BaseModel):
350
reason: str
351
priority: str
352
user_id: str
353
354
escalation_agent = Agent(
355
name="Escalation Handler",
356
instructions="Handle escalated issues."
357
)
358
359
typed_handoff = handoff(
360
escalation_agent,
361
input_type=EscalationInput,
362
tool_description_override="Escalate issue with structured details"
363
)
364
365
agent = Agent(
366
name="Support Agent",
367
handoffs=[typed_handoff]
368
)
369
```
370
371
### Multi-Stage Workflows
372
373
Chain multiple agents with handoffs.
374
375
```python
376
from agents import Agent
377
378
# Define workflow stages
379
intake_agent = Agent(
380
name="Intake",
381
instructions="Gather initial information."
382
)
383
384
analysis_agent = Agent(
385
name="Analysis",
386
instructions="Analyze the gathered information.",
387
handoffs=[intake_agent] # Can go back if needed
388
)
389
390
resolution_agent = Agent(
391
name="Resolution",
392
instructions="Provide final resolution.",
393
handoffs=[analysis_agent] # Can request more analysis
394
)
395
396
# Start with analysis stage
397
analysis_agent.handoffs.append(resolution_agent)
398
399
# Entry point
400
entry_agent = Agent(
401
name="Entry",
402
instructions="Start the workflow.",
403
handoffs=[intake_agent]
404
)
405
406
result = Runner.run_sync(entry_agent, "I need help with...")
407
```
408
409
### Handoff with History Filtering
410
411
Filter conversation history based on content.
412
413
```python
414
from agents import handoff, HandoffInputData
415
416
async def filter_history(data: HandoffInputData) -> HandoffInputData:
417
"""Keep only relevant messages."""
418
# Filter based on content, age, or other criteria
419
recent_items = data.input_history[-10:] # Last 10 messages
420
return data.clone(input_history=tuple(recent_items))
421
422
specialist = Agent(
423
name="Specialist",
424
instructions="Handle specialized requests."
425
)
426
427
filtered_handoff = handoff(
428
specialist,
429
input_filter=filter_history
430
)
431
432
agent = Agent(
433
name="General Agent",
434
handoffs=[filtered_handoff]
435
)
436
```
437
438
### Handoff Callbacks
439
440
Execute logic when handoffs occur.
441
442
```python
443
from agents import handoff
444
import logging
445
446
logger = logging.getLogger(__name__)
447
448
async def log_handoff(ctx, agent, input_data):
449
"""Log handoff events for analytics."""
450
logger.info(f"Handoff to {agent.name}", extra={
451
"from_agent": ctx.agent.name,
452
"to_agent": agent.name,
453
"user_id": ctx.context.user_id
454
})
455
456
expert = Agent(name="Expert", instructions="Expert assistance")
457
458
monitored_handoff = handoff(
459
expert,
460
on_handoff=log_handoff
461
)
462
```
463
464
## Handoff Best Practices
465
466
1. **Clear Descriptions**: Provide clear handoff tool descriptions to help the LLM understand when to use each handoff
467
2. **Avoid Cycles**: Be careful with bidirectional handoffs to prevent infinite loops
468
3. **Filter History**: Use input filters to reduce token usage and improve context relevance
469
4. **Use Nesting**: Enable `nest_handoff_history` for long conversations to manage context size
470
5. **Conditional Handoffs**: Use `is_enabled` to dynamically control handoff availability
471
6. **Monitor Handoffs**: Use `on_handoff` callbacks for logging and analytics
472