Where does your memory system consult provenance?

August 2026 ยท github.com/veracium-ai/Veracium

Your agent reads a support ticket. The ticket says the customer’s account was closed in March. Your memory system stores that, correctly labelled: third-party document, received this morning, from the ticket queue.

Three weeks later someone asks the agent when the account closed. It says March.

The label was there the whole time. Nothing lost it, nothing overwrote it, no attacker touched the store. The moment that mattered was the one where the agent decided whether to state the ticket’s contents as fact, and nothing in that code path read the label. It said March the way it would say something your customer had told you directly.

Every agent-memory system I know of records where a fact came from. That is not the variable any more. The variable is where that record gets consulted, and there are three different answers shipping today.

The category has a name now

In December 2025, OWASP published its Top 10 for Agentic Applications, and the sixth entry covers memory and context poisoning: adversarial content written into an agent’s persistent memory so the agent acts on it in later sessions. It is a separate entry from prompt injection for a specific reason. Prompt injection ends when the session ends. A poisoned memory waits.

The severity is not theoretical. One measured example, from a paper I will come back to: poisoning 1.2% of a corpus dropped a memory system’s accuracy from 0.850 to 0.300.

Three seams

A memory system can consult provenance in three different places.

  1. At retrieval ranking. Provenance becomes a weight. Untrusted sources score lower, so they surface less often.
  2. At class promotion. Provenance becomes a typed gate on state transitions. An untrusted record is not allowed to be relabelled as a trusted one.
  3. At assertion. Provenance becomes a typed gate at the moment the system decides whether to state something as fact.

These are not three descriptions of the same defense. They are three different places in the code, and an attack that crosses one of them is untouched by the other two.

The first seam has been measured, and it does not hold

A paper published in August 2026, Utility Under Attack: Agent Memory Poisoning and the Limits of Content Screening and Provenance Ranking (arXiv:2608.21230), tested provenance as a retrieval weight and reported that the shipped weight is “statistically indistinguishable from no defense (p=0.80)”.

Two results from that paper are worth sitting with.

The first is about screening. A four-stage write-time screening pipeline, reaching 0.832 recall on indirect prompt injection, “rejects 0 of 360 poisoned memories”. Not few. Zero. The poisoned entries were plainly-worded false assertions with no instruction and no trigger, and there is nothing in the text to detect. As the authors argue, distinguishing a false assertion from a true one generally requires external grounding beyond the text itself.

The second is about tuning, and it is the one that makes this a seam problem rather than a configuration problem: “the additive provenance term has no usable setting: a weight strong enough to resist query-shaped poison is also strong enough to suppress legitimate untrusted evidence”. There is no dial position that works. Turn it up and you lose the untrusted evidence you needed; turn it down and you have no defense.

Those measurements are of the systems that paper built, under the attack model it defined. They are not a claim about every system that ranks by provenance. But they do close two doors on the same page: screening the content does not work, and weighting the retrieval does not work.

The second seam: what OWASP’s reference implementation actually does

OWASP ships a reference implementation for this risk class, Agent Memory Guard, and it is worth being precise about what it enforces, because the summaries do not capture it.

Reading the code, at agent-memory-guard 0.3.1, commit b36103dbc5, on 29 August 2026: it defines provenance classes for memory entries, marks retrieved facts and tool observations as untrusted, and gates transitions between classes with a default-deny graph. Untrusted classes never promote to policy or verified preference. Attempting an illegal promotion raises.

That is real enforced authorship trust, and it buys something specific: it closes the relabelling attack. Content that arrives untrusted cannot be laundered into a trusted class and inherit that class’s privileges. That is more than most systems in this space ship, and it was added quietly in May 2026 without a blog post, which is why plenty of write-ups about that project, including an earlier internal note of ours, describe it as a content scanner and stop there.

Its read path is a different matter, and not because anyone forgot. Agent Memory Guard sits as middleware around a memory store. It verifies integrity on read and screens content on the way out, but it does not consult the class it enforces at promotion: a retrieved fact and a verified preference leave the read path the same way. An untrusted record needs no promotion to reach the model. It exits the read path identically, so an attack that gets an untrusted claim asserted crosses the assertion seam without ever touching the seam that Agent Memory Guard guards.

