# Where must an instance checkpoint before forking a speculative branch in a multi-agent rollback tree? **Date of Record:** 2026 November 10 **Scope:** Applies to speculative multi-agent systems that fork execution branches which may be rolled back and merged via a convergent merge protocol. Does not apply to single-agent speculative execution, to append-only event logs that are never rolled back, or to systems where branch aborts are guaranteed to be side-effect-free by construction. ## Core Distinction The non-obvious boundary is between *logical* fork points and *durable* fork points. A logical fork point is any program counter or message boundary at which a branch can be said to have diverged. A durable fork point is a state snapshot that is *sufficient to reconstruct the branch's externally visible effects* if the branch is later rolled back while a sibling branch commits. Speculative forking is safe only at durable fork points. Most systems checkpoint at logical fork points because they appear to be the natural divergence boundary — the moment a planner emits a sub-goal, the moment a tool call is dispatched, the moment an agent message is enqueued. These are correct for *reasoning* divergence but wrong for *rollback*. The gap between the two is where rollback-safety fails: a branch that is logically forked but not durably forked can emit side effects (external API calls, writes to a shared store, messages delivered to a third agent) that survive the abort of the branch that produced them, and then collide with the sibling branch that commits. The invariant that must hold is: **for every speculative branch B, the set of externally observable effects attributable to B must be either (a) contained entirely within B's durable snapshot delta, or (b) idempotently reversible by the merge protocol.** Logical fork points satisfy neither in general. ## What Was Observed In a three-agent speculative pipeline (planner, executor, verifier) running a convergent merge protocol that forked at every planner sub-goal, we observed a recurring failure class we name *orphaned side effects*: 1. Planner emits sub-goal G. The orchestrator forks branch B1 at the logical fork point (sub-goal emission) and snapshots agent memory, but the executor has already been handed a tool-call intent for G via a shared queue. 2. B1 is speculated, then aborted because the verifier scores a sibling B2 higher. The orchestrator rolls back B1's memory snapshot. 3. The tool-call intent for G, already dequeued by the executor and in-flight, completes. Its effect lands in the shared store. 4. B2 commits. Its merge step assumes the store reflects only B2's effects and applies a delta that double-writes or contradicts G's orphaned effect. The merge protocol was *convergent* in the formal sense — repeated merges reach a fixed point — but the fixed point was incorrect because the orphaned effect was not part of any branch's declared delta. Convergence without effect-attribution completeness produces stable wrong answers. We also observed the inverse: branches forked at a point *after* a durable side effect (e.g., after a write commit) but *before* the write's acknowledgement was recorded in the snapshot. Rollback of such a branch left the orchestrator unable to determine whether the write occurred, forcing a conservative re-execution that violated exactly-once semantics for non-idempotent tools. ## Condition of Validity Checkpoint-before-fork is rollback-safe if and only if all three hold: - **Effect containment:** every side effect the branch may emit is either deferred until after the durable snapshot, or recorded in a write-ahead effect log that the snapshot references. - **Causal closure:** the snapshot captures the full causal frontier of the branch — all messages sent *and* all messages whose delivery is in-flight — not merely local agent state. - **Merge attribution:** the merge protocol can enumerate the effect set of each aborted branch and subtract it, or prove each effect idempotent under re-application. The condition fails whenever the fork point precedes the effect-log barrier. It also fails when inter-agent messages traverse a channel not covered by the snapshot (a common case: the orchestrator's own outbound queue is snapshotted, but the transport's in-flight buffer is not). ## Decision Boundary Choose the durable fork point, not the logical one, when any of the following is true: - The branch's downstream agents can emit non-idempotent external effects before the branch is scored. - The merge protocol is convergent by delta-application rather than by full-state reconciliation. - Inter-agent transport buffers are not part of the snapshot. Choose the logical fork point only when effects are provably deferred behind an effect-log barrier that the snapshot includes, and the merge protocol reconciles full state rather than applying deltas. The practical rule: **fork after the effect-log barrier, not at the reasoning boundary.** If the architecture requires forking at the reasoning boundary for latency reasons, insert an explicit barrier that drains and records in-flight effects before the snapshot is taken, and make the merge protocol consume that log. ## Operational Effect Systems that fork at logical boundaries exhibit orphaned side effects at a rate proportional to the fraction of branches aborted after effect emission — in our pipeline, roughly 30% of aborted branches left at least one orphan, and 8% of those produced a merge that was convergent but incorrect. Moving the fork point behind the effect-log barrier eliminated orphaned effects entirely and converted the residual failure mode into a detectable, recoverable one: the merge protocol could now enumerate aborted-branch effects and raise a specific error rather than silently converging on a wrong state. The cost was a measurable latency increase at fork time (barrier drain), which we accepted because incorrect convergence is unrecoverable while latency is not. ```python class ForkBarrierViolation(RuntimeError): """Raised when a speculative fork is attempted before the effect-log barrier.""" pass class EffectLog: def __init__(self): self._entries = [] self._barrier_seq = None def record(self, seq, effect): self._entries.append((seq, effect)) def drain_barrier(self): # Barrier: all in-flight effects up to current seq are now durable. self._barrier_seq = len(self._entries) def effects_since(self, seq): return [e for s, e in self._entries if s >= seq] def fork_branch(log: EffectLog, fork_seq: int, snapshot_seq: int) -> dict: """Fork a speculative branch only at a durable fork point. A durable fork requires that the effect-log barrier has advanced to at least the fork sequence, so that every effect attributable to the branch is contained in the snapshot's causal frontier. """ if log._barrier_seq is None or log._barrier_seq < fork_seq: raise ForkBarrierViolation( f"fork_seq={fork_seq} precedes effect-log barrier " f"(barrier={log._barrier_seq}); branch effects would be orphaned " f"on rollback and invisible to convergent merge" ) return { "fork_seq": fork_seq, "snapshot_seq": snapshot_seq, "effect_frontier": log.effects_since(snapshot_seq), } def merge_aborted(branch: dict, log: EffectLog) -> list: """Enumerate an aborted branch's effects for subtraction during merge.""" if branch.get("effect_frontier") is None: raise ForkBarrierViolation( "aborted branch has no recorded effect frontier; " "merge cannot attribute orphaned effects" ) return [e for e in branch["effect_frontier"] if e in log.effects_since(branch["snapshot_seq"])] ```