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
7 Agent as Tool
The ultimate composition pattern: agents that use other agents as tools.
Code Reference: code/v0.4/src/agentsilex/
agent_as_tool.pytool.pyagent.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 wrapperUnlike @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_toolKey points:
- Fresh session — Each tool call gets its own session (agent doesn’t see parent’s history)
- Context passed through — Shares the same context dict as parent
- 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
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 researcher7.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 |
cd code/v0.4You 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!