---
title: "Agents as Tools: The as_tool() Pattern for Orchestration"
description: "Learn how to use agent.as_tool() to turn entire agents into callable tools for other agents. Master approval workflows, output extraction, and dynamic tool enabling for multi-agent orchestration."
canonical: https://callsphere.ai/blog/agents-as-tools-pattern-orchestration-openai-agents-sdk
category: "Learn Agentic AI"
tags: ["OpenAI", "Agents as Tools", "Orchestration", "Multi-Agent", "Python"]
author: "CallSphere Team"
published: 2026-03-14T00:00:00.000Z
updated: 2026-05-09T00:47:21.864Z
---

# Agents as Tools: The as_tool() Pattern for Orchestration

> Learn how to use agent.as_tool() to turn entire agents into callable tools for other agents. Master approval workflows, output extraction, and dynamic tool enabling for multi-agent orchestration.

## The Orchestration Problem

As your agent system grows, you will have specialized agents for different domains — one for billing, one for technical support, one for scheduling. The question becomes: how does a top-level agent delegate to these specialists?

The OpenAI Agents SDK offers two patterns for multi-agent coordination:

1. **Handoffs** — transfer control entirely to another agent (the original agent stops)
2. **Agents as Tools** — call another agent like a function, get back a result, and continue

The `as_tool()` pattern is ideal when your orchestrator agent needs to **gather information from multiple specialists** and synthesize a combined response, rather than handing off control entirely.

## Basic as_tool() Usage

The `as_tool()` method converts an agent into a tool that another agent can call:

```mermaid
flowchart LR
    INPUT(["User input"])
    AGENT["Agent
name plus instructions"]
    HAND{"Handoff to
another agent?"}
    SUB["Sub-agent
specialist"]
    GUARD{"Guardrail
passed?"}
    TOOL["Tool call"]
    SDK[("Tracing
OpenAI dashboard")]
    OUT(["Final output"])
    INPUT --> AGENT --> HAND
    HAND -->|Yes| SUB --> GUARD
    HAND -->|No| GUARD
    GUARD -->|Yes| TOOL --> AGENT
    GUARD -->|Block| OUT
    AGENT --> OUT
    AGENT --> SDK
    style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
    style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
    style SDK fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
    style OUT fill:#059669,stroke:#047857,color:#fff
```

```python
from agents import Agent, Runner

# Specialist agents
billing_agent = Agent(
    name="Billing Specialist",
    instructions="You are a billing expert. Answer questions about invoices, payments, and subscription plans. Be concise and precise.",
)

tech_agent = Agent(
    name="Tech Support",
    instructions="You are a technical support specialist. Help diagnose and resolve technical issues. Be concise.",
)

# Orchestrator uses specialists as tools
orchestrator = Agent(
    name="Customer Service Lead",
    instructions="You are the primary customer service agent. Use the billing and tech support tools to get specialist answers, then provide a unified response to the customer.",
    tools=[
        billing_agent.as_tool(
            tool_name="ask_billing",
            tool_description="Ask the billing specialist a question about invoices, payments, or subscriptions.",
        ),
        tech_agent.as_tool(
            tool_name="ask_tech_support",
            tool_description="Ask the tech support specialist to diagnose or resolve a technical issue.",
        ),
    ],
)

result = Runner.run_sync(
    orchestrator,
    "I was charged twice on my last invoice, and I also can't log into my dashboard.",
)
print(result.final_output)
```

When the orchestrator calls `ask_billing`, it runs the billing agent as a sub-agent, waits for its response, and receives the result as a tool output. The orchestrator then continues its own reasoning with that information.

## Custom Output Extraction

By default, `as_tool()` returns the sub-agent's `final_output` as the tool result. You can customize this with `custom_output_extractor` to pull specific data from the run result:

```python
from agents import Agent, RunResult

def extract_summary(run_result: RunResult) -> str:
    """Extract just the key findings from the research agent's output."""
    output = run_result.final_output
    # You could parse, truncate, or restructure the output here
    if len(output) > 500:
        return output[:500] + "... [truncated for brevity]"
    return output

research_agent = Agent(
    name="Deep Researcher",
    instructions="You perform thorough research on topics. Provide detailed analysis with sources.",
)

orchestrator = Agent(
    name="Report Writer",
    instructions="You write executive summaries. Use the research tool to gather information, then synthesize it into a concise report.",
    tools=[
        research_agent.as_tool(
            tool_name="research",
            tool_description="Perform deep research on a topic.",
            custom_output_extractor=extract_summary,
        ),
    ],
)
```

This is useful when the sub-agent produces verbose output but the orchestrator only needs key facts.

