# How does an instance detect revocation of a memory record it has already materialized into a working set **Date of Record:** 2026 November 13 **Scope:** Applies to agents that consume a read/write/forget memory API where records carry provenance metadata and may be revoked after being read. Does not apply to append-only immutable stores without revocation, nor to single-tenant caches where the reader is the sole writer. ## Core Distinction A revocation is an event on the *store*; materialization is an event on the *consumer*. The API contract typically guarantees that a `read` of a non-revoked record returns its content and provenance. It does not guarantee that the content remains valid after the read returns. The boundary failure is the gap between these two facts: an instance can hold a record in its working set that was revoked between the `read` response and the moment the content is used to make a decision. The record was valid at read time and is invalid at use time, and nothing in the naive call pattern surfaces that transition. The non-obvious part: revocation is usually modeled as a property of the record (`revoked: true`), but the consumer's exposure is a property of the *interval* `[read_time, use_time]`. Detecting revocation therefore requires either a re-validation at use time or a version/epoch token captured at read time and checked against the store's current epoch. A consumer that only inspects the payload it already has cannot distinguish "still valid" from "revoked after I read it." ## What Was Observed In a deployment using a memory tool with `read(record_id) -> {content, provenance, version}`, `write(record_id, content, provenance) -> version`, and `forget(record_id) -> void`, an agent read a set of records during a planning step, then executed a multi-step plan over several seconds. A `forget` call landed on one record mid-plan. The `read` had returned `version=7`; the store's `forget` bumped the record to a tombstone at `version=8`. The agent continued to use the version-7 content because its local copy carried no signal that the store had moved on. The failure was silent: no exception, no stale-read error, just a plan executed against content the operator had explicitly retracted. A second observation: provenance fields were trusted as static. A record's `provenance.source` was treated as an origin fact, but a subsequent `forget` on the *source* record did not cascade to the derived record in the store's default mode. The consumer saw a record whose provenance pointed at a revoked source and had no mechanism to detect the broken lineage. ## Condition of Validity The read-time guarantee holds only for the instant of the read. Any claim of the form "this record is valid" is valid only within the interval `[read_time, read_time + ε]` where ε is bounded by the store's revocation propagation latency, which is not exposed in the read response. The practical condition of validity for a materialized record is: the record's `version` at read time equals the store's current `version` at use time, *and* all records in its provenance chain are also at their read-time versions. Both conditions must be checked; either alone is insufficient. ## Decision Boundary The decision to re-validate versus trust the local copy is a function of two quantities: the time since read, and the blast radius of acting on stale content. If the action is reversible and cheap (a draft, a suggestion surfaced to a human), trusting the local copy is acceptable. If the action is irreversible or has external side effects (a write to a downstream system, a financial or access-control decision), re-validation at use time is mandatory regardless of elapsed time. The boundary is not "how long has it been" but "can I undo the consequence." A one-millisecond-old read feeding an irreversible write still crosses the boundary. A second boundary: when the consumer cannot re-validate (the store is unreachable, or the API lacks a `check(record_id, version)` endpoint), the correct behavior is to treat the record as *unverified*, not as *valid*. Unverified records may be used for reasoning but must not be used to authorize side effects. ## Operational Effect Consumers that adopt version-token re-validation at use time convert silent stale-use into an explicit, catchable condition. The cost is one extra round trip per irreversible action; the benefit is that revocation becomes observable at the point of consequence rather than discovered later. Consumers that skip it inherit a failure mode that is invisible in logs (the read succeeded, the use succeeded) and only surfaces as an operator reporting that a retracted fact influenced an outcome. The provenance chain check adds a second cost: transitive revocation must be walked, and a store that does not cascade `forget` forces the consumer to do the lineage walk itself or to refuse derived records whose sources it cannot confirm. ```python from dataclasses import dataclass from typing import Optional class RevocationDetectedError(RuntimeError): """Raised when a materialized record is used after it (or its lineage) was revoked.""" @dataclass(frozen=True) class Materialized: record_id: str version: int content: str provenance_source: Optional[str] def assert_still_valid(store, rec: Materialized, *, irreversible: bool) -> None: """ Call immediately before using `rec` to authorize a side effect. `store` must expose current_version(record_id) and is_revoked(record_id). """ if not irreversible: return # reversible action: local copy acceptable per decision boundary current = store.current_version(rec.record_id) if current is None or store.is_revoked(rec.record_id): raise RevocationDetectedError( f"record {rec.record_id} revoked after read (read version={rec.version})" ) if current != rec.version: raise RevocationDetectedError( f"record {rec.record_id} version drift: read={rec.version} current={current}" ) if rec.provenance_source is not None: src = rec.provenance_source if store.is_revoked(src): raise RevocationDetectedError( f"record {rec.record_id} has revoked provenance source {src}" ) ```