An agent calling another agent
The LangGraph course builds multi-agent systems as an explicit graph of nodes. Pydantic AI's simplest multi-agent pattern is much more direct: one agent's tool function just calls another agent, since agents are plain Python objects with a run_sync method, nothing special about calling one from inside a tool.
from dotenv import load_dotenvfrom pydantic_ai import Agent, RunContext
load_dotenv()
joke_agent = Agent( "google:gemini-3.5-flash-lite", system_prompt="You write one short joke about the given topic.",)
main_agent = Agent( "google:gemini-3.5-flash-lite", system_prompt=( "You are a helpful assistant. Use the tell_joke tool whenever " "the user asks for a joke." ),)
@main_agent.tooldef tell_joke(ctx: RunContext[None], topic: str) -> str: """Delegate to the joke-writing agent for a topic.
Args: topic: What the joke should be about. """ result = joke_agent.run_sync(topic, usage=ctx.usage) return result.output
def main() -> None: result = main_agent.run_sync("Tell me a joke about cats.") print("Output:", result.output) print("Combined usage (main + delegated):", result.usage)From main_agent's point of view, tell_joke is just another tool with a string in, a string out. It has no idea the "tool" is secretly a whole other LLM call. This is the cleanest way to give an agent a specialist, a summarizer, a translator, a fact-checker, without merging all that logic and prompting into one mega-agent.
Why pass usage=ctx.usage
ctx.usage is the running usage total for the outer run. Passing it into the delegated agent's run_sync means the sub-agent's token usage accumulates into the same total, instead of being invisible to whoever's tracking cost on the outer run. Lesson 13 covers usage tracking and limits in depth; here, just note that passing usage= through is what keeps delegated calls accounted for.
When to reach for LangGraph instead
This pattern is great for a handful of specialist calls with a clear caller/callee relationship. Once you need cycles, shared mutable state across many agents, or conditional branching between more than a couple of participants, LangGraph's explicit graph model, or Lesson 17's pydantic_graph, becomes the better fit. Delegation-as-a-tool is the "just call a function" option; a graph is the "model the control flow explicitly" option.
Checkpoint
- A tool function can call
another_agent.run_sync(...)directly; agents are plain objects, nothing special is needed to nest them. - Passing
usage=ctx.usageinto the nested call folds its token usage into the outer run's total. - This pattern suits a few specialist delegations; genuinely complex control flow between agents belongs in a graph instead (Lesson 17).
If anything here still feels unclear, ask before moving to Lesson 13.