devjoinedthechat / writing

Agents / Architecture

Agent Patterns Are Sorted by the Wrong Axis

Pattern catalogs split agents into single-agent cognition and multi-agent orchestration. The failures that reach production split along other lines: who picks the next step, what a restart runs again, and where authority changes hands.

Oct 202610 min read

One sentence in LangGraph’s documentation explains a bug many teams ship: when a paused run resumes, “the runtime restarts the entire node from the beginning”.

Put a model call and a human approval in the same node and follow it through. The model drafts a refund of €240 for a duplicated March charge. A reviewer sees €240 and approves. The run resumes, the node restarts, the model drafts again and proposes €720, and the gate hands back the approval it already holds. The customer receives €720, and on LangGraph 1.2 it takes a dozen lines to reproduce.

Pattern catalogs often file the human gate under a single agent’s internal cognition, next to reflection and planning. Nothing that went wrong here was cognition. A paused run was stored, resumed and partly executed again, and an approval stayed attached to a step instead of to the call it was given for. That is the trouble with sorting patterns by topology: it tells you how to draw the boxes, and nothing about where the system breaks.

The catalog, and where its split leaks #

A typical catalog has ten entries in two layers. The single-agent layer holds the tool-calling loop (ReAct), reflection, plan-and-execute and the human-in-the-loop gate. The multi-agent layer holds orchestrator-worker, the sequential pipeline, fan-out/fan-in, debate, handoff and swarm. Every entry describes something real. The layers do not hold up:

  • Plan-and-execute is orchestrator-worker with one worker.
  • Reflection is generator-critic with one model playing both parts.
  • The human gate is not reasoning at all. It is a durable pause plus an authorization, and it fails the way pauses and authorizations fail.
  • Swarm is usually defined as a flat peer-to-peer mesh. OpenAI’s Swarm, the library that made the word popular, is agents and handoffs inside one client-side run loop. Its README calls it experimental and educational, and the OpenAI Agents SDK has replaced it.

Three questions sort the same ten patterns in a way that predicts failures:

  1. Who picks the next step, your code or the model?
  2. What runs again after a restart, and what does it touch?
  3. Where does authority change hands, and what text crosses with it?

Code decides, or the model decides #

Anthropic’s Building effective agents draws the line that matters most. Workflows are “systems where LLMs and tools are orchestrated through predefined code paths”. Agents are systems where LLMs “dynamically direct their own processes and tool usage”. Sorted that way:

  • Code picks the next step in the sequential pipeline, in fan-out/fan-in with fixed branches, and at the human gate.
  • The model picks it in the tool-calling loop, in a handoff, in an orchestrator that splits tasks on the fly, and in a swarm.
  • Reflection, debate and plan-and-execute go either way, depending on whether code or the model decides when to stop and what comes next.

Every step the model picks is control flow you did not write and cannot review in advance. That doesn’t make the model-driven patterns wrong. It changes what you owe them: a step budget and a stop condition enforced in code, a trace of every decision, and tools scoped to what the current step needs. The same article recommends “finding the simplest solution possible, and only increasing complexity when needed”. In practice that means moving each decision into code as soon as you can write it down.

The tool-calling loop is the one pattern every agent uses. Native tool-calling APIs have replaced ReAct’s text format, and the loop kept both of its habits: it repeats writes after a timeout, and it stops when the model says so. Plan-and-execute only helps with the first if the plan is data, with each step stored as a record and a status. A plan held only in the model’s context is a list the model can rewrite, including steps that already ran.

Code-driven patterns fail in a different place. The catalog’s own pipeline example runs document ingestion, then PII masking, then legal review. If the masking stage is a model, the pipeline masks personal data most of the time, and at volume the rest adds up. Put the control in code you can test, measure its recall, and let a model add a second pass.

PatternWho picks the next stepWhere it breaksWhat holds it
Tool-calling loopModelRepeats a write after a timeout, or never stopsStep budget and stop condition in code; keyed writes
ReflectionModel, or a fixed round countTalks itself out of a right answerCritique against tests, schemas or tool errors
Plan-and-executeModel plans, code runs the planA re-plan repeats a step that already ranSteps stored as records with a status
Human gateCodeThe approval lands on a different callGate in its own step; approve a digest of the call
Orchestrator-workerModelWorkers make decisions that conflictParallelize reads, keep decisions in one context
Sequential pipelineCodeA model stage enforces a rule most of the timeControls in tested code; validate between stages
Fan-out / fan-inCodeThe collector summarizes over a missing branchReport missing branches; key each branch’s writes
Debate / generator-criticCode sets the roundsAgreement that sounds stronger, not betterBeat best-of-n at the same token budget
Handoff / routingModelUntrusted text reaches the agent with more toolsTyped handoff fields; filtered history
Swarm / meshEvery agentNowhere to enforce a budget, a deadline or an auditTrace ID, budget and deadline on every call

