skip to content

In LlamaIndex AgentWorkflow, how does one agent hand off to another?

level: seniorimportance: should knowfreq 42%

answer

  1. Transfer is an injected tool call
  2. can_handoff_to defines allowed targets
  3. One Context shared by all agents
  4. Descriptions are the routing signal
  5. Nothing caps handoff count by default

basics

~20 s

AgentWorkflow gives each agent a handoff tool listing the agents named in its can_handoff_to. Calling it transfers control to that agent, which continues with the same shared Context and conversation history rather than starting fresh.

solid answer

~50 s

You build agents with `name`, `description`, `system_prompt` and `can_handoff_to=[...]`, then pass them to `AgentWorkflow(agents=[...], root_agent="...")`. The workflow injects a handoff tool into each agent whose allowed targets are exactly the names in its `can_handoff_to`. When the model calls it, the named agent becomes the active one and keeps going against the same `Context` — same chat history, same shared state — so nothing is re-derived. Routing quality therefore depends almost entirely on each agent's `description`, because that is what the handing-off model reads. Shared state is set up with `initial_state` and surfaced to the agents through a state prompt, and tools can read or mutate it by taking a `Context` parameter. The failure modes are ping-pong between two agents that each think the other owns the task, and a specialist inheriting a long history that pulls it off-brief. There is no built-in handoff cap, so bound it yourself with a counter in state or the workflow timeout.

code

python · 25 lines
python
import asyncio
from llama_index.core.agent.workflow import AgentWorkflow, FunctionAgent

researcher = FunctionAgent(
    name="researcher",
    description="Finds and cites source material. Cannot write prose.",
    system_prompt="Collect facts, store them, then hand off to writer.",
    llm="openai/gpt-4o-mini",
    can_handoff_to=["writer"],
)

writer = FunctionAgent(
    name="writer",
    description="Turns collected facts into finished prose. Does no research.",
    system_prompt="Write from the collected facts only.",
    llm="openai/gpt-4o-mini",
)

workflow = AgentWorkflow(
    agents=[researcher, writer],
    root_agent="researcher",
    initial_state={"facts": []},
)

asyncio.run(workflow.run(user_msg="Write a short brief on sea otters."))

go deeper

for a junior

Know the vocabulary: agents carry a name, a description and a can_handoff_to list, and AgentWorkflow names a root_agent that starts the run.

for a middle

Explain that handoff is an injected tool call, that all agents share one Context so history and state carry over, and that descriptions are what the handing-off model reads.

for a senior

Show you have operated it: diagnose ping-pong by restricting the handoff graph, keep specialists on brief by moving the payload into shared state, and add your own transfer cap since none exists.

for a principal

Own the choice itself — LLM-decided routing buys adaptability and costs a reasoning turn per decision, so defend when a deterministic workflow with an agent inside one step is the better architecture.

## The construct `AgentWorkflow` composes several agents into one run. Each participating agent is configured with: - `name` — the identifier other agents hand off to. - `description` — what this agent is for. This is the routing signal. - `system_prompt` — how this agent behaves once it is active. - `tools` — its own tools. - `can_handoff_to` — the list of agent names it is allowed to transfer to. The workflow is constructed with the agent list, a `root_agent` naming who starts, and optionally `initial_state` for shared data. At wiring time each agent receives an extra handoff tool whose permitted targets are drawn from its `can_handoff_to`. A handoff is therefore an ordinary tool call the model chooses to make — not a supervisor deciding for it. ## What transfers Control transfers; context does not reset. All agents in the workflow share one `Context`, so the incoming agent sees the conversation so far and any state written by its predecessor. That is the point — a research agent gathers material and a writing agent uses it without re-retrieving. Events emitted during the run identify which agent is currently active, so your logging and UI can attribute output correctly rather than presenting one undifferentiated stream. Shared state is the second channel. `initial_state` seeds a dict; a state prompt injects a rendered view of it into each agent's prompt so agents can see the current values; and tools that declare a `Context` parameter can read and write it. This is how you pass structured artifacts — a draft, a plan, a list of chosen sources — without stuffing them into chat messages. ## Where it goes wrong **Ping-pong.** Two agents whose descriptions overlap hand the task back and forth, each believing the other owns it. Every bounce is a full LLM turn plus prompt, so cost climbs with no progress. Fix by making descriptions mutually exclusive, restricting `can_handoff_to` so the graph is not fully connected, and giving one agent explicit ownership of finishing. **History poisoning.** The incoming specialist inherits everything, including the previous agent's reasoning and failed attempts. A narrow agent given a long, meandering history often drifts off its brief. Mitigate by keeping the durable handoff payload in shared state and instructing each agent, in its system prompt, to work from that state rather than from the transcript. **Unbounded loops.** There is no built-in cap on handoff count. Your backstops are the workflow-level timeout and your own guard — a counter in shared state, incremented by a tool or a step, that makes agents refuse further transfers past a threshold. Add it before production, not after the first runaway bill. **Description drift.** Because routing is prompt-driven, changing one agent's description silently changes another's behaviour. Treat descriptions as interface contracts and review them together. ## When AgentWorkflow is the wrong shape Handoff is LLM-decided routing. If the sequence is actually known — retrieve, then summarise, then check — a custom Workflow with typed events gives deterministic control flow, cheaper execution, and a testable graph, and you can still put an agent inside a single step where genuine judgment is needed. Use `AgentWorkflow` when who acts next genuinely depends on what was found; use explicit steps when it does not. Interviewers ask this to see whether you reach for a multi-agent construct by default or because the problem needs it. ## Operating it Stream the run and record, per event, which agent produced it and which tool it called; a transcript without agent attribution is nearly useless when debugging routing. Keep per-agent tool lists small — an agent that can do everything has no reason to hand off. And test routing on its own: fixed inputs that should each land on a specific agent, asserted against the active-agent field, so a description edit that breaks routing fails in CI rather than in production.

  • Two agents keep handing the task back and forth. What do you change first?
    Restrict the graph before touching prompts: remove the return edge from `can_handoff_to` so the transfer cannot bounce. Then make the descriptions mutually exclusive and name one agent as the one that must produce the final answer. Add a handoff counter in shared state as a hard backstop, since nothing caps transfers by default and every bounce is a paid LLM turn.
  • How do you pass a structured artifact between agents without relying on chat history?
    Put it in shared state. Seed the shape with `initial_state`, let tools that take a `Context` parameter read and write it, and rely on the state prompt to show each agent the current values. The receiving agent's system prompt should tell it to work from that state rather than reconstructing intent from the transcript, which is what keeps a specialist on brief after inheriting a long history.
  • When would you not use AgentWorkflow for a multi-step task?
    When the sequence is known in advance. Handoff is LLM-decided routing and you pay a reasoning turn for each decision; if the flow is retrieve, then summarise, then verify, a custom Workflow with typed events gives deterministic control flow, lower cost, and a graph you can test. You can still embed an agent inside one step where genuine judgment is required.

saying these in an interview costs you the question

  • Thinks a supervisor component routes rather than the agent's own tool call
  • Assumes the receiving agent starts with a clean history
  • Believes handoff count is capped by default
  • Ignores that agent descriptions drive routing accuracy
  • Reaches for multi-agent handoff when the sequence is fixed

context