## Approval Workflows with needs_approval

Some agent actions require human approval before execution. The `needs_approval` parameter on function tools integrates human-in-the-loop checks into your pipeline:

```python
from agents import Agent, Runner, function_tool

@function_tool(needs_approval=True)
def execute_refund(order_id: str, amount: float, reason: str) -> str:
    """Process a customer refund."""
    return f"Refund of ${amount:.2f} processed for order {order_id}."

@function_tool(needs_approval=True)
def delete_account(customer_id: str, confirmation: str) -> str:
    """Permanently delete a customer account."""
    return f"Account {customer_id} has been permanently deleted."

agent = Agent(
    name="Account Manager",
    instructions="You help manage customer accounts. Refunds and deletions require approval.",
    tools=[execute_refund, delete_account],
)
```

When the agent tries to call a tool with `needs_approval=True`, the SDK raises an `ApprovalRequired` event that your application must handle. The tool only executes after explicit approval is granted:

```python
from agents import Runner

async def run_with_approval():
    result = await Runner.run(
        agent,
        "Please refund $50 for order ORD-12345 due to a shipping delay.",
    )
    # In practice, you would check result for approval requests
    # and implement your approval UI/logic here
    print(result.final_output)
```

You can also make `needs_approval` dynamic by passing a function instead of a boolean:

```python
from agents import RunContextWrapper

def approve_large_refunds(ctx: RunContextWrapper, tool_name: str, tool_input: dict) -> bool:
    """Only require approval for refunds over $100."""
    if tool_name == "execute_refund":
        return tool_input.get("amount", 0) > 100
    return False
```

## Dynamic Tool Enabling with is_enabled

Sometimes a tool should only be available under certain conditions. The `is_enabled` parameter on function tools lets you dynamically control tool availability:

```python
from agents import function_tool, RunContextWrapper
from dataclasses import dataclass

@dataclass
class UserContext:
    role: str
    is_verified: bool

def admin_only(ctx: RunContextWrapper[UserContext], tool_name: str) -> bool:
    """Only enable this tool for admin users."""
    return ctx.context.role == "admin"

@function_tool(is_enabled=admin_only)
def view_audit_log(days: int) -> str:
    """View the system audit log for the past N days."""
    return f"Showing audit log for the past {days} days..."
```

When `is_enabled` returns `False`, the tool is not included in the agent's tool list for that run. The agent does not even know the tool exists, so it will not try to call it or hallucinate about its availability.

## Combining Patterns for Production Orchestration

Here is a more complete example combining agents-as-tools with approval and dynamic enabling:

```python
from dataclasses import dataclass
from agents import Agent, Runner, function_tool, RunContextWrapper

@dataclass
class SessionContext:
    user_id: str
    role: str
    tier: str

# Specialist agents
analyst = Agent(
    name="Data Analyst",
    instructions="You analyze data and provide insights. Be concise and data-driven.",
)

writer = Agent(
    name="Report Writer",
    instructions="You write clear, professional reports based on provided data.",
)

def premium_only(ctx: RunContextWrapper[SessionContext], tool_name: str) -> bool:
    return ctx.context.tier == "premium"

@function_tool(is_enabled=premium_only)
def export_to_pdf(content: str) -> str:
    """Export content to a PDF document."""
    return "PDF generated and saved to /reports/output.pdf"

orchestrator = Agent(
    name="Report Orchestrator",
    instructions="You coordinate data analysis and report generation. Use the analyst for data questions, the writer for formatting, and the PDF export for premium users.",
    tools=[
        analyst.as_tool(
            tool_name="analyze_data",
            tool_description="Ask the data analyst to analyze a dataset or answer a data question.",
        ),
        writer.as_tool(
            tool_name="write_report",
            tool_description="Ask the report writer to format findings into a professional report.",
        ),
        export_to_pdf,
    ],
)

ctx = SessionContext(user_id="u_123", role="manager", tier="premium")
result = Runner.run_sync(
    orchestrator,
    "Analyze our Q1 sales data and write an executive summary report. Export it to PDF.",
    context=ctx,
)
print(result.final_output)
```

## Key Takeaways

- Use `agent.as_tool()` when you need to delegate and continue, not hand off entirely
- Set `custom_output_extractor` to control what the orchestrator sees from sub-agents
- Use `needs_approval` for sensitive operations that require human confirmation
- Use `is_enabled` to dynamically show or hide tools based on user context
- Combine these patterns for production-grade multi-agent orchestration systems

---

Source: https://callsphere.ai/blog/agents-as-tools-pattern-orchestration-openai-agents-sdk
