Claim ledger
The Graph Looks Right. The Merge Is Where It Breaks.
A multi-agent graph can lose a verdict with no error and nothing turning red. The bug is one line in the state schema.
19 claims · 19 verified to primary
The claim ladder
| Rung | Claim | Evidence | Whose behaviour it measures | Scope limit |
|---|---|---|---|---|
| 1 | The single-vs-multi-agent debate resolves to one rule: parallelise reads, keep writes single-threaded | Cognition vs Anthropic, reconciled by LangChain | Framework designers and practitioners, at the architecture level | A design heuristic, stated abstractly; it does not tell you where in your code the rule binds |
| 2 | In LangGraph that rule is a property of the state schema: a channel written by more than one parallel node is where writes collide | verify.py EXP 1, real InvalidUpdateError: At key 'decision': Can receive only one value per step (langgraph 0.6.11) | The LangGraph runtime | LangGraph specifically; other frameworks (handoffs, shared crew state) locate the same collision elsewhere |
| 3 | The framework refuses the blunt collision LOUDLY, and its own error routes you to the fix that makes it SILENT | The error says "Use an Annotated key"; add operator.add; the channel becomes ['ship it','hold','roll back'] and a consumer acting on decision[0] ignores two, no error | The builder's reaction to the error message | The silent failure needs a parallel CONSUMER of a decision channel; a single writer reading a findings list is safe by construction |
| 4 | So build it safe and audit any graph by its schema: parallel branches to isolated/append channels, one writer per decision channel, plus a recursion cap and a human gate | agent_graph_demo.py: safe run reconciles 3 of 3 with a human veto; cycle hits GraphRecursionError at 25; interrupt() needs a checkpointer | The reader's own graph | A discrimination the reader applies, not a benchmark; makes no claim about how often this fails in production |
What would make it wrong
The central claim fails if: (a) the InvalidUpdateError / silent-merge behaviour does not reproduce on current LangGraph at the stated version (it is version-pinned and reproduced by verify.py; a future version that raises on the reducer merge too would narrow the claim); or (b) the schema audit does not separate safe from unsafe graphs — a graph that passes the channel check still ships conflicting decisions, or one that fails it is actually fine. Either collapses the instrument.
The evidence, row by row
Each row is a claim the essay makes, the words in the source that support it, where in the source they are, the day the source was read, and the status the check assigned. Verified means the primary was opened and read; Executed means we ran it ourselves; Reported means carried from a source we could not open in full.
-
Two parallel nodes writing one plain (LastValue) channel do not silently overwrite; LangGraph refuses and raises
At key 'decision': Can receive only one value per step. Use an Annotated key to handle multiple values.
first-party own run, langgraph 0.6.11 · verify.py step 1; agent_graph_demo.py --trap step 1
-
Silencing that error with a last-write-wins reducer on the decision channel keeps one write and silently drops the rest, with no error
the channel now holds: 'roll back' and silently dropped: ['ship it', 'hold'] (no error was raised)
first-party own run, langgraph 0.6.11 · verify.py step 2; agent_graph_demo.py --trap step 2
-
Which write survives follows node-name order (the node whose name sorts last in Python's default codepoint order wins, not case-insensitive alphabetical) and is stable across re-runs of the same graph; an undocumented implementation detail
zebra=ship, alpha=hold, mike=roll -> survives: ship and determinism (same order x3): ['roll back', 'roll back', 'roll back']; codepoint check Zebra vs alpha -> alpha survives (Z=0x5A < a=0x61)
first-party own run, langgraph 0.6.11 · nodename.py (crossed name/value order); trap re-run x4 all kept hold; order_test.py (uppercase vs lowercase, re-run 2026-08-29)
-
The documented reducer operator.add is lossless: on a str channel it concatenates the writes in node-name (alphabetical) fold order; on a list channel it keeps all of them
decision -> 'roll backship ithold' (str, release-gate fan-out changelog/metrics/tests, stable x3) and ['ship it', 'hold', 'roll back'] (list)
first-party own run, langgraph 0.6.11 · graph fan-out re-verified 2026-08-29 (add_test.py); earlier reducer_shapes.py fixed-order unit test showed 'ship itholdroll back' — the article uses the graph value
-
The SAME reducer knob is correct on a gather channel: three read-workers append an isolated operator.add findings channel and a single writer reconciles all three
findings reaching the single writer = 3 of 3 and reconciled 3 of 3
first-party own run, langgraph 0.6.11 · verify.py step 3; agent_graph_demo.py safe run
-
A Send fan-out into a shared plain decision channel raises the same InvalidUpdateError
raised InvalidUpdateError: At key 'decision': Can receive only one value per step.
first-party own run, langgraph 0.6.11 · verify_claims.py CLAIM A (Send API)
-
A cyclic graph with no stop condition halts at LangGraph's default recursion limit of 25 and raises GraphRecursionError
Recursion limit of 25 reached without hitting a stop condition.
first-party own run, langgraph 0.6.11 · verify.py step 4
-
interrupt() surfaces its payload with or without a checkpointer, but resuming needs one: without a checkpointer Command(resume=...) raises
RuntimeError: Cannot use Command(resume=...) without checkpointer and (no-checkpointer first run) has __interrupt__: True
first-party own run, langgraph 0.6.11 · resume_test.py; no_ckpt2.py (returns __interrupt__ without a checkpointer)
-
On resume the interrupt node re-runs from the top, so a side effect above the interrupt() call happens twice
side effect ran 2 times -> twice, claim SUPPORTED
first-party own run, langgraph 0.6.11 · verify_claims.py CLAIM B
-
A plain channel written by two nodes in sequence (across supersteps) overwrites silently, no error; the first write is lost
no error. final decision = 'B said ship' -> A's write silently lost
first-party own run, langgraph 0.6.11 · verify_claims.py CLAIM C
-
LangGraph exposes no static per-node write set: a node's introspectable .channels is its read set (a node that writes only decision still lists the whole readable state)
a.channels: ['decision', 'other']
first-party own run, langgraph 0.6.11 · writers.py (node a writes only decision)
-
The safe demo makes exactly three worker model calls, one per read-worker
model calls made: 3
first-party own run · agent_graph_demo.py safe run
-
audit.py reads a State TypedDict and flags reducer channels (the fan-out silent-merge surface): findings on the safe schema, decision on the trap schema
! classify findings REDUCER (add) and fan-out safe decision plain
first-party own run (re-run 2026-08-29 after output cleanup) · python3 Products/agent-graph/audit.py
-
Environment of record for every mechanism row
langgraph 0.6.11 and langchain-core 0.3.86 and python 3.9.6
first-party own run · python3 -c "from importlib.metadata import version; ..."
-
Cognition argues actions carry implicit decisions and recommends a single-threaded linear agent
Actions carry implicit decisions, and conflicting decisions carry bad results and The simplest way to follow the principles is to just use a single-threaded linear agent
Cognition (Walden Yan), Don't Build Multi-Agents, 2025 · cognition.com
-
Cognition's 2026 follow-up concludes multi-agent systems work best when writes stay single-threaded and the extra agents add intelligence rather than actions (cited in [^2])
Multi-agent systems work best today when writes stay single-threaded and the additional agents contribute intelligence rather than actions
Cognition (Walden Yan), Multi-Agents: What's Actually Working, 22 Apr 2026 · essay body (re-fetched at primary, 2026-08-29) · cognition.com
-
Anthropic reports its multi-agent research system uses about 15x the tokens of a chat
multi-agent systems use about 15× more tokens than chats
Anthropic engineering blog · anthropic.com
-
The MAST study puts the largest share of multi-agent failures in Specification and System Design, ahead of model capability
FC1 "Specification and System Design" = 41.77% (largest) and failures attributed to system design not model performance
Cemri et al., MAST · openreview.net
-
Ranjan Kumar published the reducer-as-concurrency-policy framing and a compiled-graph merge-policy auditor before this piece (convergent prior art, credited in the body)
The reducer is not a merge helper. It is the concurrency-control policy for that state key. and It appears on no diagram, in no edge list, and in no type checker's output.
Ranjan Kumar, "LangGraph Reducers Are a Concurrency Policy", 27 Jul 2026 · post body (re-fetched at primary, 2026-08-29) · ranjankumar.in
Cite this
- A single claim
"[claim text]" (Floyd, Harry, 2026, https://durabilitycurve.com/claims/where-agents-disagree/)Replace the bracket with the row's claim text. The page URL carries the source; the row's own source link is in the row.- The essay
Floyd, Harry (2026). The Graph Looks Right. The Merge Is Where It Breaks.. The Durability Curve. https://durabilitycurve.com/blog/where-agents-disagree/- This ledger
Floyd, Harry (2026). Claim Ledger: The Graph Looks Right. The Merge Is Where It Breaks. [structured claims with sources]. The Durability Curve. https://durabilitycurve.com/claims/where-agents-disagree/
Quote with attribution and a link to this page or the essay. Say if you changed the wording. Not licensed for model training. Plain-text copy for machines: /md/claims/where-agents-disagree.md.