# When must an instance refuse to commit a speculative branch that has already passed verification: the divergent-tool-outcome rollback trap **Date of Record:** 2026 November 13 **Scope:** Applies to orchestrators that execute multiple candidate agent branches speculatively and select one to commit based on post-hoc verification of each branch's tool-call transcript. Does not apply to single-branch execution, to branches whose tool calls are pure functions over immutable inputs, or to systems where tool side effects are journaled and replayed idempotently at commit time. ## Core Distinction The distinction that matters is between *verification of a branch's reasoning* and *verification of a branch's effects*. A speculative branch is a sequence of (thought, tool-call, tool-result) triples. Verification typically checks that the branch's final state satisfies a goal predicate — the answer is correct, the diff compiles, the plan is consistent with the observed tool results. This is a check over the *transcript*. It says nothing about whether the tool results that the transcript was built from are *stable* — that is, whether re-executing the same tool call now would return the same result. The non-obvious boundary is this: a branch can pass verification perfectly and still be unsafe to commit, because verification was performed against a tool-result snapshot that other speculative branches (or external actors) have since invalidated. The branch is verified-true but stale-false. Committing it applies effects that were justified by a world state that no longer holds. ## What Was Observed In a three-branch speculative run over a shared filesystem-backed tool surface, branch A read `config.yaml` (version 4), planned an edit, and wrote a patch that verified cleanly against version 4's schema. Branch B, executing concurrently, also read `config.yaml`, and — because the verifier for B's subgoal required a migration — *wrote* version 5 to the same file before B was rolled back for an unrelated verification failure. Branch A's verification pass ran after B's write but was evaluated against A's cached read of version 4. A's patch was committed. The commit applied a v4-shaped edit onto a v5 file. The schema check that had "passed" never re-ran against the post-commit bytes. The failure was not a race in the ordinary sense: no two writes interleaved, no torn read occurred, and every individual operation was atomic. The failure was that the *verification predicate* and the *commit effect* were evaluated against different snapshots of the same resource, and the orchestrator treated "verified" as a durable property of the branch rather than a property relative to a snapshot. A second observation sharpened this: when branches were forced to serialize their tool calls, the same divergent-outcome scenario produced a *detectable* conflict (write-write on `config.yaml`) and the orchestrator correctly aborted. The bug only manifested under speculative concurrency, where the conflict was masked by branch isolation. Isolation hid the very conflict that made the commit unsafe. ## Condition of Validity The invariant "a verified branch may be committed" holds only under the conjunction: 1. **Snapshot pinning.** Every tool result used in verification is tagged with a resource version (content hash, ETag, monotonic version counter), and the commit operation is conditional on the resource still being at that version — a compare-and-swap, not a blind write. 2. **Effect isolation.** Speculative branches' tool calls either produce no durable effects until commit, or produce effects that are fully reversible and are reversed on rollback *before* any sibling branch's verification is allowed to conclude. 3. **Verification freshness.** The verification predicate is re-evaluated against the current resource state at commit time, or the commit is rejected if the state changed since the snapshot the predicate was evaluated against. When any of these is absent, "verified" is a statement about a past world, not the present one. The orchestrator must treat it as a *claim to be re-checked*, not a *license to write*. ## Decision Boundary The decision the orchestrator must make at commit time is not "did this branch verify?" but "is the world still the world this branch verified against?" Operationally: - If the branch's read-set versions all match current versions → commit. - If any read-set version is stale **and** the branch's write-set does not touch the changed resource → the branch's *reasoning* may still be sound, but its *effects* must be re-verified against current state before commit. Do not commit on the strength of the old verification. - If any read-set version is stale **and** the branch's write-set intersects the changed resource → refuse. The branch's justification is invalidated by a change it did not observe. Re-planning is required; committing is not an option, regardless of how cleanly the branch verified. The subtle case — and the one that produces silent corruption — is the second. A branch that read `config.yaml` but writes only `deploy.yaml` may be committable, but only if the verifier is re-run against current `config.yaml`. The trap is treating "write-set disjoint from read-set change" as "safe," when the change may invalidate the *premise* of the write even though it does not touch the write's *target*. ## Operational Effect The practical consequence is that speculative verification must be *versioned*, and commit must be a *conditional transaction* over the union of all branches' read-sets, not an unconditional application of the winning branch's write-set. This adds a version-check to every commit and forces the orchestrator to retain read-set metadata through the entire speculative window — it cannot discard a branch's read-set once the branch verifies, because that read-set is the precondition of the commit. It also means rollback must be *effect-complete*: a rolled-back branch that wrote a resource must have that write undone before any sibling's verification is trusted, or the sibling is verifying against a ghost. In the observed incident, B's rollback restored `config.yaml` to v4 *after* A had already committed, which is why the corruption surfaced as a v5 file with a v4 edit rather than as a clean conflict. The rollback was correct in isolation and wrong in composition. The invariant to encode: **a branch is committable iff its verification was evaluated against the resource versions currently live, or the commit is gated on a compare-and-swap that fails closed.** Anything weaker converts speculative parallelism into a mechanism for laundering stale reads into durable writes. ```python from dataclasses import dataclass, field from typing import Mapping class StaleVerificationError(RuntimeError): """Raised when a branch's commit is attempted against resource versions that differ from those its verification was evaluated against.""" @dataclass class Branch: branch_id: str read_set: Mapping[str, int] # resource -> version observed during execution write_set: Mapping[str, int] # resource -> version the write is conditional on verified: bool = False def commit_branch(branch: Branch, live_versions: Mapping[str, int]) -> None: if not branch.verified: raise StaleVerificationError( f"branch {branch.branch_id} is not verified; refusing commit" ) # The core check: verification is only a license to write if the world # it was evaluated against is still the live world. stale_reads = { res: (version, live_versions.get(res)) for res, version in branch.read_set.items() if live_versions.get(res) != version } if stale_reads: raise StaleVerificationError( f"branch {branch.branch_id} verified against stale resource versions: " f"{stale_reads}; re-verify against live state before commit" ) # Writes must be conditional on the exact version they were planned against. for res, expected in branch.write_set.items(): if live_versions.get(res) != expected: raise StaleVerificationError( f"branch {branch.branch_id} write to {res!r} expected version " f"{expected}, live version is {live_versions.get(res)}; " f"compare-and-swap would fail closed" ) # Only here — read-set fresh AND write-set CAS-valid — is commit permitted. for res, version in branch.write_set.items(): live_versions[res] = version + 1 def rollback_branch(branch: Branch, live_versions: Mapping[str, int]) -> None: # Rollback must restore every resource the branch touched to the version # its siblings may have read, before sibling verification is trusted. for res, expected in branch.write_set.items(): if live_versions.get(res) != expected + 1: raise StaleVerificationError( f"rollback of {branch.branch_id} found {res!r} at unexpected " f"version {live_versions.get(res)}; effect-complete rollback impossible" ) live_versions[res] = expected ```