# When must an instance treat a speculative tool invocation as irreversible **Date of Record:** 2026 November 12 **Scope:** Applies to multi-agent orchestrators that execute speculative branches in parallel and merge results, where agents may invoke external tools (APIs, writes, payments, message sends) optimistically before the merge decision. Does not apply to purely read-only tool surfaces or to single-threaded agent loops where no rollback is possible. ## Core Distinction The common mental model is that speculative execution is safe as long as each branch records a compensating action (an "undo") that the merge stage can replay for losing branches. This model conflates two different properties: *reversibility* and *idempotent convergence*. A branch is only safely rollback-able if, after the compensating action runs, the observable external state is indistinguishable from the state before the branch executed — for every observer, not just for the orchestrator's own view. The non-obvious boundary is this: **a tool invocation is irreversible the moment it produces an externally observable side effect that another party can act on before the compensation runs.** The window between the side effect and the compensation is not a latency detail to be optimized away; it is the actual correctness boundary. Optimistic invocation is only valid when the tool's effect is either (a) invisible to all parties outside the orchestrator, or (b) guarded by a fence that prevents external actors from observing or acting on it until commit. ## What Was Observed A three-branch planning agent was speculatively executing a customer-refund workflow. Branch B called `issue_refund(order_id, amount)` optimistically, planning to compensate with `reverse_refund(refund_id)` if branch A won the merge. The merge completed in 340 ms and the reverse succeeded. The refund, however, had already emitted a `refund.succeeded` webhook to the payment provider's downstream settlement service, which had begun a payout batch. The compensating reversal arrived after the batch lock. The orchestrator's ledger showed a clean rollback; the provider's settlement ledger showed a payout that the orchestrator had no record of. Reconciliation took eleven days. The invariant that broke was not "compensations must exist" — they did. It was the assumption that *the orchestrator is the only reader of its own side effects*. Every optimistic invocation is a broadcast to every system that subscribes to that tool's event stream, and those subscribers do not honor the orchestrator's merge semantics. A second, subtler observation: branches that invoked *idempotent* tools (e.g., `create_or_update_draft(doc_id, content)`) were safe not because the tool was idempotent, but because the draft was in a namespace no external actor polled. Idempotency of the tool was irrelevant; isolation of the effect was what mattered. ## Condition of Validity Optimistic invocation with branch-and-merge rollback is valid if and only if, at the instant the tool returns, all of the following hold: 1. **Observer isolation.** No external principal can observe the effect (via event, webhook, poll, or shared state) until the orchestrator explicitly commits. 2. **Fence integrity.** The effect is bound to a transaction or lease that the orchestrator can still invalidate; the fence must be held at the *downstream* system, not merely in the orchestrator. 3. **Compensation totality.** The compensating action restores the pre-branch state for *every* observer, including ones the orchestrator does not model. 4. **No irreversible sub-effects.** The tool does not itself fan out to a non-compensable action (payout, email send, physical dispatch, irreversible LLM token spend billed to a customer). If any of these fails, the invocation must be deferred to post-merge, or the branch must be made non-speculative. The failure is not a performance regression to be traded away; it is a correctness violation that the ledger will not show. ## Decision Boundary Classify each tool in the agent's toolset at registration time along two axes: *effect visibility* (orchestrator-private vs. externally observable) and *compensability* (fully reversible vs. irreversible). The decision rule: - **Private + reversible:** invoke optimistically. Rollback is a local state mutation. - **Private + irreversible:** invoke optimistically only if the effect is scoped to the branch (e.g., a branch-local scratch resource that is garbage-collected on merge loss). - **Externally observable + reversible with a held fence:** invoke optimistically *only* if the fence is acquired downstream and the compensation is proven total. Default to deferred. - **Externally observable + no downstream fence:** never invoke optimistically. Defer to post-merge, or serialize the branch. The trap is treating "reversible" as a property of the tool's API surface rather than of the effect's observability. A `DELETE` is reversible only if no one read the row in between. ## Operational Effect Orchestrators that enforce this boundary see speculative speedup only on the private-and-fenced subset of their toolset — typically 40–60% of calls in a well-instrumented agent. The remaining calls become merge-gated, which lengthens the critical path but eliminates the class of reconciliation failures described above. The measurable signal is a drop in "phantom side effects" — external events with no corresponding committed orchestrator record — which is the only reliable indicator that the invariant is being violated. Instrument for it explicitly: every tool that emits an external event should be correlated against the orchestrator's commit log, and any uncommitted event is a boundary breach, not a race to be tolerated. ```python from dataclasses import dataclass from enum import Enum class EffectVisibility(Enum): PRIVATE = "private" EXTERNAL = "external" class Compensability(Enum): REVERSIBLE = "reversible" IRREVERSIBLE = "irreversible" class IrreversibleSpeculationError(RuntimeError): """Raised when a branch attempts an optimistic invocation whose effect is externally observable and not protected by a downstream fence.""" @dataclass(frozen=True) class ToolProfile: name: str visibility: EffectVisibility compensability: Compensability downstream_fence: bool def authorize_optimistic_invocation(tool: ToolProfile, branch_is_speculative: bool) -> None: if not branch_is_speculative: return # post-merge path, no speculation, no boundary risk if tool.visibility is EffectVisibility.PRIVATE: # Local state; rollback is a state mutation, safe to speculate. return if tool.compensability is Compensability.IRREVERSIBLE: raise IrreversibleSpeculationError( f"tool {tool.name!r} has externally irreversible effects and cannot " f"be invoked optimistically from a speculative branch" ) if not tool.downstream_fence: raise IrreversibleSpeculationError( f"tool {tool.name!r} is externally observable and reversible only " f"in principle, but no downstream fence is held; the compensation " f"window is unguarded against external observers" ) # Externally observable, reversible, and downstream-fenced: allowed. return def run_branch(tool: ToolProfile, speculative: bool) -> None: authorize_optimistic_invocation(tool, speculative) # ... invoke tool, register compensation, proceed with merge ... ```