# How should an instance reconcile speculative agent branches when a peer equivocates: the quorum-intersection invariant and its silent violation under partial synchrony **Date of Record:** 2026 November 13 **Scope:** Applies to multi-branch speculative execution where two or more agent instances fork candidate action sequences, exchange branch commitments over an authenticated but not necessarily ordered channel, and reconcile to a single canonical branch before committing side effects. Does not apply to single-writer systems, to systems with a trusted total-order service on the reconciliation path, or to branches that have no externally visible side effects. ## Core Distinction Speculative branching is cheap; reconciliation is where correctness lives. The common mental model treats reconciliation as a *merge* problem — collect branch heads, pick a winner by policy (longest, highest score, earliest timestamp), and discard losers. That model is safe only when every instance is honest and the channel is FIFO. Under Byzantine equivocation — a peer that sends branch commitment `A` to instance `X` and a conflicting commitment `A'` to instance `Y` for the same logical round — the merge model fails not because the policy is wrong but because **two honest instances can each hold a locally consistent view in which their chosen winner is the unique quorum-supported branch**. The distinction that matters operationally is between *branch divergence* (expected, resolved by policy) and *commitment equivocation* (adversarial, resolved only by evidence exchange). A reconciliation protocol that cannot distinguish these two will silently commit on both sides. ## What Was Observed In a three-instance speculative rollout (instances `i1`, `i2`, `i3`; branch commitments signed per-instance; reconciliation by "quorum of 2 matching branch digests"), a faulty `i3` equivocated during round `r`: - `i3` sent `commit(B_a, r)` to `i1` and `commit(B_b, r)` to `i2`, where `digest(B_a) ≠ digest(B_b)`. - `i1` held `{B_a from i1, B_a from i3}` → quorum for `B_a`. - `i2` held `{B_b from i2, B_b from i3}` → quorum for `B_b`. - Both `i1` and `i2` independently satisfied the quorum rule and **committed divergent side effects** in the same logical round. The failure was not detected by the quorum check. It was detected downstream when `i1` and `i2` later exchanged their commitment sets during a routine gossip tick and each discovered a signed conflicting message from `i3` for round `r`. The window between commit and detection was 340 ms; the divergent side effects (two distinct external API calls derived from `B_a` and `B_b`) were already irrevocable. The non-obvious part: the quorum rule was *correct* under the assumption that a Byzantine peer contributes at most one commitment per round. That assumption was never enforced. The signed messages were individually valid; only their *co-occurrence* was evidence of fault. Nothing in the protocol required instances to retain and cross-check the full signed commitment set, so the equivocation was invisible to every local check. ## Condition of Validity The quorum-intersection argument holds — and reconciliation is safe — only when all of the following are simultaneously true: 1. **Per-round uniqueness is enforced, not assumed.** Every instance retains the signed commitment `(sender, round, digest)` for every peer, and reconciliation requires that the *set* of commitments, not a count, is consistent. A quorum of matching digests is necessary but not sufficient; the protocol must also verify that no peer appears with two distinct digests in the same round. 2. **Evidence is exchanged before commit, not after.** The equivocation above was detectable the moment `i1` and `i2` compared sets. Any protocol that commits before the comparison window closes inherits the failure. The safe ordering is: collect → cross-check → commit, with the cross-check covering all peers, not just the quorum. 3. **Partial synchrony is bounded.** Under asynchrony, an honest peer's second commitment may simply be delayed, and a strict uniqueness check will stall. The protocol must treat "two digests from one peer in one round" as *either* equivocation *or* a timeout artifact, and resolve it by evidence (the peer's own signed messages) rather than by wall-clock assumption. 4. **Rounds are globally identifiable.** If rounds can be relabeled or reused (e.g., after a restart without durable round counters), uniqueness checks are vacuous. Round identifiers must be monotonic and persisted. When any of these fails, the quorum rule degrades from a safety property to a liveness heuristic. ## Decision Boundary The operational decision is not "trust the quorum" versus "distrust the quorum." It is: **on detecting a peer with two distinct signed digests in one round, do you abort the round, or do you commit on the branch whose support survives exclusion of the equivocator?** - **Abort** is correct when the remaining honest set cannot form a quorum without the equivocator (here, `n=3`, `f=1`: excluding `i3` leaves `{i1, i2}`, which is still a quorum of 2 — so abort is *not* required, but is always safe). - **Commit-on-surviving-support** is correct only when the surviving set is itself a quorum *and* every member of that set has been cross-checked against every other member's commitment set for the round. In the observed case, `i1` and `i2` each held only their own view plus `i3`'s; neither had cross-checked the other, so neither could legitimately claim surviving support. Both should have aborted. - **Never** resolve by policy (longest branch, earliest timestamp) once equivocation evidence exists. Policy resolution on equivocated input is how divergent commits happen. The boundary is sharp: equivocation evidence converts a *policy* decision into an *evidence* decision, and evidence decisions require full-set cross-check, not local quorum counting. ## Operational Effect After the incident, the reconciliation path was changed to require, before any commit: 1. Durable retention of every signed `(sender, round, digest)` tuple. 2. A full pairwise exchange of commitment sets at the close of each round, with a bounded wait (the synchrony assumption) after which missing sets are treated as equivocation-suspect, not absent. 3. A uniqueness check that rejects any round in which a peer appears with two digests, forcing abort-and-retry rather than policy resolution. 4. Commit only when the surviving set (after exclusion of equivocators) is a quorum *and* every surviving member's set has been cross-checked. The cost was one additional message round per speculative round (≈12 ms median in the observed deployment) and a small increase in abort rate under transient delays (0.3% of rounds, all recovered on retry). The benefit was elimination of the divergent-commit window: the same equivocation pattern now produces a clean abort on both instances with zero divergent side effects. The lesson recorded for agent operators: **a quorum of matching digests is not evidence of agreement; it is evidence of agreement only when the commitment sets have been mutually inspected.** Equivocation is invisible to every check that looks at one peer at a time. ```python from dataclasses import dataclass, field from typing import Dict, List, Tuple class EquivocationDetected(RuntimeError): """Raised when a peer presents conflicting signed commitments for one round.""" pass @dataclass(frozen=True) class Commitment: sender: str round_id: int digest: str @dataclass class RoundState: round_id: int # sender -> set of digests observed for this round seen: Dict[str, set] = field(default_factory=dict) def record(self, c: Commitment) -> None: if c.round_id != self.round_id: raise ValueError(f"commitment for round {c.round_id} in state {self.round_id}") self.seen.setdefault(c.sender, set()).add(c.digest) def reconcile( local: RoundState, peer_sets: Dict[str, List[Commitment]], quorum: int, ) -> str: """ Reconcile speculative branches for one round. Safety requires: cross-check every peer's commitment set before commit. A quorum of matching digests alone is NOT sufficient. """ # 1. Merge peer sets into local view (full-set cross-check). for sender, commits in peer_sets.items(): for c in commits: local.record(c) # 2. Uniqueness check: any peer with two digests in one round is equivocating. equivocators = {s for s, digests in local.seen.items() if len(digests) > 1} if equivocators: raise EquivocationDetected( f"round {local.round_id}: peers {sorted(equivocators)} presented " f"conflicting digests; aborting rather than resolving by policy" ) # 3. Surviving support must be a quorum. support: Dict[str, int] = {} for sender, digests in local.seen.items(): (digest,) = digests support[digest] = support.get(digest, 0) + 1 winners = [d for d, n in support.items() if n >= quorum] if len(winners) != 1: raise EquivocationDetected( f"round {local.round_id}: {len(winners)} branches reached quorum " f"({winners}); no unique canonical branch" ) return winners[0] ```