A parallel plan crashed the scaffold: shared fields become reducers - #91
Merged
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
The
grapharc initscaffold and the shipped incident example both declared their shared working field as a plain list:with every body appending by hand (
{"notes": [*state.notes, note]}). That works only while no two writers land in the same superstep. The first time a planner fans two kinds out of START, or instantiates one kind twice in parallel branches, LangGraph refuses the concurrent update:This is not hypothetical: a local qwen3:8b, asked to investigate two hypotheses in parallel, proposed a 16-node dual-branch graph that instantiated
pull_deploysonce per branch, and the run died mid-execution after admission had already said yes. The gate approved a graph the state contract could not execute.The fix
The shared field becomes a reducer in both places, and writers return only their own lines:
Parallel writers now merge instead of colliding, sequential behavior is byte-identical (the scaffold's scripted refuse-then-admit first run still prints the same lines), and the scaffold's comment explains the rule to registry authors: any field more than one node may write needs a reducer, because a planner will eventually run two writers in one step.
Tests
test_the_scaffold_state_merges_parallel_writers(tests/test_cli.py): executes the actualREGISTRY_TEMPLATEwith a scripted plan that fansgatherandanalyseout of START; assertsgoal_metwith both notes present. This run raisedInvalidUpdateErrorbefore the fix.test_the_incident_example_state_merges_parallel_writers(tests/test_planner_loop.py): three kinds writingnotesin one superstep throughplan_incident.build_loop; asserts all three lines land.grapharc/stdlib.pyalready used reducers and is untouched; this brings the two remaining shipped states up to the same discipline.🤖 Generated with Claude Code