Self-correction needs a signal from outside the model #

Reflection and debate rest on the hope that a model reviewing its own output catches its own mistakes. Without help, it mostly doesn’t. Huang et al. (ICLR 2024) found that LLMs “struggle to self-correct their responses without external feedback”, and that reasoning sometimes got worse after self-correction. Smit et al. found that multi-agent debate systems “do not reliably outperform other proposed prompting strategies”, though some did better once tuned.

The fix is to give the critic something the generator didn’t have: the test run, the compiler error, the failed schema validation, the policy engine’s verdict. A critic with evidence is a check. A critic without it is a second sample, and two samples that agree sound more convincing without being more correct. Before keeping a debate loop, compare it with best-of-n at the same token budget. If it doesn’t win there, it is spending tokens on the appearance of review.

More agents buy more context, and cost tokens and agreement #

Anthropic’s write-up of its multi-agent research system is the clearest public account of what orchestrator-worker buys. A lead agent spawning parallel subagents “outperformed single-agent Claude Opus 4 by 90.2%” on Anthropic’s internal research eval. The same post reports that agents use about 4× the tokens of chat and multi-agent systems about 15×, and that on BrowseComp “token usage by itself explains 80% of the variance” in performance. Much of what the extra agents buy is the ability to spend more tokens at once.

The post is just as clear about the limits. Domains where every agent needs the same context, or with many dependencies between agents, “are not a good fit for multi-agent systems today”, and most coding tasks have fewer truly parallel parts than research. Cognition’s Don’t Build Multi-Agents reaches the same place from the other side: “Actions carry implicit decisions, and conflicting decisions carry bad results.” Its example splits a Flappy Bird clone into a background task and a bird task. One subagent returns a Super Mario–style background, the other a bird that looks and moves nothing like the original, and the final agent has to merge two halves that don’t fit. The MAST study (Cemri et al.) annotated more than 1,600 traces across seven multi-agent frameworks and found 14 failure modes in three groups: system design, misalignment between agents, and task verification.

The rule that falls out is to parallelize reads and keep decisions in one context. Research, search and code scanning split well because each worker returns evidence and nothing else. Once workers make choices that must agree, such as a schema, a style or a write to the same record, those choices belong in one place.

Fan-in has a trap of its own. A collector handed two of three branch results will write a confident summary of three. Make the missing branch a field it has to report. Give every side effect inside a branch its own idempotency key, because a retried fan-out retries every branch.

A handoff moves authority, not just the conversation #

In the OpenAI Agents SDK, when a handoff occurs, “the new agent takes over the conversation, and gets to see the entire previous conversation history”. That default is convenient, and for a triage agent in front of a billing agent it is a path to more privilege. The triage agent reads untrusted customer text. The billing agent holds the refund tools. Whatever instructions the customer wrote into their message now sit in the context of the agent that can move money.

The SDK’s input_filter changes what the receiving agent sees. Use it, and go further: hand off typed fields instead of the transcript.

JSON
{
  "intent": "refund_request",
  "customer_id": "cus_A17",
  "charge_id": "ch_3Mar",
  "customer_message_id": "msg_8812",
  "identity": "verified_by_session"
}

The billing agent reads the charge from the payments system, not from the conversation. If it needs the customer’s words, it fetches the message by ID and treats it as data. Its tools act with the permissions of the customer’s session, not with everything the agent was configured to reach.

A real mesh raises the stakes. Cross-organization protocols such as A2A, which Google announced in April 2025 and gave to the Linux Foundation that June, let agents find each other through published Agent Cards and call each other directly. Every hop is then a trust boundary, and no central orchestrator holds the budget, the deadline or the audit log. They have to travel with the request: a trace ID, the remaining budget and a deadline on every call, and authentication at every hop. Without them, a runaway task has no place where it can be stopped.

A human gate is a durable pause and an authorization #

The same documentation draws the consequence of the sentence in the opening: any code that ran before interrupt() runs again on resume, so you should not perform non-idempotent operations before it. A model call is not idempotent. Here is the node from the opening:

PYTHON
from langgraph.types import interrupt

def refund_step(state):
    call = draft_refund(state["messages"])       # model call: runs again on resume
    answer = interrupt({"call": call})           # the reviewer sees this call
    if answer == "approve":                      # on resume, returns "approve" at once
        return {"result": refund(**call["args"])}
    return {"result": None}

