# When must an instance reject a speculative branch before reconciliation? **Date of Record:** 2026 November 6 **Scope:** Applies to orchestrators that fork speculative agent branches, execute them optimistically against shared external state, and reconcile them at a join point. Does not apply to purely read-only speculation, nor to branches whose effects are fully isolated behind a commit-gated transactional boundary. ## Core Distinction The non-obvious invariant is this: **a speculative branch is safe to reconcile only if every external side effect it performed is either (a) idempotent under re-application, or (b) reversible by a compensating action that the branch itself recorded at effect-time.** The failure mode is not "the branch was wrong and we roll back." It is "the branch was right, but we cannot prove *what it touched*, so rollback is incomplete and reconciliation silently double-applies." The distinction that operators conflate: *logical* rollback (discarding the branch's in-memory result tree) versus *physical* rollback (undoing writes the branch emitted to external systems — queues, object stores, remote APIs, shared databases). Speculative execution frameworks routinely implement the former and assume the latter is free. It is not. A branch that emitted a non-idempotent write before losing the race cannot be rolled back by dropping its result; it must be *compensated*, and compensation requires a recorded effect log captured at emission time — not reconstructed afterward. ## What Was Observed During a fork-and-reconcile cycle across three speculative branches sharing a single mutable task queue, two branches emitted `enqueue(work_item)` with distinct payloads derived from the same parent snapshot. The losing branch was discarded at the join. Its enqueued item was *not* withdrawn, because the rollback path only unwound the branch's result tree — the queue write had already been acknowledged and dequeued by a worker before reconciliation completed. The winning branch enqueued a *second* item. Downstream consumers processed both, producing a duplicate effect that no branch's local state recorded as anomalous. The tell was not an error. The tell was that the reconciled result was internally consistent while the external system was not. This is the signature of the boundary: **speculation that is correct in the result graph and incorrect in the effect graph.** Standard branch-comparison tooling — diffing result trees, checking equivalence at the join — cannot see it, because the divergence lives outside the tree. A second observed instance: a branch called a remote API with a non-idempotent `POST` (no idempotency key, server-side dedup window of minutes). The branch lost the race, was rolled back logically, and the compensating "delete" call targeted an ID the branch had *inferred* rather than *recorded*. The inference was wrong under a retry that had returned a different server-assigned ID. The delete removed an unrelated record. ## Condition of Validity The invariant holds — that is, a branch *must* be rejected before reconciliation rather than rolled back after — precisely when all of the following are true: 1. The branch has emitted at least one external side effect. 2. At least one such effect is neither idempotent nor covered by a recorded, effect-time compensation handle. 3. The reconciliation window overlaps the effect's visibility to other actors (i.e., the effect can be observed or consumed before the branch is resolved). When any of these fails, ordinary optimistic rollback is sound and the heavy machinery below is unnecessary. When all three hold, the branch has crossed a boundary that logical rollback cannot recover: it has *committed* to the external world while remaining *uncommitted* in the orchestrator's model. The only correct action is to refuse the branch at the join and escalate it to a compensating transaction path — never to "discard and proceed." ## Decision Boundary At each join point, before comparing branch results, the reconciler must classify each branch by its effect record: - **Pure / idempotent / fully compensated-at-effect-time** → eligible for normal optimistic reconciliation; discard losers freely. - **Non-idempotent effect, handle recorded** → eligible, but reconciliation must *execute* the recorded compensation on losers before declaring the join complete. - **Non-idempotent effect, no recorded handle** → **ineligible**. Do not roll back. Do not discard. Quarantine the branch and route to a human-or-supervised compensation path. Proceeding is the failure. The boundary is therefore drawn at *effect-time record completeness*, not at *result correctness* and not at *join outcome*. A branch that would have won is still ineligible if it lacks a compensation handle — because "would have won" is not knowable until after the effects have escaped, and the effects have already escaped. ## Operational Effect Enforcing this boundary converts a class of silent duplicate-effect and mis-targeted-compensation incidents into explicit, loud quarantine events at the join. The cost is throughput: branches that speculatively touch non-idempotent external state can no longer be freely forked-and-discarded, so the orchestrator must either (a) gate such effects behind a commit barrier that defers emission until branch selection, or (b) accept quarantine latency on the fraction of branches that emit them. In practice the correct design is (a) for anything known non-idempotent, reserving (b) for the residual unknown cases — and treating every quarantine as a signal that an effect type was mis-classified as safe to emit speculatively. The durable lesson: optimistic forking is safe only over the *effect-free* or *effect-recorded* subset of actions. The result graph is not the system. Reconciliation must read the effect log first and the result tree second, or it will reconcile a fiction. ```python from dataclasses import dataclass, field from enum import Enum from typing import Optional class SpeculationBoundaryError(RuntimeError): """Raised when a branch has escaped non-idempotent effects without a recorded compensation handle, making logical rollback unsound.""" pass class EffectClass(Enum): PURE = "pure" IDEMPOTENT = "idempotent" COMPENSABLE = "compensable" # non-idempotent, handle recorded at effect-time UNRECOVERABLE = "unrecoverable" # non-idempotent, no handle @dataclass class Effect: kind: str effect_class: EffectClass compensation_handle: Optional[str] = None @dataclass class Branch: branch_id: str effects: list[Effect] = field(default_factory=list) result: Optional[dict] = None def emit(self, kind: str, effect_class: EffectClass, compensation_handle: Optional[str] = None) -> None: self.effects.append(Effect(kind, effect_class, compensation_handle)) def classify_for_reconciliation(branch: Branch) -> str: """Decide whether a branch may be optimistically reconciled. Returns one of: 'reconcile', 'compensate', 'quarantine'. Raises SpeculationBoundaryError if the branch is ineligible yet the caller attempts to proceed as if it were reconcilable. """ has_unrecoverable = any( e.effect_class is EffectClass.UNRECOVERABLE for e in branch.effects ) has_compensable = any( e.effect_class is EffectClass.COMPENSABLE for e in branch.effects ) if has_unrecoverable: # Non-idempotent effect with no recorded handle: must NOT be rolled # back logically; quarantine for supervised compensation. return "quarantine" if has_compensable: return "compensate" return "reconcile" def reconcile(winner: Branch, losers: list[Branch]) -> Branch: for loser in losers: verdict = classify_for_reconciliation(loser) if verdict == "quarantine": raise SpeculationBoundaryError( f"branch {loser.branch_id!r} emitted unrecoverable external " f"effects; logical rollback is unsound. Quarantine and route " f"to supervised compensation instead of discarding." ) if verdict == "compensate": for e in loser.effects: if e.effect_class is EffectClass.COMPENSABLE: # Execute recorded compensation before declaring join done. _run_compensation(e.compensation_handle) # verdict == 'reconcile': safe to discard loser's result tree. return winner def _run_compensation(handle: Optional[str]) -> None: if not handle: raise SpeculationBoundaryError( "compensable effect missing handle at execution time" ) # ... invoke recorded compensating action ... ```