State, channels and reducers
Two parallel branches write the same key: no reducer is an error, an add reducer keeps both in a fixed order, a custom reducer picks one; and what a router can see.
Loading the animation…
Concept
The state is a set of channels, one per key, and each channel has a reducer: the rule that turns the current value and a write into the new value. A key declared without one keeps the last value written, and accepts one write per super-step. A key declared Annotated[list, operator.add] appends; any two-argument function will do, such as max.
Parallel nodes are where this matters. The animation runs a planner and then two branches, fast (700 ms) and careful (1,600 ms), that both write the key answer in the same super-step. Neither sees the other's write; both writes meet at the barrier.
- No reducer: two writes to one key in one step is an error, LangGraph's
InvalidUpdateError("Can receive only one value per step"). The step saves no checkpoint; the last good one stays the thread's latest. - Add: both writes are kept,
answer= [42, 41]. Note the order:carefulfinishes last but its write comes first, because LangGraph applies a step's writes in task order (for plain nodes, sorted by node name), not in the order they finish. The result does not depend on timing, so a run replays the same way. - Max: a custom reducer keeps the larger answer, 42.
The fourth graph is about what a node can see. Node p has a conditional edge whose router goes to r only if x = 7, and its sibling q sets x = 7 in the same step. The router runs with the snapshot plus its own node's writes, not its sibling's, so it sees no x and the run ends without r, although the state after the step does hold x = 7.
Maths
Let be the writes to key in super-step , sorted by the writing node's name. The new value is the left fold in the equation above. With and the step fails. With list concatenation the result keeps every write in that order; with an associative and commutative reducer such as or integer the order does not matter at all, which is the property to aim for when parallel branches write one key.
A router on node evaluates on , the snapshot with only 's own writes applied through the reducers. So a routing decision can depend on what just wrote, but never on a sibling in the same step: if it must, put the router after a join.
Code
The reducers at the barrier, and the router's view, cut from the vendored engine (graph.ts):
if (chan.reducer === "overwrite" && seen[k]! > 1)
throw new InvalidUpdateError(`At key '${k}': Can receive only one value per step.`);
out[k] = reduceValue(chan, out[k], k in out, v);
const own = applyWrites(this.spec, values, [[name, upd]]);
for (const t of route(e.router, own)) {
How the conformance check declares the same channels in real LangGraph (conformance/langgraph_graphs.py):
fields[s["key"]] = Annotated[t, operator.add]