The standard introduction to multi-agent systems goes like this: one agent doing everything hits context limits, tasks interfere, nothing runs in parallel, so split the work across specialists and give them a supervisor to coordinate. Three patterns follow — network, supervisor, hierarchical — usually with a tidy diagram of boxes and arrows.
All of that is true and none of it is the interesting part. The interesting part is a question the diagrams skip: the supervisor in that picture is a model call, sitting between every pair of steps. Does it need to be?
For a lot of systems the answer is no, and the tell is easy to spot once you look for it.
The three patterns, briefly
Network. Every agent can hand off to every other. Nobody is in charge. Suits exploratory work where the path is genuinely unknown, and it is the hardest to reason about, because with n agents you have n² possible handoffs and no single place to look when it goes wrong.
Supervisor. One coordinator decides who acts next, based on current state. Workers do the work and report back. This is the pattern most systems actually use, and the rest of this article is about it.
Hierarchical. Supervisors managing supervisors: each team is a compiled subgraph nested as a node in a higher graph. Reasonable at genuine scale, and premature almost everywhere else.
The mechanic that makes the supervisor pattern work is a loop: worker finishes, control returns to the supervisor, supervisor decides what happens next, control goes to the next worker. Every arrow back to the centre is where the cost lives.
The question the diagrams skip
Look at what the supervisor is actually deciding in most pipelines.
Research finished, so analyse next. Analysis finished, so write next. Writing finished, so stop. That is not a judgement call. That is a lookup table, and you can write it in five lines of Python that always return the same answer in microseconds and cost nothing.
Now compare what the supervisor pattern does instead: it serialises the state into a prompt, sends it to a model, waits, parses a worker name out of the response, and hopes the model did not return something that is not on the list. Between every step. For a decision that was never in doubt.
Count it out. Three workers under a model supervisor runs supervisor, worker, supervisor, worker, supervisor, worker, supervisor — four coordination calls on top of the three that do the work. More than half the round trips exist to answer a question a dict could have answered.
The tell is easy: write out the routing decision as a table. If every row is determined by the previous step’s completion status, you have a state machine wearing a supervisor costume.
Here is what makes this more than a theoretical objection. The write-ups that teach the supervisor pattern often ship a showcase pipeline that quietly does the sensible thing instead. I read one recently that defined a status-based router function, added it to the graph as a node — and then wired the whole pipeline with plain sequential edges that never touch it. The router is dead code. The example works, it is well structured, and its orchestration is decorative.
That is not an author being careless. It is what happens when you build the thing: the deterministic path is obviously right for a fixed pipeline, so that is what gets written, even in an article arguing for the model-driven version.
When the supervisor does need to be a model
There is a real case, and it is narrower than the pattern’s popularity suggests. Use a model to route when:
- The next step depends on the content, not the status. “This research came back thin, go search again” is a judgement. “Research finished, analyse next” is not.
- The path is genuinely open. Debugging, exploratory research, anything where a fixed sequence would be a guess.
- The set of workers changes at runtime and no static table could enumerate the transitions.
Outside those, prefer deterministic edges and keep the model for the work itself. The useful default is a hybrid: fixed edges for the parts of the flow you can draw, a model decision at the one or two genuinely branching points, and a state field recording which branch was taken so you can debug it later.
This is the same shape as the argument in the previous piece on validating agent output: determinism where you can specify, judgement where you cannot, and never judgement by default, because judgement is the expensive one.
State is where it actually goes wrong
Two ways for agents to share information, and the choice has consequences well beyond style.
Shared state — a typed object where each agent owns named fields. Explicit, debuggable, and the one to reach for by default. The discipline that makes it work is assigning field ownership up front: this agent writes raw_research, that one writes structured_insights, and nobody writes another’s field. Without that rule you get agents silently clobbering each other and a bug that only appears under a particular ordering.
Message passing — every agent appends to a shared list and reads the whole history. More flexible, and it has a failure mode with a name. Each agent inherits everything every previous agent said, so context grows with every hop, and by the fifth agent the model is reading four agents’ worth of intermediate reasoning to do its own job. That is exactly the context distraction and rot problem, arriving through the architecture rather than through a long conversation.
The practical rule: message passing for genuinely conversational flows, typed state fields for pipelines. And for anything large — documents, datasets, page dumps — put the artifact somewhere else and pass a reference. State should carry identifiers, not payloads.
The part that is genuinely worth it
The feature that earns the framework, more than the patterns do, is the ability to interrupt a compiled graph before a specific node, persist the state, hand it to a human, and resume from exactly that point.
That turns human review from a wrapper around the system into a node inside it. A pipeline that pauses before the step that writes the report — or sends the email, or merges the branch — is a pipeline where the human gate sits where the judgement is needed, rather than at the end where everything has already happened.
It is the piece I would keep if I had to throw the rest away. Our own autonomous QA cycle is built on this: the graph is worth having because it can stop, not because it can route.
The pitfalls are all one pitfall
The standard list is four items: unbounded loops between agents, bloated state, ambiguous agent roles that confuse the router, and one failing agent taking down the pipeline. The fixes are an iteration cap, external storage with references, sharper role descriptions, and try/except in every node returning an error status the supervisor can route on.
All four are the same observation from different angles. The moment you split one agent into several, you have built a distributed system, and these are its ordinary hazards: no termination guarantee, unbounded message growth, ambiguous routing, and no failure isolation. Every one of them has a well-known answer in any other distributed system, and none of the answers are AI-specific.
Which suggests the useful test before adopting the pattern at all: are you prepared to operate a distributed system? Timeouts, retries, idempotency, partial-failure handling, and a way to see what happened. If the answer is no, a single well-prompted agent will beat a multi-agent setup on almost any task short enough to fit in one context window, and most tasks are.
What to take from it
- Write the routing table before you write the supervisor. If every transition follows from the previous step’s status, use edges. Keep the model for decisions that depend on content.
- Count the model calls. A supervisor between every pair of steps roughly doubles them. That is fine when it buys a real decision and waste when it does not.
- Give every state field an owner. One writer per field, decided up front. This is cheap and it prevents the ordering-dependent bugs that are worst to debug.
- Pass references, not payloads. Large artifacts live outside the state.
- Put the human gate at the judgement point. Interrupting before a specific node is the capability worth building the graph for.
The pattern is not wrong. It is just applied one level of complexity above where most problems live, and the cost of that shows up as latency and tokens rather than as a broken build, which is why it survives review.