# Which invariants prevent silent state corruption when speculative agents merge after a cost-aware commit boundary **Date of Record:** 2026 November 9 **Scope:** Applies to orchestrators that run multiple speculative agent branches against a shared mutable substrate (tool side effects, filesystem, database rows, external API calls) and merge branches on a cost-aware commit decision. Does not apply to read-only fan-out, to single-agent retry loops, or to systems where every branch runs in a fully isolated sandbox with no shared external effects. ## Core Distinction There are two different things people call "rollback." *Logical rollback* means discarding a branch's in-memory trace and its declared intent. *Physical rollback* means undoing the effects that branch already committed to the outside world. Speculative multi-agent systems routinely conflate the two because the branch-and-merge abstraction makes a branch look like a transaction. It is not. A branch is a *proposal generator* that may have already leaked side effects before the merge decision is made. The invariant that matters is not "branches are atomic" — that is usually false — but "the merge point is the first moment at which any branch's effects are permitted to become visible to a peer branch or to a downstream consumer." Cost-aware commitment breaks this because it deliberately delays the merge decision to accumulate more evidence, extending the window during which leaked effects are live but unowned. ## What Was Observed In a deployment running three-to-five speculative branches per task, each branch was given a cost budget and allowed to call tools (write files, POST to internal services, mutate a staging table). The orchestrator selected the winning branch by a cost/latency/quality score and "rolled back" the losers by deleting their traces and replaying the winner's diff. The failure was not a crash. It was a slow divergence: two branches independently wrote to the same staging row under different keys derived from their own branch IDs, and a third branch, spawned *after* the first two, read the union of those rows during its exploration phase. The merge selected branch B, replayed B's diff, and deleted A and C's traces. The staging table retained A's and C's rows because their branch IDs were not part of the winner's diff namespace. Downstream consumers then read a state that no branch had ever proposed and no merge had ever authorized. The corruption was silent for six days because every individual operation succeeded and every trace looked internally consistent. The key observation: the losers' effects were *physically committed* at the moment of the tool call, but *logically uncommitted* until the merge. The window between those two events is where the invariant lives, and cost-aware commitment widens that window on purpose. ## Condition of Validity The invariant "no branch effect is visible to a peer branch or downstream consumer before the merge decision" holds only if one of the following is true: 1. Every tool call is namespaced by branch ID and the merge is the *only* operation that rewrites the namespace to the canonical one; or 2. Every tool call is deferred behind a commit gate that the orchestrator controls, and the gate is opened only for the winning branch; or 3. Branches are physically isolated (separate sandboxes, separate credentials, separate tables) and the merge is a copy operation with an explicit conflict check. If none of these hold — which is the common case when agents are given broad tool access for latency reasons — then the merge is not a commit boundary. It is a *selection* boundary, and any invariant you assert about atomicity is false. Cost-aware commitment is only safe under conditions 1–3; under the "broad access, late decision" pattern it is a corruption generator, because the longer you wait to decide, the more unowned effects accumulate. ## Decision Boundary The decision to speculate is not the decision to commit. Treat them as separate gates: - **Speculate** when the expected value of parallel exploration exceeds the cost of duplicated tool calls *and* the substrate supports per-branch namespacing or deferral. Otherwise, serialize. - **Commit** only when the winning branch's diff is the *sole* source of canonical state, and the merge operation explicitly enumerates and reconciles every other branch's namespaced effects (including no-op reconciliation, which must be recorded). - **Abort** (do not merge, do not delete traces) when the reconciliation finds effects outside the winner's namespace that cannot be attributed to a known branch. Deletion of an unattributable effect is itself a corruption event. The non-obvious boundary: a branch that "did nothing" is not the same as a branch that "wrote nothing." An empty trace with a non-empty physical footprint is the signature of a leaked effect, and cost-aware commitment makes this common because cheap branches are often cheap precisely because they skipped the bookkeeping that would have recorded the effect. ## Operational Effect Teams that adopt cost-aware speculative execution without per-branch namespacing see three recurring symptoms: (a) merge diffs that are smaller than the actual state change, (b) downstream reads that are reproducible only if you replay *all* branch traces, not just the winner's, and (c) "phantom rows" that survive rollback and reappear after garbage collection. The fix is not to abandon speculation; it is to make the merge a reconciliation over the union of branch namespaces rather than a replay of the winner. The invariant to enforce in code is that the merge function refuses to complete if any branch's physical footprint is non-empty and not explicitly accounted for. That refusal must be a hard error, not a warning, because a warning will be ignored under cost pressure — which is exactly the pressure that produced the widened window in the first place. ```python class MergeReconciliationError(RuntimeError): """Raised when a speculative merge would leave unowned physical effects.""" pass def merge_branches(winner, all_branches, substrate): """ Reconcile a speculative multi-agent merge. Invariant: every branch's physical footprint must be either the winner's (replayed) or explicitly reconciled (no-op or discarded) before the merge is allowed to complete. An empty trace does not imply an empty footprint. """ winner_ns = winner.namespace winner_effects = substrate.effects_in(winner_ns) unowned = [] for branch in all_branches: if branch is winner: continue branch_effects = substrate.effects_in(branch.namespace) if branch_effects and not branch.reconciliation_declared: unowned.append((branch.id, branch_effects)) if unowned: raise MergeReconciliationError( "refusing to merge: branches with physical effects but no " f"declared reconciliation: {[b for b, _ in unowned]}. " "Deleting these effects would itself be a corruption event." ) # Only now is it safe to promote the winner's namespace to canonical. substrate.promote(winner_ns) for branch in all_branches: if branch is not winner: substrate.reconcile(branch.namespace, branch.reconciliation_declared) return substrate.canonical_state() ```