7  Agent as Tool

The ultimate composition pattern: agents that use other agents as tools.

Note

Code Reference: code/v0.4/src/agentsilex/

  • agent_as_tool.py
  • tool.py
  • agent.py

7.1 Handoff vs Agent as Tool

In Chapter 5, we saw handoffs that transfer control:

User → Main Agent → [handoff] → Specialist → User

Sometimes you want the main agent to stay in control and just get a result:

User → Main Agent → [tool: ask specialist] → Specialist → Main Agent → User
Pattern Control Flow Use Case
Handoff Transfers permanently “Let the expert handle this”
Agent as Tool Main agent orchestrates “Get info, then decide”

7.2 The generate_tool Decorator

First, we add a variant of @tool that lets us specify name and description (tool.py):

def generate_tool(tool_name, tool_description):
    def wrapper(func: Callable):
        schema = extract_function_schema(func)

        return FunctionTool(
            name=tool_name,
            description=tool_description,
            function=func,
            parameters_specification=schema.params_json_schema,
        )

    return wrapper

Unlike @tool, this decorator lets us override the tool’s name and description.

7.3 The agent_as_tool Function

Now we can convert any agent into a tool (agent_as_tool.py):

from agentsilex import Runner, Session
from agentsilex.function_tool import FunctionTool
from agentsilex.tool import generate_tool


def agent_as_tool(agent, tool_name: str, tool_description: str) -> FunctionTool:

    @generate_tool(tool_name, tool_description)
    def agent_tool(input: str, context) -> str:
        # Create an in-memory session for this tool call
        session = Session()

        # Run the agent with the input
        result = Runner(session, context).run(agent, input)

        return result.final_output

    return agent_tool

Key points:

  1. Fresh session — Each tool call gets its own session (agent doesn’t see parent’s history)
  2. Context passed through — Shares the same context dict as parent
  3. Returns string — Just the final output, like any other tool

7.4 Agent.as_tool() Method

Add a convenience method to Agent (agent.py):

class Agent:
    # ... existing code ...

    def as_tool(self, tool_name: str, tool_description: str) -> FunctionTool:
        from agentsilex.agent_as_tool import agent_as_tool

        return agent_as_tool(self, tool_name, tool_description)

Now you can write agent.as_tool(...) directly.

7.5 Example: Research Pipeline

from agentsilex import Agent, Runner, Session, tool

# Research agent with web search
@tool
def search_web(query: str) -> str:
    """Search the web for information."""
    # In real code, call a search API
    return f"Search results for '{query}': [Article 1, Article 2, Article 3]"

researcher = Agent(
    name="researcher",
    model="gpt-4o",
    instructions="You research topics thoroughly. Use search_web to find information.",
    tools=[search_web],
)

# Writer agent
writer = Agent(
    name="writer",
    model="gpt-4o",
    instructions="You write clear, engaging content based on provided information.",
    tools=[],
)

# Orchestrator that uses both as tools
orchestrator = Agent(
    name="orchestrator",
    model="gpt-4o",
    instructions="""You coordinate research and writing tasks.
    1. Use ask_researcher to gather information on a topic
    2. Use ask_writer to create content from the research
    3. Combine and present the final result""",
    tools=[
        researcher.as_tool(
            "ask_researcher",
            "Ask the research agent to find information on a topic"
        ),
        writer.as_tool(
            "ask_writer",
            "Ask the writer agent to create content from information"
        ),
    ],
)

# Run the orchestrator
session = Session()
runner = Runner(session)

result = runner.run(
    orchestrator,
    "Research AI agents and write a brief summary"
)
print(result.final_output)

7.6 What Happens

sequenceDiagram
    participant U as User
    participant O as Orchestrator
    participant R as Researcher
    participant W as Writer

    U->>O: "Research AI agents and write summary"
    O->>O: Calls ask_researcher("AI agents")
    O->>R: Input: "AI agents"
    R->>R: Calls search_web("AI agents")
    R->>O: "Research findings: ..."
    O->>O: Calls ask_writer(research results)
    O->>W: Input: research findings
    W->>O: "Summary: AI agents are..."
    O->>U: Final combined response

The orchestrator stays in control throughout, using specialist agents as tools.

7.7 Context Sharing

Note that context flows through:

@tool
def track_progress(step: str, context: dict) -> str:
    """Track research progress."""
    context.setdefault("steps", []).append(step)
    return f"Tracked: {step}"

# Both researcher and orchestrator can use context
researcher = Agent(
    name="researcher",
    tools=[search_web, track_progress],  # Can write to context
    # ...
)

# Orchestrator's context is shared with sub-agents
runner = Runner(session, context={"project": "AI Research"})
result = runner.run(orchestrator, "...")

print(runner.context["steps"])  # Steps from both orchestrator and researcher

7.8 When to Use Each Pattern

Scenario Pattern
User should talk to specialist Handoff
Main agent needs sub-results Agent as Tool
Parallel sub-tasks Multiple Agent as Tool calls
Deep specialist conversation Handoff
One-shot specialist query Agent as Tool

7.9 Key Design Decisions

Decision Why
Fresh session per call Sub-agent doesn’t see parent’s history
Context shared State can flow between agents
Custom name/description Better tool descriptions for LLM
Returns final_output Simple string, like any tool
TipPart II Complete!
cd code/v0.4

You now have full multi-agent capabilities:

  • Handoffs — Transfer control to specialists
  • Context — Share state across tools
  • Agent as Tool — Use agents as composable tools

The framework is now ~600 lines. Next: production features!