# How to Implement Multi-Agent Workflows with Agent-to-Agent Delegation in openai-agents-python

> Learn how to implement multi-agent workflows with agent-to-agent delegation in openai-agents-python. Master handoff and context transfer for seamless agent collaboration.

- Repository: [OpenAI/openai-agents-python](https://github.com/openai/openai-agents-python)
- Tags: how-to-guide
- Published: 2026-04-17

---

**Use the `handoff()` helper to expose one agent as a tool on another, then wire the `Runner` with `nest_handoff_history=True` to automatically transfer context and conversation state between agents.**

The `openai-agents-python` SDK provides a first-class **handoff** mechanism for building multi-agent workflows. By treating agents as callable tools, you can implement complex delegation patterns such as triage → specialist → resolution while preserving conversation context across agent boundaries.

## Core Handoff Concepts

The delegation system relies on several interconnected components defined in [`src/agents/handoffs/__init__.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/__init__.py) and [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py):

- **`Handoff` dataclass** (lines 42‑56): Describes the handoff tool, including the name, description, JSON schema, and the function that creates the target agent instance.
- **`handoff()` helper** (lines 88‑135): Validates input schemas, builds the `Handoff` object, and wires the tool into the model’s tool list.
- **`HandoffInputData`** (lines 44‑71): A dataclass that carries the transcript, pre‑handoff items, new items, and optional `input_filter` results to the next agent.
- **`nest_handoff_history`** (history.py lines 71‑104): Automatically injects a summarized conversation snippet into the next agent’s input, preventing token bloat while maintaining context.
- **`input_filter`**: An optional callable (lines 27‑34) that prunes or reshapes items before the receiving agent sees them, useful for privacy or deduplication.

## Step 1: Define Specialized Agents

Create distinct `Agent` instances, each with its own tools and system instructions. In [`src/agents/agent.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/agent.py), the `Agent` class accepts a `tools` list that can include both function tools and handoff tools.

```python

# agents.py

from agents import Agent, function_tool

# -------------------------------------------------

# Triage agent – decides which specialist to call

# -------------------------------------------------

def triage_logic(request: str) -> str:
    """Simple rule‑based triage: keywords → specialist name."""
    if "billing" in request.lower():
        return "billing"
    if "technical" in request.lower():
        return "tech"
    return "general"

triage = Agent(
    name="triage",
    description="Routes the user to the appropriate specialist.",
    tools=[
        function_tool(triage_logic, name="triage", description="Return the specialist name")
    ],
)

# -------------------------------------------------

# Billing specialist

# -------------------------------------------------

billing = Agent(
    name="billing",
    description="Handles billing‑related questions.",
    tools=[],
)

# -------------------------------------------------

# Technical specialist

# -------------------------------------------------

technical = Agent(
    name="technical",
    description="Handles technical support queries.",
    tools=[],
)

# -------------------------------------------------

# General fallback agent

# -------------------------------------------------

general = Agent(
    name="general",
    description="Catches all other requests.",
    tools=[],
)

```

## Step 2: Configure Agent-to-Agent Handoffs

Use the `handoff()` helper (defined in [`src/agents/handoffs/__init__.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/__init__.py) lines 88‑135) to create delegation tools. Then attach these tools to the orchestrator agent (triage).

```python

# handoffs.py

from agents import handoff
from agents import triage, billing, technical, general

# -------------- Billing handoff --------------

billing_handoff = handoff(
    billing,
    tool_name_override="transfer_to_billing",
    nest_handoff_history=True,          # include concise transcript summary

)

# -------------- Technical handoff --------------

tech_handoff = handoff(
    technical,
    tool_name_override="transfer_to_technical",
    nest_handoff_history=True,
)

# -------------- General handoff (fallback) --------------

general_handoff = handoff(
    general,
    tool_name_override="transfer_to_general",
    nest_handoff_history=True,
)

# Attach the handoffs as *tools* on the triage agent

triage.tools.extend([billing_handoff, tech_handoff, general_handoff])

```

When the LLM calls `transfer_to_billing`, the SDK executes `Handoff._invoke_handoff`, returns the target `Agent` object, and automatically prepares the next turn’s input using `HandoffInputData`.

## Step 3: Execute the Multi-Agent Workflow

Instantiate `Runner` (from [`src/agents/run.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/run.py)) with `RunConfig` to control history nesting and safety limits.

```python

# run_workflow.py

from agents import Runner, RunConfig
from agents import triage  # import the triage agent defined earlier

from handoffs import billing_handoff, tech_handoff, general_handoff

run_cfg = RunConfig(
    nest_handoff_history=True,  # Enable global history nesting

    max_turns=10,               # Prevent infinite loops

)

runner = Runner(agent=triage, config=run_cfg)

# Example user request that triggers the billing specialist

result = runner.run("I have a question about my last invoice.")
print("Final response:", result.output)

```

**Execution flow:**

1. **Turn 1**: `triage` analyzes the request and generates a tool call `transfer_to_billing`.
2. **Turn 2**: Runtime executes the handoff, invokes `nest_handoff_history()` (from [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py) lines 71‑104) to summarize prior turns, and injects the summary into the `billing` agent’s input.
3. **Turn 3**: `billing` receives the summarized transcript plus the original user request, runs its logic, and returns the final answer.

## Advanced Customization Options

### Conditional Handoff with is_enabled

Control visibility of handoff tools using the `is_enabled` parameter (handled in [`src/agents/handoffs/__init__.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/__init__.py) lines 14‑23).

```python
def billing_enabled(ctx, agent):
    import datetime
    now = datetime.datetime.utcnow().hour
    return 9 <= now <= 17  # UTC business hours only

billing_handoff = handoff(
    billing,
    is_enabled=billing_enabled,
)

```

### Input Filtering for Privacy

Use `input_filter` (defined in [`src/agents/handoffs/__init__.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/__init__.py) lines 27‑34) to sanitize data before the next agent receives it.

```python
def strip_sensitive_data(input_data):
    filtered = tuple(
        item for item in input_data.new_items
        if not (hasattr(item, "content") and "ssn" in str(item.content))
    )
    return input_data.clone(new_items=filtered)

billing_handoff = handoff(
    billing,
    input_filter=strip_sensitive_data,
    nest_handoff_history=True,
)

```

### Custom History Summarization

Override the default mapper by passing a `history_mapper` callable to customize how conversation history is compressed.

```python
def bullet_summary(transcript):
    lines = [f"* {item['role']}: {item.get('content', '')}" for item in transcript]
    summary = "\n".join(lines) or "(no previous turns)"
    return [{
        "role": "assistant",
        "content": f"The conversation so far:\n{summary}",
    }]

billing_handoff = handoff(
    billing,
    nest_handoff_history=True,
    history_mapper=bullet_summary,
)

```

## Summary

- **Create separate agents** by instantiating `Agent` objects with distinct tool sets and instructions.
- **Expose handoff tools** using `handoff(target_agent, ...)` and optionally override `tool_name_override` for clearer LLM prompts.
- **Attach handoffs** to the orchestrator agent (e.g., triage) by extending its `tools` list.
- **Preserve context** by setting `nest_handoff_history=True` in `RunConfig` or individual handoffs, which invokes `nest_handoff_history()` from [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py).
- **Filter sensitive data** by providing an `input_filter` callable that returns a cloned `HandoffInputData` with sanitized items.
- **Control availability** using `is_enabled` to dynamically hide handoff tools based on runtime conditions like time of day or user permissions.
- **Execute workflows** by passing the entry agent to `Runner` with `RunConfig(max_turns=...)` to prevent infinite loops.

## Frequently Asked Questions

### How does conversation history transfer between agents?

The SDK automatically manages history transfer through the `nest_handoff_history` function in [`src/agents/handoffs/history.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/history.py). When enabled, it summarizes prior conversation turns using `default_handoff_history_mapper` and injects the summary into the receiving agent's input wrapped in `<CONVERSATION HISTORY>` markers. This prevents token bloat while ensuring the specialist agent retains necessary context.

### Can I prevent a handoff from appearing based on runtime conditions?

Yes. Pass an `is_enabled` parameter to the `handoff()` function—either a boolean or a callable receiving `RunContextWrapper` and the target `Agent`. If the callable returns `False`, the handoff tool is hidden from the LLM for that specific turn. This is implemented in [`src/agents/handoffs/__init__.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/__init__.py) and enables time-based routing or feature-flagging specific agents.

### What is the difference between input_filter and nest_handoff_history?

`nest_handoff_history` is a boolean flag that controls whether a summarized conversation snippet is prepended to the next agent's input using the history mapper. `input_filter` is an optional callable that receives the full `HandoffInputData` object—containing `new_items`, `pre_handoff_items`, and the transcript—and returns a modified version before the next agent receives it. Use `input_filter` to remove PII, deduplicate tool calls, or reshape payloads while keeping the full history intact in the session store.

### How do I debug a multi-agent workflow?

Set `max_turns` in `RunConfig` to prevent infinite loops during development. Inspect the `HandoffInputData` objects inside your `input_filter` functions to verify exactly what content passes between agents. Enable detailed logging around the `handoff()` helper and `Runner.run()` calls. Finally, review the source implementation in [`src/agents/handoffs/__init__.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/handoffs/__init__.py) and [`src/agents/run.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/run.py) to understand the exact orchestration logic and error handling paths.