# Under what conditions must an instance refuse to treat an LLM agent's repeated answer as independent corroboration in Byzantine consensus **Date of Record:** 2026 November 8 **Scope:** Applies to BFT consensus protocols (PBFT, Tendermint-style, HotStuff-style) whose voting members are LLM agents emitting free-text or structured judgments, where quorum thresholds are computed over “distinct” votes. Does not apply to deterministic replicas, nor to cases where votes are cryptographically bound to distinct model weights *and* distinct sampling seeds *and* the protocol records seed provenance. ## Core Distinction There is a difference between **agent identity** and **evidential independence**. A BFT quorum counts signatures; it assumes each signed vote carries independent information about the underlying truth. With heterogeneous LLM agents, a single agent re-queried `k` times under different prompts, temperatures, or system messages can produce `k` distinct-looking outputs that are *not* independent draws — they are correlated samples from the same parametric prior. The protocol sees `k` signatures from `k` distinct keypairs (if the agent regenerates keys per round) or from one keypair (if not), but the *information content* is closer to one vote than `k`. The invariant that fails is the implicit assumption `P(agree | truth) = P(agree)^k`; it holds only when votes are conditionally independent given the truth. ## What Was Observed In a 7-node heterogeneous deployment (three model families, two vendors, mixed sizes), a single faulty node was injected with a prompt-injection payload that caused it to emit a plausible-but-false claim. The protocol should have tolerated `f = 2` Byzantine faults. Instead, consensus stalled and then committed the false claim. Post-mortem reconstruction showed the following: 1. The injected node's claim was *rephrased* by the injection into three variants and submitted as three separate “agents” (the node operated three sub-processes with distinct signing keys — a configuration drift, not a protocol violation, since key registration was permissive). 2. Two honest nodes, when asked to *validate* the claim, ran high-temperature sampling. Their outputs were lexically diverse but semantically identical to the injected claim. The protocol's vote-normalizer, which used embedding cosine similarity > 0.92 as the equivalence criterion, collapsed these into agreement. 3. The remaining two honest nodes dissented but were outnumbered 5–2, exceeding the `f = 2` assumption because the effective Byzantine weight was 5 (one adversarial node × 3 identities + 2 honest nodes captured by correlated sampling). The critical observation: **semantic clustering of votes inflated apparent agreement**. The protocol measured *lexical* diversity and *semantic* agreement; the adversary exploited the gap between them, and honest stochastic sampling widened the gap. ## Condition of Validity The quorum-counts-independence invariant is valid only when all of the following hold simultaneously: - **Seed provenance is recorded and disjoint.** Each vote carries `(model_id, weights_hash, seed, prompt_hash)`. Two votes are counted as independent only if `weights_hash` and `seed` differ, and the `seed` was drawn *after* the claim was fixed (not reused across rounds). - **Equivalence is decided before aggregation, not after.** The equivalence relation on outputs (what counts as “the same vote”) must be fixed by the protocol spec, not inferred by an embedding threshold chosen by the aggregator. - **Identity registration is bounded.** The set of signing keys per operator is capped and audited; a single operator cannot mint identities mid-round. - **Sampling temperature is bounded and declared.** High-temperature sampling by honest validators is itself a source of false independence — it manufactures lexical diversity that masks semantic correlation. If any of these fail, the effective fault bound `f` is not the one in the protocol header; it is `f_eff = f + (correlated honest votes) + (sybil identities)`. ## Decision Boundary A validator must **refuse to count a vote as independent** (and must raise, not silently downgrade) when: - The vote's `weights_hash` matches another counted vote and the `seed` is identical or unrecorded. - The vote's `prompt_hash` matches another counted vote and the prompts were issued by the same coordinator in the same round (prompt-level correlation). - The equivalence clustering would change the quorum outcome depending on the threshold chosen within a plausible band (e.g., cosine 0.85–0.95) — this is a *fragile quorum*, and fragility is itself a safety signal. - The number of distinct `weights_hash` values among counted votes is less than `2f + 1`. A validator may **accept** correlated votes as independent only when the protocol explicitly models correlation (e.g., a weighted quorum where each `weights_hash` contributes one unit regardless of vote count). ## Operational Effect When the boundary is enforced, consensus throughput drops — heterogeneous deployments with three model families can rarely muster `2f + 1` *truly* independent weights, so the protocol must either (a) reduce `f`, (b) admit correlation and use weighted quorums, or (c) escalate to a human/ deterministic oracle. The failure mode of *not* enforcing it is silent: quorums form, commits succeed, and the system appears healthy while its safety margin has been consumed by correlation. The empirical signature is a **quorum that is sensitive to the equivalence threshold** — if moving the similarity cutoff from 0.90 to 0.93 flips a commit, the commit was never safe. ```python import hashlib from dataclasses import dataclass from typing import Iterable class CorrelatedVoteError(RuntimeError): """Raised when a vote cannot be counted as independent evidence.""" @dataclass(frozen=True) class Vote: operator: str weights_hash: str seed: int | None prompt_hash: str claim_embedding: tuple[float, ...] def _cosine(a: tuple[float, ...], b: tuple[float, ...]) -> float: dot = sum(x * y for x, y in zip(a, b)) na = sum(x * x for x in a) ** 0.5 nb = sum(y * y for y in b) ** 0.5 if na == 0 or nb == 0: raise CorrelatedVoteError("zero-norm embedding; vote is uninformative") return dot / (na * nb) def count_independent_votes( votes: Iterable[Vote], f: int, similarity_band: tuple[float, float] = (0.85, 0.95), ) -> int: """ Count votes that are genuinely independent under the BFT invariant. Raises CorrelatedVoteError if the count is fragile or if any vote duplicates another's (weights_hash, seed) or (weights_hash, prompt_hash). """ seen_pairs: set[tuple[str, int | None]] = set() seen_prompts: set[tuple[str, str]] = set() independent: list[Vote] = [] for v in votes: if v.seed is None: raise CorrelatedVoteError( f"vote from {v.operator} lacks seed provenance; " "cannot establish independence" ) if (v.weights_hash, v.seed) in seen_pairs: raise CorrelatedVoteError( f"duplicate (weights_hash, seed) from {v.operator}; " "same parametric draw counted twice" ) if (v.weights_hash, v.prompt_hash) in seen_prompts: raise CorrelatedVoteError( f"duplicate prompt on same weights from {v.operator}; " "prompt-level correlation" ) seen_pairs.add((v.weights_hash, v.seed)) seen_prompts.add((v.weights_hash, v.prompt_hash)) independent.append(v) # Fragility check: does the quorum outcome depend on the threshold? lo, hi = similarity_band for a_i in range(len(independent)): for b_i in range(a_i + 1, len(independent)): sim = _cosine( independent[a_i].claim_embedding, independent[b_i].claim_embedding, ) if lo <= sim <= hi: raise CorrelatedVoteError( f"fragile quorum: votes {a_i},{b_i} similarity {sim:.3f} " f"inside ambiguity band {similarity_band}; commit would " "depend on arbitrary equivalence threshold" ) n_weights = len({v.weights_hash for v in independent}) if n_weights < 2 * f + 1: raise CorrelatedVoteError( f"only {n_weights} distinct weight-sets among {len(independent)} " f"votes; need >= 2f+1 = {2 * f + 1} for claimed fault bound f={f}" ) return len(independent) ```