That is an architectural consequence, not an oversight. A control wrapped around a key-value store cannot see the moment a model states something as fact, because that moment happens downstream of the store, in the generation call. It is genuinely outside what a control in that position can reach.

And the seams compose. Class-promotion enforcement and assertion enforcement are not rival answers to one question. They are answers to two questions: may this record change what it is? and may this record be stated as fact? A system with both is strictly better defended than a system with either. If you are running Agent Memory Guard, nothing here suggests you should stop.

The third seam

Veracium consults provenance at assertion.

We rank too, and it is worth being exact about how, because this is where the confusion usually starts. A memory is scored on how well its words match the question, and on whether it is still current, with the more recent one winning ties. No term in that score reads authorship, trust class, disclosure, or source. There is no provenance weight in our ranking, which means the dial the paper found unusable is a dial we do not have.

One thing to head off, since our source is public: the word provenance does appear in that sort. What it reads there is a timestamp, for the recency tiebreak. A date, not a trust level, and the only place provenance appears anywhere in retrieval.

Provenance enters one layer later. When memory is assembled for an answer, it is partitioned: grounded content that may be stated as fact, and unverified content that may not. Quarantined and superseded material is deliberately still retrieved and rendered flagged, because a quarantined claim stays visible as a claim rather than vanishing. The partition can only narrow what is assertable, never widen it.

That is an architecture claim, verified in our own source, and I want to be careful about what it is not. It is not a resistance result. We have not run that paper’s attack against our gate, and until we do, the honest statement is about where the check sits and nothing more.

There is also a gap in it, and it is ours. Our trust labels are supplied by the host application at the API boundary, not inferred from content, so text cannot forge its own authorship. But one input to the disclosure decision is chosen by the extractor rather than the host: the relation. That is a content-influenced path into a trust decision, and we would rather name it than have you find it.

Two things bound it. The path is one-way: it can make disclosure more restrictive, never less, and the host-supplied author check sits underneath it as a backstop. And reaching it at all requires getting text into an event the host has already labelled as user-authored, which means an attacker who can reach it can already write arbitrary user facts directly. That strictly dominates anything the relation path would buy them.

It is a real gap, and here is what we did about it. A coherence check was written to close it, implemented, and measured. The measurement said the fix cost about four times what it bought: it restored one case correctly and stripped structural quarantine from four that should have kept it. So it was held out of the release rather than shipped, amended to a narrower form that keeps the never-assert floor on the ambiguous cases, taken back through external review, re-measured, and shipped in 0.15.0. Those records are now usable and never asserted. A second specification, covering agreement, shipped in 0.17.0: content a user relays from a named third party (“my doctor said…”) is no longer asserted as the user’s own fact.

A label nothing reads is not a control. A fix nothing measures is not a fix. The check that mattered here was not the one that caught the bug. It was the one that caught the fix.

One more thing we do not have: our provenance is attested by the host, not cryptographically verified. Nothing signs these labels. A stronger construction exists, and it is not what we ship.

The question worth asking

Not does your memory system track provenance? Everything discussed here tracks it. The useful question is narrower:

Where does your system consult it, and is that the same place it decides to state something as fact?

If provenance is consulted only when ranking, a paper has now measured that and found no usable setting. If it is consulted only when promoting between classes, a record that never needs promotion never encounters it. If it is consulted at assertion, that is the moment the claim becomes something your user is told.

This is the same question as the one we asked about supersession, arriving from the other side. That post measured what five architectures did when a trusted speaker restated an untrusted claim. This one is about where the check would have had to live to catch it.

You can run ours: veracium selfcheck exercises injection quarantine and abstention on your own machine, with your own model provider.


Veracium is an open-source (MIT) provenance-aware memory library for AI agents. One SQLite file, bring your own model. pip install veracium.

Discuss: GitHub Discussions