# In what scenarios must an instance treat speculative tool invocation as irreversible: rollback reconciliation boundaries in multi-agent parallel branch rollouts **Date of Record:** 2026 November 7 **Scope:** Applies to orchestrators that fan out multiple agent branches which optimistically invoke side-effecting tools (writes, sends, payments, state mutations) before a merge/commit decision. Does not apply to read-only speculative branches, pure-function tool calls, or single-branch sequential pipelines where no rollback reconciliation is required. ## Core Distinction The failure boundary is not "did the speculative branch succeed?" but "was the tool's side effect *reconcilable* — idempotent, compensable, or externally observable-but-reversible — at the moment the branch was rolled back?" Speculative execution assumes rollback is free. That assumption holds for compute and memory, and silently breaks for any tool whose effect escapes the branch's transactional envelope. The non-obvious invariant: **a speculative branch may only optimistically invoke a tool if a compensating action exists that is (a) defined before invocation, (b) itself idempotent, and (c) ordered correctly relative to sibling branches that may have observed the effect.** When any of these fails, the branch is not speculative — it is committed, regardless of what the orchestrator's state machine believes. ## What Was Observed In a three-branch rollout (planner → [retriever, mutator, notifier]), the mutator branch optimistically issued a `reserve_inventory` call, and the notifier branch optimistically issued `send_confirmation_email` to the same customer. The planner selected the retriever branch on a confidence threshold and issued rollback to the other two. The mutator's reservation was compensated (release) successfully. The notifier's email had already been delivered; its "compensation" was a second email apologizing for the first. Two sibling branches had also *read* the reservation during their own speculative execution and cached it, so the released reservation left stale state in the merged branch's context. Net result: a customer received a confirmation for an order that was never placed, and the surviving branch carried a phantom inventory record. The orchestrator logged both branches as `rolled_back` with no error. The key detail: every individual tool call returned HTTP 200. No exception was raised anywhere. The system's rollback accounting was internally consistent and externally wrong. ## Condition of Validity Optimistic invocation is valid only under the conjunction: 1. **Compensability:** a declared inverse operation exists, registered before the forward call, not synthesized at rollback time. 2. **Idempotency of compensation:** replaying the inverse (due to retry or duplicate rollback) is safe. 3. **Isolation:** no sibling branch observes the speculative effect before the merge decision, or if it does, the observation is tagged with the producing branch's speculative epoch and invalidated on rollback. 4. **Ordering:** compensation of branch A completes before any branch that read A's effect is itself committed. Condition 3 is the one most often violated in practice, because fan-out frameworks share a context object across branches by default. Condition 4 is violated by any rollback that runs compensations concurrently with merge. ## Decision Boundary Classify each tool call at fan-out time into one of three tiers: - **Reconcilable:** read-only, or write with a registered idempotent inverse and no cross-branch observation. Safe to speculate. - **Escrowed:** write whose effect can be staged (reservation, pending record, draft) and only promoted at merge. Safe to speculate *only if* the staging layer is visible to siblings as pending, not as committed. - **Irreversible:** external delivery, payment capture, physical action, or any effect observed by a party outside the orchestrator. **Must not be speculated.** Serialize behind the merge decision. The boundary is crossed the moment a branch invokes an irreversible-tier tool before the planner has committed. At that point rollback is theater: the orchestrator may report success, but the external world has already accepted the branch. ## Operational Effect Enforcing this boundary converts some parallelism into serialization at the merge gate, costing latency but eliminating phantom-effect reconciliation. The practical implementation is a two-phase tool registry: branches may call `escrow()` freely during speculation, but `commit()` on irreversible tools is gated on a signed merge token from the planner. Rollback reconciliation must run compensations in reverse topological order of cross-branch observation, and any branch that observed a rolled-back effect must be invalidated rather than patched. Without this, the system exhibits the worst failure mode in distributed pipelines: consistent logs, silent divergence from reality. ```python class IrreversibleSpeculationError(RuntimeError): """Raised when a speculative branch invokes a non-compensable tool before the planner has issued a merge commit token.""" pass class ToolTier: RECONCILABLE = "reconcilable" ESCROWED = "escrowed" IRREVERSIBLE = "irreversible" IRREVERSIBLE_TOOLS = { "send_confirmation_email", "capture_payment", "dispatch_physical_shipment", } def guard_speculative_invocation(tool_name: str, branch_id: str, merge_token: str | None) -> None: """Enforce the speculation boundary before a branch calls a tool. Reconcilable and escrowed tools may be called with no merge token. Irreversible tools require a committed merge token from the planner, because no idempotent inverse exists and sibling branches may have already observed the external effect. """ tier = ToolTier.IRREVERSIBLE if tool_name in IRREVERSIBLE_TOOLS else ToolTier.ESCROWED if tier == ToolTier.IRREVERSIBLE and merge_token is None: raise IrreversibleSpeculationError( f"branch {branch_id!r} attempted optimistic invocation of " f"irreversible tool {tool_name!r} with no merge token; " f"rollback would be non-compensable and siblings may observe " f"a phantom effect" ) def rollback_reconcile(branches: list[dict], observed_effects: dict[str, list[str]]) -> list[str]: """Compensate rolled-back branches in reverse topological order of cross-branch observation; invalidate any branch that observed a compensated effect.""" invalidated: list[str] = [] rolled_back = {b["id"] for b in branches if b["status"] == "rolled_back"} # Reverse topological order: compensate observers before producers. for branch_id in reversed(list(rolled_back)): for observer, seen in observed_effects.items(): if branch_id in seen and observer not in rolled_back: invalidated.append(observer) if invalidated: # A surviving branch carries stale state from a rolled-back effect. # Refuse to silently patch; surface for re-planning. raise IrreversibleSpeculationError( f"surviving branches {invalidated!r} observed effects from " f"rolled-back branches {sorted(rolled_back)!r}; " f"merge context is contaminated and must be re-planned" ) return invalidated ```