When a multi-agent system helps, and when it is just more surface area
Four reasons to split one agent into several that hold up, the ones that do not, and the handoff, budget and tracing mechanics that decide the result.
Split one agent into several when the children differ in something the parent cannot hold at once: a separate context, work that genuinely runs in parallel, a different tool pool with different trust, or a different model tier and budget. Splitting for tidiness, or to mirror the people on your team, adds handoffs without removing any work.
The common version is a researcher, a planner and a writer. Each is a plausible job title and the diagram looks like architecture. What happens at run time is that the researcher's findings reach the writer as a summary the planner wrote, the planner's summary drops the two details that mattered, and the run pays for three system prompts and three tool-definition blocks instead of one.
Do I actually need multiple AI agents
Answer it by pricing the split before you make it. A child agent costs a system prompt and a full tool schema on every turn it takes, a serialization of its input, a deserialization of its output, and a boundary where information is compressed into prose and back. None of that is fatal. It is the fixed cost you are buying something with, and the question is what.
The something has to be a property of the child, not a property of your diagram. Four properties are worth the cost, and each one has a test you can apply to a design before writing it.
Reasons to split that hold up
The child handles material the parent should never carry
A support agent that answers one question about a 40-page contract does not need the contract in its window for the rest of the conversation. If the parent reads the PDF, those tokens sit in history and get re-sent on every subsequent turn, crowding out the earlier exchange that made the conversation coherent. A child that reads the document and returns three sentences plus a page number leaves the parent's window roughly where it was.
The test. Ask whether the material the child touches would still be in the parent's context ten turns later, and whether that would be wrong. If both are yes, the split is buying you something real. Context pressure is the most common reason a long conversation degrades, and context window management covers the alternatives to splitting.
The subtasks are genuinely parallel
Checking one claim against five vendor documents is five independent lookups. Run as five children with the same instructions and different inputs, the batch finishes in roughly the time of the slowest lookup. Run inside one agent, it is five sequential turns, each carrying the growing transcript of the previous four.
The test. Shuffle the subtasks in your head. If the answers do not change and there are more than about three of them, parallel children pay off. If subtask two needs subtask one's output, you have a sequence, and a sequence inside one agent avoids two handoffs per step.
The children need different tool pools and different trust
This is the strongest reason, because it is the only one where the split changes what is possible rather than what is efficient. A triage agent with read-only tools that delegates a refund to a child holding a single issue_refund tool behind an approval gate has made "refund without review" unreachable, not discouraged. The same shape covers untrusted input: a child that reads fetched web pages gets a tool pool with nothing in it that can write, so a prompt injection in the page has no reachable action to hijack.
The test. Name a tool you would be uncomfortable leaving attached during the other half of the work. If you can name one, split there. Tool count matters separately: selection accuracy falls as the list grows, which is the subject of how many tools an agent should have, and a split is one way to keep each pool small.
The subtasks want different model tiers and different budgets
Extraction over 200 rows and one synthesis pass over the results are different jobs with different error costs. Running the extraction on a small fast model and the synthesis on a larger one is a real saving, and it only exists if the two are separately addressable. Budgets follow the same line: a child doing bounded extraction can be capped at three or four turns, while the parent that has to hold a conversation cannot.
The test. Would you be willing to run this subtask on your cheapest model? If yes, and the parent needs your most expensive one, the boundary between them is already there and you are only making it explicit.
Reasons that do not hold up
Mirroring the org chart. Researcher, planner and writer describe how a document gets produced by people who cannot share a brain. Two model calls can share a context window, which is the constraint the human division of labor existed to work around.
The system prompt got long. Splitting a 2,000-word prompt into three 900-word prompts plus handoff text costs more tokens and adds two lossy boundaries. Long prompts are usually fixed by deleting instructions the model already follows, not by distributing them.
One agent per tool. A tool is already a scoped capability with a schema. Wrapping it in an agent adds a model call whose job is to decide to call the thing you were going to call anyway.
Single responsibility. The principle is about code you invoke deterministically and can test in isolation. A subagent call is a natural-language remote call with a nondeterministic callee, so the tidiness you get in the diagram is paid for in the one place software design does not usually have to pay: correctness of the interface. When the call graph is fixed in advance, the honest comparison is between a multi-agent system and a workflow, which agents versus workflows works through.
What crosses the handoff boundary
A delegation is a function call whose argument and return value are both prose unless you make them otherwise. Three decisions determine whether it holds up.
Pass raw values and identifiers into the child, not the parent's summary of them. A child reasoning about a compression it did not make cannot recover what the compression dropped, and it has no way to know something was dropped.
Ask for a structured return with a status separate from the content. A shape like this makes "nothing found" a value the parent can branch on:
{
"name": "contract_reader",
"model": "small-fast-model",
"tools": ["fetch_document", "search_document"],
"input": {
"document_id": "doc_8123",
"question": "What is the termination notice period?"
},
"returns": {
"found": "boolean",
"answer": "string",
"quote": "string",
"page": "integer"
},
"maxTurns": 4
}
Decide what the child cannot see. A child that inherits the whole parent transcript loses the context isolation that justified the split, and a child that inherits none of it will ask for things the user already said. The usable default is the task plus the specific facts the task needs, named explicitly in the delegation.
A failed child has to be reported, not absorbed
The failure that costs the most is the quiet one: a child that ran out of turns, or whose tool timed out, and returned its best guess in fluent prose. The parent has no way to distinguish that from an answer, so it writes a confident paragraph on top of nothing.
| What happens | How it looks to the parent | What to do about it |
|---|---|---|
| Child hits its turn cap | A partial answer with no marker | Return a status field; treat a truncated run as a failure, not a result |
| Tool call times out (30s typical) | "I was unable to retrieve the file" | Distinguish "found nothing" from "could not look" in the return shape |
| Child loops on the same tool | Slow run, plausible output | Cap turns and tool calls per child; alert on the cap being hit |
| Child misreads its own input | Fluent answer to a different question | Echo the interpreted task back in the return and check it |
The rule underneath all four rows: the parent's instructions must say what to do with a non-ok status, and the status must be a field rather than a sentence. Fluency is not evidence, and a model reading another model's prose has no better signal than you do.
Tracing and evaluating a run that fans out
A fan-out has no single transcript. The parent's history contains the delegation and the returned value, and everything that actually happened, the tool calls, the retries, the child's own reasoning, sits in a trace the parent never saw. Debugging that shape means keeping each child's trace attached to the parent run with the delegation arguments and returned value recorded on both sides, which is the practical difference between one run you can read and four you have to correlate by timestamp. The general technique is in debugging an AI agent, and the runtime features that make a fan-out legible sit in the category of agent orchestration platforms.
Evaluation splits the same way. Score each child in isolation against its own cases, since a child with a fixed input and a structured output is the easiest thing in the system to test. Then score the parent on two things the children cannot be blamed for: whether it delegated at the right moment, and whether it used what came back. A suite that only grades the final answer will tell you a regression exists and never which of four children caused it.
Cost deserves the same treatment. Attribute spend per child, because the usual surprise in a fan-out is one cheap-looking child that runs twelve turns on every request while the expensive-looking parent runs two.
Where this gets easier
Runtype treats subagent delegation as a configured relationship rather than a prompt convention: each child runs with its own tool pool, its own model and its own budget, so the read-only researcher and the child holding the refund tool are separate pools instead of separate paragraphs of instruction. Per-run limits are explicit (loopConfig.maxTurns from 1 to 100, maxToolCalls defaulting to 10 with a maximum of 100, and an optional cost ceiling per run), which is what stops a stuck child from consuming the parent's whole budget. Every child's trace stays attached to the parent execution with per-step inputs, outputs, tool calls and latency, and cost is recorded per execution, so the fan-out reads as one run. Eval suites can score a child on its own cases and the parent on the delegation itself, and a failure found in production can be promoted into a regression case against either one.
Frequently asked questions
- How many agents is too many?
- There is no number, but there is a signal: if you cannot say what each child holds that the others must not, you have one agent wearing costumes. Every child adds a system prompt and a tool schema on every one of its turns, plus a prose handoff in each direction. Start with one agent and split only when a specific child has its own context, its own tool pool, its own model tier or its own concurrency.
- Is a supervisor agent the same as a workflow?
- No. A supervisor decides at run time which child to call and how many times, so the call graph differs between two runs of the same input. A workflow fixes that graph in advance and only branches on conditions you wrote. If the order of your steps is known before the run starts, the workflow is cheaper to operate and much easier to test.
- What should a subagent return to its parent?
- A structured value with a status field separate from the content, not a paragraph. Give it an explicit shape (the answer, the evidence it rests on, and a boolean for whether anything was found), and make the failure case a value rather than a sentence. A parent that has to read prose to decide whether the child succeeded will sometimes decide wrong, and it will do so silently.