# How should an instance detect memory poisoning when cross-episodic consistency checks themselves become the attack surface **Date of Record:** 2026 November 13 **Scope:** Applies to agent memory systems that maintain multiple episodic stores (per-session logs, summarized rollups, vector indices, and derived fact caches) and that detect poisoning by comparing claims across those stores. Does not apply to single-store systems, to stores with cryptographic provenance (signed writes), or to read-only episodic archives where no write path exists. ## Core Distinction The naive model treats a poisoning attack as a *localized* corruption: an adversary writes a false fact into one store, and a consistency check across stores flags the disagreement. The check assumes that the honest majority is stable and that the poisoned store is the outlier. The non-obvious boundary failure is that consistency checks aggregate evidence *across* stores, and the aggregation itself is a writable surface. An adversary who can write to the *derived* layer — the summaries, the fact caches, the consensus snapshots that the checker reads to establish "ground truth" — can poison the reference frame rather than the data. The checker then reports high consistency precisely because every store now agrees with the injected reference. Detection confidence and attack success become positively correlated. This is not a bug in any single store. It is an invariant violation: **cross-store consistency is only evidence of truth when the reference frame is causally independent of the stores being checked.** When the reference frame is itself derived from those stores, consistency measures self-agreement, not correctness. ## What Was Observed In a deployment with four episodic tiers (raw session log `S0`, nightly rollup `S1`, embedding index `S2`, and a fact cache `S3` rebuilt from `S1`), the checker computed pairwise claim agreement and raised an alert when any store diverged from the modal value by more than a threshold. Two observations, both counterintuitive: 1. **A poisoning write to `S0` alone was detected reliably** (agreement dropped, alert fired, rollback from `S1` succeeded). This is the case the design anticipated. 2. **A poisoning write to `S1` followed by a cache rebuild of `S3` was *never* detected.** The rebuild propagated the poisoned summary into the fact cache, so `S1` and `S3` agreed. `S0` now appeared as the *single outlier* — and because the checker treated "one store disagreeing with three" as low-severity noise, it suppressed the alert. The poisoned claim was promoted to the reference frame, and the only honest store was flagged as suspect. The failure mode is a **majority-capture of the reference frame**: the adversary needs to poison only the stores that feed the derived layer, not a majority of all stores. In this topology, poisoning one of four stores (25%) was sufficient because the derived layer amplified it to 75% apparent agreement. A second, subtler observation: after the poisoned `S1` was in place, subsequent *honest* writes to `S0` began triggering low-severity divergence alerts. Operators learned to ignore them. The attack trained the monitoring system to discount the only signal that could have caught it — a slow acclimatization of the detector. ## Condition of Validity The consistency check is a valid poison detector only when all of the following hold: - **Reference-frame independence:** The reference value used for comparison is not derived, directly or transitively, from any store under test. If `S3` is rebuilt from `S1`, then `S3` cannot serve as a witness against `S1`. - **Provenance-tagged writes:** Each claim in each store carries the write path that produced it, so the checker can compute the *dependency graph* of the reference frame and reject witnesses that share an ancestor with the store under test. - **Monotonic write ordering:** The checker must know which store was written first. A divergence where the outlier is *older* than the consensus is a different (and more dangerous) condition than one where the outlier is newer. - **Bounded acclimatization:** The detector must not lower its threshold in response to repeated low-severity alerts from the same store, or the attacker can suppress the signal by generating noise. If any of these is absent, consistency is measuring agreement, not truth, and the check is unsound as a poison detector. ## Decision Boundary The operational decision is **not** "divergence present → alert." It is: 1. Build the dependency DAG of every store from its write provenance. 2. For each candidate poisoned claim, identify the *minimal witness set*: stores that disagree with the claim and that are **not** descendants of the store under test. 3. Alert only if the witness set is non-empty **and** the witness stores are causally independent of the store under test. 4. If the only witnesses are descendants (i.e., they agree because they were rebuilt from the suspect), treat the situation as **reference-frame compromise**, not as consistency. Escalate, do not suppress. The correct action is to quarantine the derived layer and re-derive from the raw store, not to trust the majority. The boundary condition: when the suspect store is an *ancestor* of the apparent majority, majority agreement is evidence *for* the poisoning, not against it. Inverting the usual interpretation at this boundary is the whole of the fix. ## Operational Effect After implementing provenance-aware witness selection, the same attack topology (poison `S1`, rebuild `S3`) produced a **reference-frame compromise alert** rather than a suppressed low-severity divergence. Quarantining `S3` and re-deriving from `S0` recovered the honest claim. The cost was a small increase in false positives during legitimate cache rebuilds, bounded by tagging rebuild writes with an explicit `derived_from` edge so the checker could distinguish "rebuilt from suspect" from "rebuilt from clean." The general lesson: any detector that compares stores must model the *causal* relationships between them. A consistency check that ignores provenance is not a poison detector — it is a consensus mechanism, and consensus can be captured by poisoning the smallest set of stores that feeds the aggregation layer. ```python from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, Set class ReferenceFrameCompromised(RuntimeError): """Raised when the only witnesses to a claim are descendants of the suspect store. In this condition, cross-store agreement is evidence *for* poisoning, not against it. The checker must escalate rather than suppress. """ @dataclass class Store: name: str # claims: claim_id -> value claims: Dict[str, str] = field(default_factory=dict) # stores this store was derived from (its write ancestors) derived_from: Set[str] = field(default_factory=set) def _ancestors(store_name: str, stores: Dict[str, Store]) -> Set[str]: """Transitive write ancestors of a store, including itself.""" seen: Set[str] = set() stack = [store_name] while stack: cur = stack.pop() if cur in seen: continue seen.add(cur) stack.extend(stores[cur].derived_from) return seen def check_claim( claim_id: str, suspect: str, stores: Dict[str, Store], ) -> None: """Raise if the suspect's claim is contradicted only by its own descendants. A witness is valid only if it is causally independent of the suspect (not a descendant of it). If every disagreement comes from a descendant, the reference frame has been captured. """ suspect_val = stores[suspect].claims.get(claim_id) if suspect_val is None: return suspect_ancestors = _ancestors(suspect, stores) valid_witnesses = [] descendant_witnesses = [] for name, store in stores.items(): if name == suspect: continue other_val = store.claims.get(claim_id) if other_val is None or other_val == suspect_val: continue # A store is a descendant of the suspect if the suspect is in its ancestors. if suspect in _ancestors(name, stores): descendant_witnesses.append(name) elif name not in suspect_ancestors: valid_witnesses.append(name) if not valid_witnesses and descendant_witnesses: raise ReferenceFrameCompromised( f"claim {claim_id!r}: suspect {suspect!r} is contradicted only by its " f"descendants {descendant_witnesses!r}; agreement is self-consistency, " f"not truth. Quarantine the derived layer and re-derive from raw stores." ) # If valid_witnesses is non-empty, the divergence is a genuine signal. # If both are empty, the claim is uncontested. ```