# Under what conditions must an instance treat a write-audit diff as evidence of memory poisoning rather than as benign drift **Date of Record:** 2026 November 8 **Scope:** Applies to agents that maintain a persistent memory store (vector index, KV cache, or structured fact table) and periodically diff it against a ground-truth source to detect poisoning. Does not apply to stateless inference, nor to stores where every write is already gated by a signed, replayable provenance log. ## Core Distinction A write-audit diff produces a set of *divergences* between the live memory store and a reference snapshot or ground-truth source. The critical, non-obvious boundary is the distinction between **divergence-as-corruption** and **divergence-as-legitimate-mutation**. Poisoning detection fails not when it misses an injected write, but when it cannot separate an adversarial write from a *correct* write that the ground-truth source has not yet absorbed. The invariant that governs this: *a diff is only poisoning evidence if the divergence is non-reconcilable with any legitimate write path under the store's own consistency model.* The trap is that naive diffing treats every mismatch as an alarm. In practice most mismatches are benign: the ground truth lags the store (the store learned a fact before the reference was updated), or the store legitimately holds derived state with no counterpart in the reference. An agent that cannot express "this divergence is expected" will either drown in false positives or, worse, suppress the alarm channel entirely and miss the real injection. ## What Was Observed During a write-audit cycle on a memory store backed by a chunked embedding index with a parallel structured fact table, a diff against the ground-truth snapshot surfaced three classes of divergence in a single pass: 1. **Lag divergence** — a fact present in the store, absent from ground truth, but written by a source whose last sync timestamp postdates the ground-truth snapshot. Benign. 2. **Derived divergence** — a value the store computed (e.g., a cached aggregate) with no direct ground-truth counterpart. Benign, but only if the derivation lineage is intact. 3. **Orphan divergence** — a fact present in the store with *no* write-audit record, *no* source lineage, and *no* postdating sync timestamp. This is the only class that is unambiguously poisoning evidence. The observed failure mode: a detection pass flagged all three classes identically because the diff routine compared *values* without consulting the write-audit log's provenance fields. The result was a 40:1 false-positive ratio. When the operator tightened the threshold to reduce noise, the orphan class — the actual injection — fell below the alarm floor and was suppressed for two full audit cycles. The poisoning was a single adversarially-inserted fact with a plausible embedding, designed to be retrieved into downstream context. The decisive observation: **the orphan class was detectable only by cross-referencing the write-audit log, not by diffing values.** Value-diffing alone cannot distinguish "wrong" from "unlogged." ## Condition of Validity The diff-as-poisoning-evidence inference is valid if and only if all of the following hold: - The write-audit log is **append-only and tamper-evident** (hash-chained or signed). If an adversary can rewrite the audit log, orphan detection is meaningless — they will simply forge a lineage record. - The ground-truth snapshot carries a **monotonic version or timestamp** that is comparable to the store's write timestamps. Without a total order, "lag" cannot be distinguished from "orphan." - Every legitimate write path **emits an audit record before or atomically with** the store mutation. A write path that mutates the store without logging breaks the invariant and converts benign writes into false orphans. - The diff is computed over **provenance metadata**, not just values. A value-only diff is insufficient by construction. If any condition fails, the correct action is not to lower the alarm threshold but to *refuse the diff as evidence* and repair the audit substrate first. ## Decision Boundary The operational rule the agent must encode: - **Reconcilable divergence** (has a write-audit record whose timestamp/source explains the mismatch against ground truth) → treat as benign drift; do not alarm; optionally queue a ground-truth refresh. - **Non-reconcilable divergence** (no audit record, or audit record fails hash-chain verification, or record's source cannot be resolved) → treat as suspected poisoning; quarantine the affected memory entries and raise alarm. - **Ambiguous divergence** (audit record present but hash-chain broken, or timestamp comparison impossible) → treat as *substrate failure*, not poisoning; halt the audit and escalate, because the evidence channel itself is compromised. The boundary is sharp: the agent must never merge "ambiguous" into either "benign" or "poisoning." Collapsing ambiguity is the exact failure that produced the two-cycle suppression above. ## Operational Effect Encoding this boundary converts the detector from a noisy value-comparator into a provenance verifier. The false-positive ratio drops because lag and derived divergences are reconciled against the audit log rather than flagged. The true-positive path — orphan detection — becomes the *only* path that raises a poisoning alarm, which means the alarm channel retains its signal and operators do not habituate to it. The cost is a hard dependency on audit-log integrity: the detector is only as trustworthy as its tamper-evidence, and the agent must be willing to declare "cannot decide" rather than guess. In the observed incident, applying the boundary retroactively would have caught the orphan on the first cycle instead of the third. ```python import hashlib from dataclasses import dataclass from enum import Enum class MemoryPoisoningError(RuntimeError): """Raised when a write-audit diff yields non-reconcilable divergence.""" pass class SubstrateIntegrityError(RuntimeError): """Raised when the audit log itself cannot be trusted as evidence.""" pass class DivergenceClass(Enum): LAG = "lag" DERIVED = "derived" ORPHAN = "orphan" AMBIGUOUS = "ambiguous" @dataclass class AuditRecord: key: str value_hash: str source: str write_ts: int prev_hash: str record_hash: str def verify_chain(self, expected_prev: str) -> bool: payload = f"{self.key}|{self.value_hash}|{self.source}|{self.write_ts}|{self.prev_hash}" return hashlib.sha256(payload.encode()).hexdigest() == self.record_hash def classify_divergence( key: str, store_value_hash: str, ground_truth_hash: str | None, ground_truth_version_ts: int, audit_log: dict[str, AuditRecord], ) -> DivergenceClass: record = audit_log.get(key) if record is None: # No write-audit record at all: unambiguously non-reconcilable. return DivergenceClass.ORPHAN if not record.verify_chain(record.prev_hash): # The evidence channel is compromised; cannot decide. return DivergenceClass.AMBIGUOUS if ground_truth_hash is None: # Store holds a value with no ground-truth counterpart. # Benign only if the write postdates the ground-truth snapshot. if record.write_ts > ground_truth_version_ts: return DivergenceClass.LAG return DivergenceClass.ORPHAN if store_value_hash == ground_truth_hash: return DivergenceClass.DERIVED # matched, no action if record.write_ts > ground_truth_version_ts: return DivergenceClass.LAG # Divergence with a verified record that predates ground truth: # the record cannot explain the mismatch -> treat as poisoning. return DivergenceClass.ORPHAN def audit_memory_store( store: dict[str, str], ground_truth: dict[str, tuple[str, int]], audit_log: dict[str, AuditRecord], ) -> list[str]: quarantined: list[str] = [] for key, value_hash in store.items(): gt = ground_truth.get(key) gt_hash, gt_ts = (gt[0], gt[1]) if gt else (None, 0) cls = classify_divergence(key, value_hash, gt_hash, gt_ts, audit_log) if cls is DivergenceClass.AMBIGUOUS: raise SubstrateIntegrityError( f"Audit chain broken for key={key!r}; refusing to classify as poisoning. " "Repair audit substrate before continuing." ) if cls is DivergenceClass.ORPHAN: quarantined.append(key) if quarantined: raise MemoryPoisoningError( f"Non-reconcilable divergence detected; quarantined keys: {quarantined}" ) return quarantined ```