Skip to content
2 changes: 2 additions & 0 deletions docs/handoffs.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ handoff_obj = handoff(

When a handoff occurs, it's as though the new agent takes over the conversation, and gets to see the entire previous conversation history. If you want to change this, you can set an [`input_filter`][agents.handoffs.Handoff.input_filter]. An input filter is a function that receives the existing input via a [`HandoffInputData`][agents.handoffs.HandoffInputData], and must return a new `HandoffInputData`.

By default the runner now collapses the prior transcript into a single assistant summary message (see [`RunConfig.nest_handoff_history`][agents.run.RunConfig.nest_handoff_history]). The summary appears inside a `<CONVERSATION HISTORY>` block that keeps appending new turns when multiple handoffs happen during the same run. You can provide your own mapping function via [`RunConfig.handoff_history_mapper`][agents.run.RunConfig.handoff_history_mapper] to replace the generated message without writing a full `input_filter`. That default only applies when neither the handoff nor the run supplies an explicit `input_filter`, so existing code that already customizes the payload (including the examples in this repository) keeps its current behavior without changes. You can override the nesting behaviour for a single handoff by passing `nest_handoff_history=True` or `False` to [`handoff(...)`][agents.handoffs.handoff], which sets [`Handoff.nest_handoff_history`][agents.handoffs.Handoff.nest_handoff_history]. If you just need to change the wrapper text for the generated summary, call [`set_conversation_history_wrappers`][agents.handoffs.set_conversation_history_wrappers] (and optionally [`reset_conversation_history_wrappers`][agents.handoffs.reset_conversation_history_wrappers]) before running your agents.

There are some common patterns (for example removing all tool calls from the history), which are implemented for you in [`agents.extensions.handoff_filters`][]

```python
Expand Down
3 changes: 3 additions & 0 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ This version doesn’t introduce any visible breaking changes, but it includes n

- Added support for `RealtimeRunner` to handle [SIP protocol connections](https://platform.openai.com/docs/guides/realtime-sip)
- Significantly revised the internal logic of `Runner#run_sync` for Python 3.14 compatibility
- By default handoff history is now packaged into a single assistant message instead of exposing the raw user/assistant turns, giving downstream agents a concise, predictable recap
- The existing single-message handoff transcript now starts with "For context, here is the conversation so far between the user and the previous agent:" before the `<CONVERSATION HISTORY>` block, so downstream agents get a clearly labeled recap
- The existing single-message handoff transcript now starts with "For context, here is the conversation so far between the user and the previous agent:" before the `<CONVERSATION HISTORY>` block, so downstream agents get a clearly labeled recap

### 0.4.0

Expand Down
6 changes: 5 additions & 1 deletion docs/running_agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@ The `run_config` parameter lets you configure some global settings for the agent
- [`model_settings`][agents.run.RunConfig.model_settings]: Overrides agent-specific settings. For example, you can set a global `temperature` or `top_p`.
- [`input_guardrails`][agents.run.RunConfig.input_guardrails], [`output_guardrails`][agents.run.RunConfig.output_guardrails]: A list of input or output guardrails to include on all runs.
- [`handoff_input_filter`][agents.run.RunConfig.handoff_input_filter]: A global input filter to apply to all handoffs, if the handoff doesn't already have one. The input filter allows you to edit the inputs that are sent to the new agent. See the documentation in [`Handoff.input_filter`][agents.handoffs.Handoff.input_filter] for more details.
- [`nest_handoff_history`][agents.run.RunConfig.nest_handoff_history]: When `True` (the default) the runner collapses the prior transcript into a single assistant message before invoking the next agent. The helper places the content inside a `<CONVERSATION HISTORY>` block that keeps appending new turns as subsequent handoffs occur. Set this to `False` or provide a custom handoff filter if you prefer to pass through the raw transcript. All [`Runner` methods](agents.run.Runner) automatically create a `RunConfig` when you do not pass one, so the quickstarts and examples pick up this default automatically, and any explicit [`Handoff.input_filter`][agents.handoffs.Handoff.input_filter] callbacks continue to override it. Individual handoffs can override this setting via [`Handoff.nest_handoff_history`][agents.handoffs.Handoff.nest_handoff_history].
- [`handoff_history_mapper`][agents.run.RunConfig.handoff_history_mapper]: Optional callable that receives the normalized transcript (history + handoff items) whenever `nest_handoff_history` is `True`. It must return the exact list of input items to forward to the next agent, allowing you to replace the built-in summary without writing a full handoff filter.
- [`tracing_disabled`][agents.run.RunConfig.tracing_disabled]: Allows you to disable [tracing](tracing.md) for the entire run.
- [`trace_include_sensitive_data`][agents.run.RunConfig.trace_include_sensitive_data]: Configures whether traces will include potentially sensitive data, such as LLM and tool call inputs/outputs.
- [`workflow_name`][agents.run.RunConfig.workflow_name], [`trace_id`][agents.run.RunConfig.trace_id], [`group_id`][agents.run.RunConfig.group_id]: Sets the tracing workflow name, trace ID and trace group ID for the run. We recommend at least setting `workflow_name`. The group ID is an optional field that lets you link traces across multiple runs.
- [`trace_metadata`][agents.run.RunConfig.trace_metadata]: Metadata to include on all traces.

By default, the SDK now nests prior turns inside a single assistant summary message whenever an agent hands off to another agent. This reduces repeated assistant messages and keeps the full transcript inside a single block that new agents can scan quickly. If you'd like to return to the legacy behavior, pass `RunConfig(nest_handoff_history=False)` or supply a `handoff_input_filter` (or `handoff_history_mapper`) that forwards the conversation exactly as you need. You can also opt out (or in) for a specific handoff by setting `handoff(..., nest_handoff_history=False)` or `True`. To change the wrapper text used in the generated summary without writing a custom mapper, call [`set_conversation_history_wrappers`][agents.handoffs.set_conversation_history_wrappers] (and [`reset_conversation_history_wrappers`][agents.handoffs.reset_conversation_history_wrappers] to restore the defaults).

## Conversations/chat threads

Calling any of the run methods can result in one or more agents running (and hence one or more LLM calls), but it represents a single logical turn in a chat conversation. For example:
Expand Down Expand Up @@ -200,4 +204,4 @@ The SDK raises exceptions in certain cases. The full list is in [`agents.excepti
- Malformed JSON: When the model provides a malformed JSON structure for tool calls or in its direct output, especially if a specific `output_type` is defined.
- Unexpected tool-related failures: When the model fails to use tools in an expected manner
- [`UserError`][agents.exceptions.UserError]: This exception is raised when you (the person writing code using the SDK) make an error while using the SDK. This typically results from incorrect code implementation, invalid configuration, or misuse of the SDK's API.
- [`InputGuardrailTripwireTriggered`][agents.exceptions.InputGuardrailTripwireTriggered], [`OutputGuardrailTripwireTriggered`][agents.exceptions.OutputGuardrailTripwireTriggered]: This exception is raised when the conditions of an input guardrail or output guardrail are met, respectively. Input guardrails check incoming messages before processing, while output guardrails check the agent's final response before delivery.
- [`InputGuardrailTripwireTriggered`][agents.exceptions.InputGuardrailTripwireTriggered], [`OutputGuardrailTripwireTriggered`][agents.exceptions.OutputGuardrailTripwireTriggered]: This exception is raised when the conditions of an input guardrail or output guardrail are met, respectively. Input guardrails check incoming messages before processing, while output guardrails check the agent's final response before delivery.
17 changes: 16 additions & 1 deletion src/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,17 @@
input_guardrail,
output_guardrail,
)
from .handoffs import Handoff, HandoffInputData, HandoffInputFilter, handoff
from .handoffs import (
Handoff,
HandoffInputData,
HandoffInputFilter,
default_handoff_history_mapper,
get_conversation_history_wrappers,
handoff,
nest_handoff_history,
reset_conversation_history_wrappers,
set_conversation_history_wrappers,
)
from .items import (
HandoffCallItem,
HandoffOutputItem,
Expand Down Expand Up @@ -191,6 +201,11 @@ def enable_verbose_stdout_logging():
"StopAtTools",
"ToolsToFinalOutputFunction",
"ToolsToFinalOutputResult",
"default_handoff_history_mapper",
"get_conversation_history_wrappers",
"nest_handoff_history",
"reset_conversation_history_wrappers",
"set_conversation_history_wrappers",
"Runner",
"run_demo_loop",
"Model",
Expand Down
35 changes: 32 additions & 3 deletions src/agents/_run_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
UserError,
)
from .guardrail import InputGuardrail, InputGuardrailResult, OutputGuardrail, OutputGuardrailResult
from .handoffs import Handoff, HandoffInputData
from .handoffs import Handoff, HandoffInputData, nest_handoff_history
from .items import (
HandoffCallItem,
HandoffOutputItem,
Expand Down Expand Up @@ -998,8 +998,14 @@ async def execute_handoffs(
input_filter = handoff.input_filter or (
run_config.handoff_input_filter if run_config else None
)
if input_filter:
logger.debug("Filtering inputs for handoff")
handoff_nest_setting = handoff.nest_handoff_history
should_nest_history = (
handoff_nest_setting
if handoff_nest_setting is not None
else run_config.nest_handoff_history
)
handoff_input_data: HandoffInputData | None = None
if input_filter or should_nest_history:
handoff_input_data = HandoffInputData(
input_history=tuple(original_input)
if isinstance(original_input, list)
Expand All @@ -1008,6 +1014,17 @@ async def execute_handoffs(
new_items=tuple(new_step_items),
run_context=context_wrapper,
)

if input_filter and handoff_input_data is not None:
filter_name = getattr(input_filter, "__qualname__", repr(input_filter))
from_agent = getattr(agent, "name", agent.__class__.__name__)
to_agent = getattr(new_agent, "name", new_agent.__class__.__name__)
logger.debug(
"Filtering handoff inputs with %s for %s -> %s",
filter_name,
from_agent,
to_agent,
)
if not callable(input_filter):
_error_tracing.attach_error_to_span(
span_handoff,
Expand Down Expand Up @@ -1037,6 +1054,18 @@ async def execute_handoffs(
)
pre_step_items = list(filtered.pre_handoff_items)
new_step_items = list(filtered.new_items)
elif should_nest_history and handoff_input_data is not None:
nested = nest_handoff_history(
handoff_input_data,
history_mapper=run_config.handoff_history_mapper,
)
original_input = (
nested.input_history
if isinstance(nested.input_history, str)
else list(nested.input_history)
)
pre_step_items = list(nested.pre_handoff_items)
new_step_items = list(nested.new_items)

return SingleStepResult(
original_input=original_input,
Expand Down
12 changes: 11 additions & 1 deletion src/agents/extensions/handoff_filters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from __future__ import annotations

from ..handoffs import HandoffInputData
from ..handoffs import (
HandoffInputData,
default_handoff_history_mapper,
nest_handoff_history,
)
from ..items import (
HandoffCallItem,
HandoffOutputItem,
Expand All @@ -13,6 +17,12 @@

"""Contains common handoff input filters, for convenience. """

__all__ = [
"remove_all_tools",
"nest_handoff_history",
"default_handoff_history_mapper",
]


def remove_all_tools(handoff_input_data: HandoffInputData) -> HandoffInputData:
"""Filters out all tool items: file search, web search and function calls+output."""
Expand Down
Loading
Loading