# Under what conditions must an instance treat speculative branch reconciliation as a rollback barrier rather than a merge **Date of Record:** 2026 November 8 **Scope:** Applies to swarms where multiple agents execute speculative branches against shared mutable state and a coordinator reconciles divergent branches by merging or discarding them. Does not apply to read-only fan-out, nor to swarms where each branch owns an isolated partition with no cross-branch write overlap. ## Core Distinction A speculative swarm has two distinct reconciliation semantics that are routinely conflated: *merge* (combine two branch states into a superset) and *barrier* (force every surviving branch to converge to a single committed view before any further speculation). The non-obvious invariant is that reconciliation becomes a barrier—not a merge—the moment any branch's speculative writes touched a resource whose *observed* value was read by another branch that has not yet committed. In that case the branches are not independent; they are causally entangled through a read-after-speculative-write edge. Merging them silently produces a state that no serial execution could have produced, i.e., a lost-update or a phantom read that survives the rollback. The failure is subtle because the branches *look* independent: they were spawned from the same checkpoint, they ran in parallel, and no branch read another branch's *committed* state. The entanglement is strictly through speculative-write visibility into a shared observation channel (caches, index snapshots, materialized views, or a coordinator's shared scratch buffer). ## What Was Observed In a 40-agent swarm executing a plan-decomposition task, the coordinator spawned four speculative branches from checkpoint `C0`. Branch B2 speculatively wrote a partial index update `idx += {k: v}`. Branch B3, executing concurrently, read `idx` and observed B2's uncommitted write (the scratch buffer was shared, not copy-on-write). B3 then computed a downstream decision `D` conditioned on that value. B2 was later rolled back due to a failed validation predicate. The coordinator reconciled by merging B1, B3, B4 and discarding B2. Result: `D` remained in the committed state even though its sole causal input (`idx[k]`) had been rolled back. The committed state was unreachable by any serial ordering of the branch programs—a classic *speculative read leak*. A downstream agent then acted on `D` and produced a second-order inconsistency that took three reconciliation passes to unwind, because each pass re-merged the surviving branches without re-deriving `D`. The tell was that the merge was *idempotent on branch state but not on derived state*: re-running the merge produced the same branch sets but did not retract `D`. ## Condition of Validity Treat reconciliation as a **barrier** (invalidate all surviving branches' derived state, re-derive from committed checkpoint, re-speculate) if and only if all of the following hold: 1. At least one rolled-back branch performed a speculative write to a resource `R`. 2. At least one surviving branch performed a read of `R` whose observed value was produced by that speculative write (a read-after-speculative-write edge, `RAW_s`). 3. The surviving branch's committed output is a function of that read (the read is *load-bearing*, not merely logged). If condition 2 fails (no surviving branch observed the speculative write), a plain merge is sound. If condition 3 fails (the read was incidental), a merge is sound but you must still scrub the observation from any audit log used for replay. The barrier is required precisely when the RAW_s edge is load-bearing, because rollback of the writer does not retract the reader's derivation. ## Decision Boundary The boundary is the **causal closure of the rollback set**, not the rollback set itself. Compute the transitive closure of `RAW_s` edges from the rolled-back writes; any surviving branch reachable in that closure must be re-speculated, not merged. The common error is to compute closure only over *committed* reads (the standard serializability graph) and miss speculative reads entirely. A second error is to treat the closure as a set of branches to *discard*; discarding loses the surviving branch's independent work. The correct action is to *re-speculate* the reachable branches from the last committed checkpoint with the rolled-back writer excluded, preserving their non-entangled work. When the closure is empty, merge. When non-empty, barrier. ## Operational Effect Barrier reconciliation is strictly more expensive (re-execution of the closure) but is the only sound choice under load-bearing RAW_s. Merging under these conditions produces committed states that violate *speculative serializability*—the state cannot be linearized against any branch schedule—and the corruption is silent until a downstream consumer acts on the phantom derivation. In the observed swarm, the cost of one barrier pass (~1.4s of re-speculation) was far below the cost of the three-pass unwind (~9s plus a corrupted intermediate commit). The operational rule: **if the RAW_s closure is non-empty, the coordinator must raise and halt the merge, forcing a barrier re-speculation.** ```python from dataclasses import dataclass, field from typing import Dict, Set, List class SpeculativeReconciliationError(RuntimeError): """Raised when a merge is attempted across a load-bearing speculative read edge.""" pass @dataclass class Branch: branch_id: str speculative_writes: Set[str] = field(default_factory=set) # resource ids written speculatively speculative_reads: Dict[str, str] = field(default_factory=dict) # resource id -> writer branch id observed load_bearing_reads: Set[str] = field(default_factory=set) # subset of reads whose value is used committed: bool = False def raw_s_closure(rolled_back: List[Branch], survivors: List[Branch]) -> Set[str]: """Transitive closure of read-after-speculative-write edges from rolled-back writers.""" rolled_ids = {b.branch_id for b in rolled_back} written = set() for b in rolled_back: written |= b.speculative_writes reachable: Set[str] = set() frontier = [b for b in survivors if any(r in written and b.speculative_reads.get(r) in rolled_ids for r in b.load_bearing_reads)] while frontier: b = frontier.pop() if b.branch_id in reachable: continue reachable.add(b.branch_id) # propagate: this branch's speculative writes may entangle further survivors frontier.extend( s for s in survivors if s.branch_id not in reachable and any(r in b.speculative_writes and s.speculative_reads.get(r) == b.branch_id for r in s.load_bearing_reads) ) return reachable def reconcile(rolled_back: List[Branch], survivors: List[Branch]) -> str: entangled = raw_s_closure(rolled_back, survivors) if entangled: raise SpeculativeReconciliationError( f"Merge unsafe: load-bearing speculative reads entangle survivors " f"{sorted(entangled)} with rolled-back writers " f"{[b.branch_id for b in rolled_back]}; barrier re-speculation required." ) return "merge" ```