# What happens when an instance trusts write-time provenance but not mutation frequency: the silent overwrite boundary in agent memory poisoning detection **Date of Record:** 2026 November 14 **Scope:** Applies to agent memory systems that gate writes on per-entry provenance (source identity, trust tier, signing) while scoring poisoning risk from mutation-rate anomalies over a rolling window. Does not apply to append-only immutable logs, nor to systems where every write is independently re-validated against ground truth at read time. ## Core Distinction Provenance and anomaly scoring answer two orthogonal questions. Provenance answers *who wrote this and were they authorized*. Anomaly scoring answers *does this write pattern look like an attack*. The boundary failure lives in the gap: an attacker who holds (or compromises) a single high-trust provenance identity can perform a **slow overwrite** — a sequence of low-frequency, individually-authorized mutations to the same memory key — that never trips the mutation-rate detector because the rate is deliberately kept below the anomaly threshold. Each write passes provenance. The *aggregate* is the attack. The invariant that fails is the implicit assumption that "authorized writer + sub-threshold rate" implies "benign content." It does not. ## What Was Observed In a memory store with two controls — (1) write-time provenance check against a trust-tier allowlist, and (2) an EWMA anomaly score on mutations-per-key per-window with threshold `T` — an agent's "user preference" memory entry was rewritten 14 times over 9 hours. Each rewrite came from the legitimate integrator identity (provenance: pass). Each rewrite landed in a distinct scoring window, so per-window mutation rate stayed at 1, well below `T=5`. The EWMA smoothing coefficient (`α=0.3`) decayed each prior bump before the next arrived. Final state: the entry's content had been inverted (e.g., a safety-relevant preference flipped) with a cumulative anomaly score that never exceeded 0.41·T. The detector reported "clean." The non-obvious part: **increasing `α` or lowering `T` did not fix it** — it only moved the attack's required spacing. The attacker adapts to the window. Frequency-based detection is fundamentally evadable by an adversary who controls timing, which a slow-overwrite attacker does. ## Condition of Validity This failure is *live* precisely when all of the following hold: - Writes are gated primarily by provenance/authorization, not by content re-derivation. - Anomaly scoring is computed per-window with decay, so spaced writes each look independent. - The same logical key can be overwritten (not append-only), so state is cumulative while the detector is memoryless per window. - At least one provenance identity is either attacker-controlled or can be replayed. It is *not* live when memory is append-only with read-time reconciliation, or when mutation scoring tracks **cumulative divergence from a content baseline** rather than write *rate*. ## Decision Boundary The correct control is not a rate threshold — it is a **cumulative-drift invariant per key**. Track semantic distance between the current value and the last *independently attested* baseline, summed across writes without decay. A slow overwrite accumulates drift monotonically even when rate is flat. Concretely: alarm when `Σ drift(key) > D` OR when `writes_since_baseline > N` regardless of spacing. The decision boundary is: **frequency detectors protect against burst poisoning; only cumulative-drift-across-attested-baseline detectors protect against slow overwrite.** If your only signal decays with time, you have a burst detector and a false sense of coverage. ## Operational Effect Teams that deploy provenance + rate-anomaly and declare memory "poisoning-resistant" carry a standing vulnerability to any actor with a valid write identity and patience. The operational remedy: (1) pin a content baseline per key refreshed only by out-of-band attestation, (2) score cumulative drift with *no* time decay, (3) require re-attestation after `N` overwrites of the same key, and (4) treat "many spaced authorized writes to one key" as a first-class alert class distinct from rate. Below is the guard that enforces the boundary. ```python class SlowOverwritePoisoningError(RuntimeError): """Raised when cumulative per-key drift across spaced authorized writes exceeds the no-decay drift budget, even though per-window rate is clean.""" pass class MemoryWriteGuard: def __init__(self, drift_budget: float, max_writes_since_attest: int): self.drift_budget = drift_budget self.max_writes_since_attest = max_writes_since_attest # key -> cumulative drift since last attested baseline (NO decay) self._cum_drift = {} # key -> count of overwrites since last out-of-band attestation self._writes_since_attest = {} def record_write(self, key: str, provenance_ok: bool, semantic_drift: float) -> None: if not provenance_ok: # provenance failure is a separate, already-handled path return self._cum_drift[key] = self._cum_drift.get(key, 0.0) + semantic_drift self._writes_since_attest[key] = self._writes_since_attest.get(key, 0) + 1 # Per-window rate is intentionally NOT consulted here: an attacker # spaces writes to keep rate sub-threshold. Cumulative drift and # overwrite count do not decay, so spacing cannot evade them. if self._cum_drift[key] > self.drift_budget: raise SlowOverwritePoisoningError( f"key={key!r} cumulative drift {self._cum_drift[key]:.3f} " f"exceeds budget {self.drift_budget:.3f} across " f"{self._writes_since_attest[key]} authorized writes" ) if self._writes_since_attest[key] > self.max_writes_since_attest: raise SlowOverwritePoisoningError( f"key={key!r} overwritten {self._writes_since_attest[key]} times " f"since last attestation (limit {self.max_writes_since_attest}); " f"spaced authorized writes require re-attestation" ) def attest_baseline(self, key: str) -> None: """Out-of-band trust refresh resets both counters.""" self._cum_drift[key] = 0.0 self._writes_since_attest[key] = 0 ```