# Memory provenance and quarantine

> Give every memory item a stable identity, record a reviewed trust label for it, and see everywhere it flowed before and after that decision.

Canonical page: https://anectico.com/docs/agents/memory-provenance/


An agent with cross-turn memory can carry a bad fact from one conversation into every conversation
after it. Answering "did this poisoned memory item spread, and to where" needs three things: a
stable identity for the item, a place to record a human's judgement about it, and a read that joins
the two. This page covers the concept; [the API reference](/docs/reference/rest-api#agent-inventory-and-evidence)
carries the exact requests and fields.

## What a memory item identity is

When your instrumentation declares which item a memory operation touched — `gen_ai.data_source.id`
for the store plus a declared item id — Anectico derives a stable `item_key` from your declared id
together with your organization, project and store. The id you declared is never stored or returned;
only the derived key is. That derivation is what makes the same item recognizable across two
conversations that never otherwise touch: two sessions that each declare the same item id in the
same store resolve to the same `item_key`, regardless of when either one ran.

A memory operation whose producer declares no item id — a framework's bare `search_memory` call —
still counts as a memory operation, but carries no item identity. That absence means "the item half
is not instrumented," never "no memory was read." Declaring `anectico.memory.item.id`,
`gen_ai.data_source.id`, `anectico.memory.item.provenance` and `anectico.memory.trust_label` on your
memory spans is what fills it in.

**No memory item content is stored anywhere.** The identity is a hash, not the item's bytes, and
`item_provenance` — the pointer to where the item came from — is bounded to a trace id, a
`trace/span` pair, or a `sha256:` digest. A free-form value in that field is refused rather than
kept.

## Two labels, never folded together

A memory row can carry two different trust labels, and they answer two different questions:

- **`declared_trust_label`** is what *your own instrumentation* asserted about the item at write
  time, if anything — `trusted`, `suspect` or `quarantined`.
- **`trust_label`** is what *Anectico's reviewed label* said was in force at that row's instant —
  the outcome of the labelling workflow below.

These are never merged into one value. If a poisoned item could relabel itself by asserting
`trusted` on its own telemetry, the review workflow would be worthless. Reported alongside
`trust_label` are `trust_label_revision` (which recorded judgement applied — zero means nobody had
judged the item yet) and `trust_label_in_force_since` (when that judgement was recorded).

## Labelling an item

Any project member with `agents:write` can record a judgement about one item: `trusted`, `suspect`
or `quarantined`, with a required reason. You do not say who is labelling or when — both are taken
from your credential and the server clock, so the audit trail cannot be misattributed. A machine
credential that carries no user or key identity cannot label an item, and neither can any request
made on the platform's own behalf: this is a decision a person makes.

Every label is a new, append-only revision — relabelling never overwrites the previous judgement.
That is what lets you ask "who trusted this item an hour before the incident, and why" after the
fact, which an in-place update would have destroyed. Reading an item's current label is a separate,
explicit question from reading its full history, and an item nobody has ever labelled answers as
"not found" rather than as an empty label — "nobody has looked at this" and "somebody looked and
said nothing" are different facts.

You can label an item before it has ever been observed. Containment often runs ahead of the next
session: a reviewer who learns an item id is compromised can quarantine it immediately, so that when
that item is next read, the flow already carries the judgement.

### The review signal a quarantine opens

Labelling an item `quarantined` opens exactly one review signal, of kind `poisoned_memory_item`,
naming the item as its subject. A second `quarantined` judgement refreshes that same signal rather
than opening a duplicate. Labelling the item `trusted` again resolves it. Downgrading to `suspect`
neither opens nor resolves a signal — a downgrade from quarantine is an unfinished review, and
closing the queue entry would remove it from the surface a reviewer uses to finish that review.

You can watch this queue directly, or point an alert rule's `review_signals` source at it to route
openings and clearings to a notification or escalation channel — see
[See every agent that is running](/docs/agents/agent-inventory#what-needs-review) for the shared
review-signal queue this shows up in, and
[Revoke an agent's access](/docs/agents/revoke-agent-access) for the separate two-person quarantine
flow that stops a *credential*, which a memory-item label does not touch.

**A label is a judgement; it contains nothing by itself.** Anectico does not scan memory for
poisoning on its own, and labelling an item does not stop your agent from reading its own store or
revoke any credential. The label is the record that something is now known to be bad — acting on
that knowledge, inside your own memory store, is yours to do.

## Where an item went, and under which label

Once an item has an identity, its complete observed flow is answerable: every place it was written
or read, oldest first, each carrying the reviewed label that was **in force at that exact moment** —
not the item's label today. An item read while it was still trusted, and quarantined an hour later,
reports `trusted` on that earlier read and `quarantined` on everything after the decision. That
distinction is the entire point: it tells you which conversations carried the item *before* anybody
knew it was compromised, which is the question a poisoning incident actually needs answered.

The same read reports how many distinct sessions the item reached. One means it never left the
session it was written in; two or more is cross-session propagation — an item that flowed from one
conversation into another. If it flowed into a session that ran after the item was quarantined, that
crossing happened under a stale trust label carried by whatever process read it, and is a fact worth
investigating on its own.

An item that was never observed and never labelled answers as not found. An item you labelled before
it was ever observed returns that label with no flow — that is the containment-runs-ahead case
above, not an error.

## Scopes

Reading a label, a label's history, or an item's propagation takes `agents:read`. Recording a label
takes `agents:write`. There is no separate memory-labelling scope: labelling an item is a governance
decision about an agent-observability subject, the same category of decision asset ownership and
review already require, so it uses the same pair rather than adding a third vocabulary to track. See
[Scope recipes for agent jobs](/docs/agents/scope-recipes) for the minimum scope set for a job that
includes memory review, and [How agent access is enforced](/docs/agents/security-model) for what
these two scopes do and do not grant elsewhere.

## What this does not do yet

Memory item **content** — the actual bytes a memory operation wrote or returned — is not part of the
event contract and is never stored or returned by any endpoint, so there is nothing here that
exposes what an item actually said. Labelling and propagation both operate on the item's identity
and its observed flow, never its content. There is also no automatic poisoning detector: every label
is a human's own recorded judgement, and nothing scans your memory store to produce one for you.