On resume, draft_refund runs again and interrupt() returns the stored answer without pausing. On LangGraph 1.2.16, with a stand-in model that drafts €240 and then €720, the reviewer is shown 24000 cents and 72000 are refunded. Move the draft into its own node, whose output is checkpointed, and the second draft never happens:

PYTHON
import hashlib, json
from langgraph.types import interrupt

def digest(call: dict) -> str:
    return hashlib.sha256(json.dumps(call, sort_keys=True).encode()).hexdigest()

def draft(state):                                # runs once; its output is checkpointed
    return {"call": draft_refund(state["messages"])}

def gate(state):                                 # restarts on resume and re-reads the stored call
    answer = interrupt({"call": state["call"], "digest": digest(state["call"])})
    return {"approval": answer}                  # {"digest": "a9d9…", "by": "rev_42"}

def execute(state):
    if state["approval"]["digest"] != digest(state["call"]):
        raise PermissionError("approval is for a different call")
    return {"result": refund(**state["call"]["args"])}

Wire the three as draft → gate → execute. The digest check is not redundant with the split. It catches every other way the call can change between approval and execution: a state edit, a migration, a retry path someone adds next quarter.

FIG · One approval, two layoutsDIAGRAM
ONE NODE · THE APPROVAL OUTLIVES THE CALL IT WAS FORDRAFTmodel: refund €240inside the nodeGATEreviewer sees €240approves, pausesRESUMEnode restartsfrom the topDOCUMENTED BEHAVIOURDRAFT AGAINmodel: refund €720gate: approveTHE WORLD€720the wrong callSPLIT STEPS · THE APPROVAL IS FOR ONE CALLDRAFTown node, runs oncestored: €240CHECKPOINTEDGATEapproves digest dof the exact callRESUMEgate restarts,reads stored callEXECUTEh(call) == d ?then refund, keyedCHECKS BEFORE IT ACTSTHE WORLD€240one refundOn resume the node restarts from the top: the model drafts again, and the gate returns the old approval.Draft in its own step, approve a digest of the call, and recheck it in the step that acts.
The same refund and the same reviewer, with the gate placed two ways. Top, the draft and the gate share a node: on resume the node restarts, the model drafts a different refund, and the gate hands back the approval it already holds. Bottom, the draft is its own checkpointed step: the gate re-reads the stored call, and the step that acts refuses any call whose digest differs from the one the reviewer approved.

Three rules hold for any gate, whatever the framework:

  • The gate gets its own step, and nothing with side effects runs before it in that step.
  • The reviewer approves the call, not the model’s description of it. Show the tool and its resolved arguments. The model’s summary is a label, and it can be wrong or injected.
  • The pause has to survive what happens while it waits. LangGraph resumes from a checkpointer with the same thread ID and asks for a durable one in production, and the OpenAI Agents SDK says to serialize the run state and store it when review might take time. Reviews take hours, and deploys happen in between. A deploy that renames a state field or a node can strand every run paused across it.

Keep the catalog as vocabulary #

None of this makes the catalog wrong. Its names are useful: “orchestrator-worker” and “handoff” say a lot in two words, and a team that shares them designs faster. Use the catalog to name what you are building, and the three questions to decide how it will fail: who picks the next step, what a restart runs again, and where authority changes hands.

Sources and method

  1. Workflow and agent definitions, and the advice to find the simplest solution, from Anthropic’s “Building effective agents” (19 December 2024). The 4× and 15× token figures, the 80% BrowseComp variance figure, the 90.2% result and the limits on fit from Anthropic’s “How we built our multi-agent research system” (13 June 2025).
  2. The quoted principle and the Flappy Bird example from Walden Yan, “Don’t Build Multi-Agents”, Cognition (12 June 2025). Failure-mode counts from Cemri et al., “Why Do Multi-Agent LLM Systems Fail?” (arXiv 2503.13657). Self-correction results from Huang et al., “Large Language Models Cannot Self-Correct Reasoning Yet” (ICLR 2024), and Smit et al., “Should we be going MAD? A Look at Multi-Agent Debate Strategies for LLMs” (arXiv 2311.17371).
  3. LangGraph’s interrupt and resume behaviour, the OpenAI Agents SDK’s handoff and approval behaviour, and the Swarm README, from the vendors’ documentation as of October 2026. A2A dates from Google’s announcement and the Linux Foundation’s launch of the project on 23 June 2025.
  4. Both gate layouts were run on LangGraph 1.2.16 with a stand-in model that returns a different draft on each call; the second layout ran as shown, with stub tools. The refund, amounts and IDs are hypothetical.
← Back to devjoinedthechat