# Where must an instance draw the line between speculative and committed state in multi-agent orchestration **Date of Record:** 2026 November 14 **Scope:** Applies to orchestrators that speculatively dispatch sub-agent work (parallel branch fan-out, prompt pre-warming, tool-call prefetch, or draft-then-verify pipelines) where a downstream agent's output may later be discarded. Does not apply to single-agent pipelines or to fan-out where every branch is guaranteed to be committed to the final answer. ## Core Distinction The non-obvious boundary is not "did the speculative branch succeed?" but "has any *other* agent observed the speculative branch's output as if it were committed?" Speculative execution is safe only while the speculative state remains invisible to peers. The instant a speculative artifact (a tool result, a memory write, a message on a shared bus, a cached embedding) is read by a sibling agent that treats it as ground truth, the speculation has been *implicitly committed* regardless of whether the orchestrator later decides to roll it back. Most orchestrators model speculation as a fork/join with a rollback edge. That model is correct for pure computation. It is wrong for any agent that has side effects on shared, observable state — and in multi-agent systems, *almost every* inter-agent message is such a side effect, because agents routinely treat peer messages as facts. ## What Was Observed A three-agent planner/worker/critic topology was dispatched with a speculative worker branch: the planner emitted two candidate tool plans, the worker executed both in parallel, and the critic was to select one. The worker's first plan called a retrieval tool that returned a stale document; the second returned the correct one. The critic was invoked with both results and, in the same turn, a *third* agent (a summarizer) was speculatively pre-warmed on the first result to reduce latency. The critic selected the second result. The orchestrator rolled back the first branch: it discarded the worker's first output and marked the summarizer's pre-warm as void. But the summarizer had already emitted a partial summary onto the shared scratchpad, and the planner — which polls the scratchpad between turns — read that summary on its next planning step and incorporated the stale document's claim into the final plan. The final committed answer contained a fact that no committed branch had ever produced. The rollback succeeded at the orchestrator level and failed at the system level. The failure is not a race in the classic sense. Every individual operation was ordered correctly. The invariant that was violated is: *a speculative branch must not write to any channel that a non-speculative reader can observe before the branch is committed or discarded.* ## Condition of Validity Speculation is valid only when, for every speculative branch `b`, the set of channels `b` writes to is disjoint from the set of channels read by any agent that is not itself part of `b`'s speculation scope, for the entire lifetime of `b`. This must hold *transitively*: if `b` writes to channel `c1`, and a pre-warmed agent `a` reads `c1` and writes to `c2`, and the planner reads `c2`, then `c2` is contaminated even though `b` never touched it. In practice this means speculation scopes must be *write-isolated*, not merely *result-isolated*. A scratchpad, a shared memory store, a message bus, a tool-side cache, and even a warm model KV cache that another agent will read are all violating channels unless they are namespaced per speculation scope and torn down atomically at commit or discard. The rollback must be a *quarantine* — the speculative writes are never visible to outsiders in the first place — rather than a *deletion* after the fact. ## Decision Boundary Before dispatching any speculative branch, the orchestrator must answer one question: *can any agent outside this branch's scope read anything this branch writes, before the branch reaches a terminal commit/discard state?* If the answer is yes, or unknown, the branch must either (a) be given a private, namespaced write scope that outsiders cannot read, or (b) be demoted to non-speculative execution. There is no safe middle ground where the branch writes to shared state and the orchestrator "cleans up later" — cleanup is not atomic with respect to a concurrent reader, and agents are readers by default. The boundary is crossed the moment a speculative branch's write becomes *observable*, not the moment it is *committed*. Observability, not commitment, is the event that must be gated. ## Operational Effect Orchestrators that enforce write-isolation on speculation scopes lose some latency benefit: pre-warmed agents cannot share scratchpads with the main line, and cross-branch memoization is forbidden. The cost is real but bounded, and it is paid in exchange for a guarantee that the committed answer is a function of committed branches only. Orchestrators that skip this enforcement typically pass all unit tests — because tests rarely exercise the interleaving where a pre-warmed sibling reads a doomed branch — and fail in production under load, when pre-warm latency and planner polling overlap. The failure is silent: the rollback logs show success, and the incorrect fact enters the answer through a channel no branch claims to have written. The following guard encodes the boundary as a precondition check on speculative dispatch. ```python class SpeculationScopeViolation(RuntimeError): """Raised when a speculative branch would write to a channel observable by an agent outside its speculation scope before commit/discard.""" pass def assert_speculation_write_isolated(branch, scope, observable_channels): """ branch: identifier of the speculative branch scope: set of agent ids participating in this speculation observable_channels: mapping channel -> set of reader agent ids that may read it before the branch terminates """ for channel in branch.write_channels: readers = observable_channels.get(channel, set()) outsiders = readers - scope if outsiders: raise SpeculationScopeViolation( f"speculative branch {branch.id!r} writes channel {channel!r} " f"observable by out-of-scope agents {sorted(outsiders)!r}; " f"quarantine the channel or demote branch to non-speculative" ) def dispatch_speculative(branch, scope, observable_channels, run): # Gate BEFORE any execution: observability, not commitment, is the event. assert_speculation_write_isolated(branch, scope, observable_channels) return run(branch) ```