Agents-as-Tools: Specialist Agents As Callable Tools
Sometimes you want to reuse a specialist agent without handing over the final answer.
In a travel assistant, the controller can call a budget specialist and a writing specialist. The specialists return narrow results. The controller still decides how to answer the user.
That is Agents-as-Tools.
One Sentence
Agents-as-Tools turns specialist takeover into controller calls specialist tools, so expertise is reusable while final-answer ownership stays with the controller.
What Breaks Without It
| Problem | What it looks like | Risk |
|---|---|---|
| All expertise in controller prompt | Simple | Prompt grows heavy |
| Specialist takes over | Professional | Conversation owner becomes unclear |
| Specialist output is unstructured | Flexible | Controller cannot consume it reliably |
What This Pattern Changes
| Who | Owns |
|---|---|
| Controller Agent | Decides when to call experts and owns final answer |
| Specialist Agent Tool | Receives narrow task and returns narrow result |
| Python | Wraps agent as tool, controls budget and trace |
Unlike Handoff, this is a call, not a takeover.
Walk Through One Trace
| Round | Controller action | Specialist result |
|---|---|---|
| 1 | Call math_agent for 7*6 |
42 |
| 2 | Call style_agent for one sentence |
The answer is 42. |
| 3 | final |
Controller returns final answer |
Flow
flowchart TD
U["User task"] --> C["Controller Agent"]
C -->|call tool| A["math_agent"]
C -->|call tool| B["style_agent"]
A --> C
B --> C
C --> O["Final answer"]
Code Walk
Wrap a specialist as a tool:
agent_as_tool(
AgentToolSpec(
name="math_agent",
description="Specialist agent for arithmetic questions.",
model=math_agent,
system_prompt="You are a math specialist. Return only the numeric answer.",
),
tracer=tracer,
)
The controller still runs ReAct:
out = run_react(controller, task="Compute 7*6 and present it nicely.", tools=tools, limits=RunLimits(max_steps=6), tracer=tracer)
Full example:
from __future__ import annotations
from pathlib import Path
from agent_patterns_lab.patterns.agents_as_tools import AgentToolSpec, agent_as_tool
from agent_patterns_lab.patterns.react import run_react
from agent_patterns_lab.runtime import MockLLM, RunLimits, ToolRegistry, Tracer
def main() -> None:
tracer = Tracer()
math_agent = MockLLM(["42"])
style_agent = MockLLM(["The answer is 42."])
tools = ToolRegistry(
[
agent_as_tool(
AgentToolSpec(
name="math_agent",
description="Specialist agent for arithmetic questions.",
model=math_agent,
system_prompt="You are a math specialist. Return only the numeric answer.",
),
tracer=tracer,
),
agent_as_tool(
AgentToolSpec(
name="style_agent",
description="Specialist agent for writing/formatting.",
model=style_agent,
system_prompt="You are a writer. Produce a single concise sentence.",
),
tracer=tracer,
),
]
)
controller = MockLLM(
[
'{"type":"tool","tool":"math_agent","args":{"task":"Compute 7*6"}}',
'{"type":"tool","tool":"style_agent","args":{"task":"Write a short sentence using the number 42."}}',
'{"type":"final","answer":"The answer is 42."}',
]
)
out = run_react(controller, task="Compute 7*6 and present it nicely.", tools=tools, limits=RunLimits(max_steps=6), tracer=tracer)
print(out)
trace_path = tracer.export_jsonl(Path(".traces") / "61_agents_as_tools.jsonl")
print(f"[trace] {trace_path}")
if __name__ == "__main__":
main()
Run:
UV_CACHE_DIR=.uv_cache PYTHONPATH=src uv run --no-sync python examples/61_agents_as_tools.py
Nearby Patterns
| Pattern | Final answer owner | Use when |
|---|---|---|
| Normal tool | Controller | Capability is deterministic |
| Agents-as-Tools | Controller | Capability needs a specialist agent |
| Handoff | Specialist | Specialist should take over |
| Manager-Worker | Manager | Work is assigned then synthesized |
When To Use It
- Specialist capability is reusable.
- Controller should keep final-answer ownership.
- Expert input/output can be contracted.
- Experts need separate budgets or permissions.
When Not To Use It
- A normal function is enough.
- Specialist needs multi-turn user conversation.
- Controller cannot assess specialist output.
- Too many experts confuse the controller.
Costs And Common Failures
| Failure | Symptom | Fix |
|---|---|---|
| Specialist too broad | Every expert can do everything | Narrow prompt and tools |
| Unusable output | Controller receives prose blob | Use structured output |
| Budget leak | One expert runs too long | Per-tool agent limits |
| Tool soup | Many experts exposed | Route or keep only high-value experts |
What To Read Next
Agents-as-Tools fits "expert helps but does not take over".
If the specialist should own the conversation, read Handoff. If a manager assigns workers, read Manager-Worker.