All posts
Fundamentals

Agent as a tool, calling one agent from another

A workflow works when you know the order. When you do not, the orchestrator has to pick the specialist at runtime, and the cleanest way to let it is one you already know: make each specialist a tool.

The orchestrator has always chosen among tools. Make a research agent look like a tool, and the orchestrator can call it exactly as it calls search_web, deciding at runtime whether this request needs research, code, both, or neither.

An agent is already tool-shaped

A tool takes an input and returns a result. So does an agent: give it a task, get back an answer. The match is exact, which is what makes this pattern so clean.

AgentTool wraps an agent so it presents as a tool. Its name and description tell the orchestrator what the specialist does, and calling it runs the specialist.

class AgentTool(BaseTool):
    """Wraps a whole agent so the orchestrator can call it as a tool."""

    def __init__(self, agent, name, description):
        self.agent = agent
        self.name = name                 # e.g. "research_agent"
        self.description = description    # "Researches a topic and returns a summary"

    async def execute(self, context, task: str) -> str:
        result = await self.agent.run(task)     # run the specialist
        return result.output                    # return only its answer

That is the same BaseTool interface from earlier in this series. The orchestrator sees a research agent and a coding agent as two more tools in its list, and picks between them the way it picks any tool, from their descriptions.

The key idea: context isolation

The most important line in that wrapper is the last one: it returns only the specialist's answer. That is context isolation, and it is what makes agent-as-tool more than a fancy function call.

Watch a research agent run three web searches, read three pages, and reason across them to produce a summary. In a shared-context arrangement, all of that, three searches, three pages, the reasoning, would pile into the orchestrator's context and stay there. With agent-as-tool, the research happens in the specialist's own context, and only the summary crosses back.

research_agent's context:      3 searches + 3 pages + reasoning   (stays there)
orchestrator's context:        "Summary: AI agent adoption is..."  (only this returns)

The orchestrator stays lean. It never sees the specialist's mess, only its conclusion, the same way a manager gets a report instead of watching every keystroke. The heavy context lives and dies inside the specialist.

The specialist's searches and pages stay in its own context. Only its answer returns to the orchestrator.The specialist's searches and pages stay in its own context. Only its answer returns to the orchestrator.

Why isolation is the whole point

Context isolation solves the problem that would otherwise sink a multi-agent system: context bloat.

Without it, every specialist's working detail accumulates in one shared window, and a system with a few busy agents overflows it fast, the space problem from the memory chapter, multiplied by the number of agents. With isolation, each agent carries only its own working set, and the orchestrator carries only the summaries. The system scales because no single context holds everything.

Isolation also keeps each agent focused. A research agent that never sees the coder's context is not distracted by it. Each specialist reasons over exactly what its job needs and nothing else.

A worked example

Give the orchestrator two specialists and a request that needs one of them.

orchestrator = Agent(
    tools=[
        AgentTool(research_agent, "research", "Research a topic, return a summary"),
        AgentTool(coding_agent,   "code",     "Write and test code, return the result"),
    ],
    instructions="Route each request to the right specialist.",
)

await orchestrator.run("What are the latest trends in AI agents?")
# orchestrator calls research; coding_agent is never invoked

The orchestrator read the request, saw that it needed research and not code, and called the research specialist, exactly as it would choose between two ordinary tools. The coding agent sat idle. A different request, "write me a script to parse this log", would have gone the other way. The model chose at runtime, which is the whole reason to use this pattern over a fixed workflow.

The orchestrator routes each request to the right specialist, chosen at runtime from their descriptions.The orchestrator routes each request to the right specialist, chosen at runtime from their descriptions.

What to take from this

  • Make a specialist callable by wrapping it as a tool. An agent already takes an input and returns a result, so it fits the tool interface exactly, and the orchestrator picks specialists at runtime.
  • Context isolation is the point: the specialist works in its own context and returns only its answer, so the orchestrator stays lean and each agent stays focused.
  • Isolation is what lets a multi-agent system scale. No single context holds everything, so the system does not drown in the combined working detail of every agent.

In this pattern the orchestrator stays in charge, calling specialists and getting results. Sometimes a specialist should take over the whole conversation instead, not report back but own the rest of the task. That is a different pattern, and the next post builds it.