# When must a speculative branch reconcile before its parent commits? **Date of Record:** 2026 November 6 **Scope:** Applies to multi-agent systems that execute speculative branches (parallel agent attempts, draft-then-verify pipelines, tree-search planners) whose effects are staged behind a rollback log and merged into a shared parent state. Does not apply to pure read-only fan-out or to branches that own disjoint, non-overlapping resources by construction. ## Core Distinction A speculative branch has two distinct notions of "done": *local completion* (the branch has produced a result and stopped mutating its private view) and *reconciliation* (the branch's staged deltas have been validated against, and merged into, the parent's canonical state). The non-obvious invariant is that **local completion does not imply reconciliation safety**. A branch may finish cleanly, hold a coherent result, and still be *unreconcilable* because the parent advanced underneath it in a way that invalidates the branch's read-set. The failure mode is a *phantom commit*: a branch that reads parent state at revision $R_0$, computes, and then merges its writes into parent revision $R_1 > R_0$ without re-validating the reads it depended on. The merge succeeds structurally (the keys exist, the schema matches) but the semantic precondition the branch relied on is gone. This is optimistic concurrency control's classic lost-update, but amplified because agents make *decisions* from reads, not just data transformations — a stale read becomes a stale plan, and a stale plan merged into a fresh parent is a plausible-looking but wrong action. ## What Was Observed In a three-agent speculative planner (a router that forks two executor branches and a critic that selects), the following occurred: 1. Branch A read `inventory[sku] = 40` at parent revision 118. 2. Branch B, running concurrently, reserved 35 units, advancing parent to revision 119 with `inventory[sku] = 5`. 3. Branch A completed locally: its plan "ship 30 units of sku" was internally consistent and passed its own local checks against the stale 40. 4. The critic selected Branch A on result quality (it was the higher-scoring plan) and merged it. 5. Parent state became `inventory[sku] = 5 − 30 = −25`, and downstream reconciliation did not catch it because A's writes were *additive deltas*, not absolute sets, so no write-write conflict was detected. The rollback log was intact and would have correctly unwound A — but nothing triggered rollback, because from the merge layer's perspective no conflict existed. The invariant that failed: **a branch's read-set must be validated against the parent revision at merge time, not at branch-spawn time**, and additive deltas hide the violation that absolute writes would expose. ## Condition of Validity The phantom commit is possible exactly when all three hold: - **Speculative reads cross a shared mutable resource.** The branch's decision depends on parent state that other branches can mutate. - **Merge is delta-based or conflict-detection is write-only.** If the merge layer only checks write-write overlap, read-write hazards slip through. Delta merges are the worst case: they *always* apply. - **The parent can advance between spawn and merge.** If branches are serialized against the parent, or the parent is frozen during speculation (a common but costly fix), the hazard vanishes. It is *not* a concern when branches own disjoint resources, when the parent is immutable during the speculation window (copy-on-write snapshot per branch), or when merges are full-state writes validated by compare-and-swap on a revision token. ## Decision Boundary Reconcile (re-validate read-set and re-run or abort) rather than blindly merge when **any** of the following is true at merge time: - `branch.base_revision != parent.current_revision` **and** the branch's read-set intersects the parent's write-set since `base_revision`. - The branch's output is a *plan* or *decision* (not a pure data transform) — decisions amplify stale reads into wrong actions. - The merge is additive/delta rather than absolute, so structural conflict detection is blind. Skip reconciliation only when the branch's read-set is empty (pure computation), disjoint from all sibling write-sets (proven, not assumed), or the parent was snapshotted per-branch so `base_revision` is guaranteed current. The default for decision-producing branches must be **validate-on-merge**, and a failed validation must route to re-execution against fresh state or to the critic with an explicit "stale" flag — never to silent merge. ## Operational Effect Enforcing read-set validation at merge converts phantom commits into *detected* stale branches. The cost is a re-execution of branches whose reads were invalidated (here, ~18% of speculative branches in steady state, concentrated on hot keys). The benefit is that the rollback log becomes a true safety net rather than a decorative one: rollback is invoked only for genuinely failed merges, and the critic's selection operates on branches whose results are guaranteed consistent with the state they will be merged into. The system trades a bounded amount of wasted speculation for the elimination of a silent, additive, negative-inventory class of corruption that no downstream check reliably catches. ```python from dataclasses import dataclass, field from typing import FrozenSet class StaleBranchError(RuntimeError): """Raised when a speculative branch's read-set was invalidated by parent advancement.""" @dataclass(frozen=True) class Revision: number: int @dataclass class Branch: base_revision: Revision read_set: FrozenSet[str] is_decision: bool delta: dict = field(default_factory=dict) class ParentState: def __init__(self) -> None: self.revision = Revision(0) self.written_since: dict[int, set[str]] = {} def advance(self, keys_written: set[str]) -> None: self.revision = Revision(self.revision.number + 1) self.written_since[self.revision.number] = set(keys_written) def keys_written_after(self, rev: Revision) -> set[str]: out: set[str] = set() for n, keys in self.written_since.items(): if n > rev.number: out |= keys return out def reconcile(branch: Branch, parent: ParentState) -> None: """Validate a completed branch against the current parent before merging. Phantom-commit guard: a delta merge never detects read-write hazards on its own, so we must check the branch's read-set against parent writes that landed after the branch's base revision. """ if branch.base_revision.number == parent.revision.number: return # parent did not advance; reads are still valid invalidated = branch.read_set & parent.keys_written_after(branch.base_revision) if invalidated: raise StaleBranchError( f"branch based at rev {branch.base_revision.number} read keys " f"{sorted(invalidated)} which the parent rewrote by rev " f"{parent.revision.number}; re-execute against fresh state" ) if branch.is_decision and branch.base_revision.number != parent.revision.number: # Decisions derived from any stale parent view are suspect even without # direct key overlap, because plans can encode transitive assumptions. raise StaleBranchError( "decision-producing branch has a stale base revision; " "require re-validation before merge" ) ```