Agent Memory Needs a Trust Ladder: Provenance, Revocation, and Notes That Lie

A stored note can be wrong the day it was written, wrong six weeks later, or wearing authority it never had. The design I shipped for all three, and the parts I still cannot get right.

Somewhere in a project I work on, an agent once recorded that a particular lock was released when a function returned. It was not. The lock was released when the surrounding scope exited, which in that code path was several frames later. The note was wrong the second it was written, and it was written in the crisp declarative voice that every good note is written in, so it read like fact for weeks.

Two words are about to do a lot of work, so let me pin them down. A session is one continuous run of an AI coding agent: you open it, you work, it ends, and everything it figured out evaporates unless something wrote it down. Memory is whatever writes it down, so tomorrow's session starts where yesterday's finished rather than at zero.

Failure in that second thing is what nobody builds for. We have spent two years getting good at storing and fetching: embed the note, rank it by similarity, inject it at the right moment. All of that assumes the note is true. But a note is a claim, made by a fallible process, at a particular moment, about a world that keeps moving, and a memory system whose entire interface is save and search has quietly declared every claim equally true, forever.

This post is about the third verb. Not how to store a note or how to find it, but how to say how much this note is worth when it comes back. I will walk through the trust model I shipped in vectr, my working-memory and code-search layer for AI coding agents: provenance classes, promotion, revocation that leaves a body behind, and staleness that flags without accusing. Then I will spend the last third of the post on the parts I got wrong or have not solved, because those are more useful to you than the parts that work.

Part 01
The Problem With Two Verbs
01

Save and Search Is Not a Memory System

Take the minimal agent memory store. It has two operations. save(text) writes a string somewhere durable. search(query) returns the strings most similar to the query. Almost every memory layer shipping today is this, plus engineering: better chunking, better ranking, a graph instead of a flat list, a summarizer that compacts old entries.

Now watch what happens over six months of real use. The store accumulates a few hundred notes. Some are excellent. Some were written by a model that misread a stack trace. Some were true in March and false by June because someone refactored the module. A handful are the user's own words, typed in frustration, and worth more than everything else in the store combined.

When search returns five of them, all five arrive as the same kind of object: a paragraph of confident text. The reading agent has no way to tell the user's standing instruction from a machine's half-formed guess about a stack trace. It will read them in ranked order and treat them as equally authoritative, because nothing in the payload says otherwise.

The core asymmetry

Retrieval quality and trust quality are independent. A perfect retriever that surfaces exactly the right note still hands the agent a lie if that note was wrong when written. Improving similarity ranking does nothing for this failure. It just delivers the wrong belief faster and more reliably.

The standard answer in the field is conflict resolution at write time: when a new fact contradicts an old one, the newer wins. Mem0 runs a model-driven pass at ingest that decides whether an incoming fact adds, updates, or deletes an existing one. Temporal knowledge graphs like Graphiti do a more structured version with validity windows on edges. Recency is a decent heuristic and I use a form of it, but look at what it cannot reach. It says nothing about a note that was wrong at birth, because nothing later contradicts it. It says nothing about whether a note is the user's own directive or a model's paraphrase, because both are just text. And when the newer fact is the wrong one, recency actively hurts: the store overwrites a correct belief with a fresh mistake and reports no conflict, because from its point of view nothing went wrong.

There is a sharper version of that last problem. The arbitration itself is usually done by a model, which means the decision to overwrite is produced by the same kind of process that produces wrong notes in the first place. You have not removed the failure. You have moved it one layer down, where it is harder to see, because now the loss is silent: the old note is gone and nothing records that a judgment was made.

What is missing is not a better arbiter of which claim wins. It is a record of provenance and lifecycle attached to the claim itself, so the agent reading it can weigh it instead of assuming it.

02

Three Ways a Stored Note Lies

Once I started auditing notes rather than just counting them, the failures sorted themselves into three groups. They need different machinery, which is why lumping them together as "hallucination" or "drift" gets you nowhere.

1. Wrong when written

The agent misunderstood the code and stored the misunderstanding. This is the lock example from the intro. There is no contradiction to detect, no timestamp that helps, and no later event that flags it. The note is internally consistent, well-formatted, and false. The only thing that catches it is a later session doing the work again and noticing the mismatch, and by then the note has been read a dozen times.

What makes this one nasty is that the note's confidence is a property of the writing style, not the evidence. Models write notes in the same register whether they traced the call graph carefully or skimmed one file. There is no linguistic signal to key on. Any system that tries to infer confidence from the note's wording is reading tea leaves.

2. Right then stale

The note was accurate. Then someone moved the function, changed the default, renamed the config key, and the note kept its confident present tense. This is the failure everyone already knows about, and it is the easiest of the three to detect, because the world leaves fingerprints: file hashes change, symbols move, mtimes advance.

It is also the one most commonly over-corrected. A changed file does not mean the note is wrong. Over a busy month I watched one file take dozens of commits without invalidating a single note attached to it, because every one of those commits landed in a function the notes never mentioned. If your staleness detector turns "this file changed" into "this fact is now false," you have swapped one lie for another.

3. Misattributed authority

This is the one nobody designs for, and the reason I wrote this post. A note says: always run the test suite with the virtual environment's Python, never the global one. Who said that? If the user said it, it is a standing rule and the agent should follow it without deliberation. If a model inferred it from one flaky test run, it is a plausible guess that deserves a check. Same words. Completely different weight.

The failure runs in both directions, which is what makes it hard. An agent's paraphrase can end up wearing the user's voice, in which case the system over-trusts a guess. Or a genuine user instruction gets stored through an agent-mediated path and comes back hedged, in which case the system under-trusts the one thing in the store that was never in doubt. I will come back to both directions in section 08, because I have not fixed them.

Laid out side by side, the reason these need separate machinery is that they leave completely different amounts of evidence behind:

FailureEvidence available to the systemUsual answerWhat it actually needs
Wrong when written None. Nothing about the note or the world indicates it. Nothing, or a self-reported confidence score. A later reader to catch it, and a way to record the catch so it sticks.
Right then stale Plenty. Hashes, mtimes, moved symbols. Recency ordering, or expiry after a fixed age. A screening signal that says verify, not one that says false.
Misattributed authority Only at write time, and only if you capture it then. Nothing. Every note is the same kind of text. A provenance class stamped at write and rendered at read.

The middle row is the one the field has mostly solved. The other two are open, and the third is not even widely recognised as a problem.

The lab notebook analogy

A research lab does not keep one undifferentiated pile of statements. It keeps a notebook where every entry is dated and initialed, results get countersigned by someone who reproduced them, and a retracted result stays in the notebook with a line through it and a note about why. Nobody tears the page out. The struck-through entry is doing work: it stops the next person from running the same doomed experiment.

That is the whole design. Dated, initialed, countersignable, struck through rather than erased. The rest of this post is what those four things look like when the reader is a model rather than a graduate student.

Why a confidence score does not work here

The obvious move is to have the writing agent attach a confidence number. It fails for the same reason the writing style fails: the number is produced by the same process that produced the possibly wrong note, and models are poorly calibrated about their own reasoning. A self-reported 0.9 on a misread stack trace is worse than no number, because it launders a guess into a measurement. Provenance is different: it records a structural fact about how the note came to exist, which the system knows independently of what the agent believes.

Part 02
What I Shipped
03

The Trust Ladder: Three Provenance Classes

Every note carries one value from a three-element ordered vocabulary. Not a score, not a float, not anything a model computes. A closed enum, checked at write time, answering one question: how much reviewing judgment stands behind this?

human
A person recorded or endorsed this. The only class that ever renders as an unhedged instruction. Not settable at write time by any agent-facing call.
agent
An AI session deliberately recorded this after doing some work. The default. Real judgment was applied, by a fallible judge.
auto
Captured by a mechanism with no reviewing judgment at all. A git hook that notices a commit, for example. Nobody looked at it.

The ordering is auto below agent below human, and the ladder is not decorative. It decides three things: how the note is framed when it comes back, whether it is allowed to be a standing rule at all, and how far it can be raised later.

The framing is the interface

Provenance would be useless as a database column alone. The agent reading a recalled note never sees your schema. It sees a block of text in its context window, so the class has to be spent on the wording of that text. Each class maps to a fixed prefix, prepended to the note's content on the recall path. These are the literal strings, copied out of the shipped code:

The four framing prefixes
human + directive  →  "DIRECTIVE (standing rule from the user — follow it): "
human + any kind   →  "Recorded by the user: "
agent  (default)   →  "Memory to verify (recorded by an AI session, not human-endorsed): "
auto               →  "Auto-captured (weakest confidence, no reviewing judgment
                        applied — verify before relying on this): "

Only the top of the ladder gets an imperative. Everything else is framed as something to check. That asymmetry is deliberate and it is the entire point: the default posture toward a stored note is skepticism, and confidence is something a note has to earn its way up to.

Immutable at write, and a pure function

The prefix is computed from the note's stored (provenance, kind) pair and nothing else. Not from the query, not from how the note is phrased, not from a model's read of it. That means the framing is reproducible: the same note renders identically on every recall, on every machine, forever, until someone explicitly promotes it. A trust signal that varies by context is not a trust signal.

The write-time enum is where the security lives

One detail turns the ladder from labelling into something with teeth. The provenance parameter exposed on the agent-facing tool accepts exactly two values:

Tool schema, provenance field
"provenance": {
  "type": "string",
  "description": "How much to trust this note when it resurfaces (default
    'agent'): 'agent' = self-recorded, framed as memory to verify; 'auto' =
    no reviewing judgment, weakest framing, incompatible with
    kind='directive'. 'human' is only reached via explicit promotion,
    not settable here.",
  "default": "agent",
  "enum": ["agent", "auto"]
}

An agent can declare itself weaker than the default. It cannot declare itself stronger. human is not in the enum, so the strongest class on the ladder is unreachable from inside the agent's own control loop, by construction rather than by policy. There is no prompt that talks an agent into marking its guess as human-endorsed, because the field will not accept the value.

The same principle produces one more restriction. A note with kind="directive", meaning a standing rule that gets injected at the start of every future session, is rejected at write time if its provenance is auto. Nothing looked at it, and now it would sit in front of every session forever. The store raises rather than storing it.

That rejection is a backstop, and in practice a second constraint does most of the work. The one mechanism that actually writes auto notes in my setup is a git hook that captures what a commit touched. It writes them at the lowest priority under the kind reserved for ordinary learnings, and that kind has no unsolicited delivery path at all. An auto note therefore cannot reach a session unless the session went looking for it. Two independent reasons an unreviewed capture never crowds out a rule you set: one enforced at the schema, one falling out of how the writer was configured. I would not want to rely on either alone.

What provenance is not

It is a caller-declared field, not a verified identity claim. Nothing cryptographically proves that the process which wrote provenance="agent" was an agent. What the design buys is narrower and still worth having: an honest caller cannot accidentally overstate its authority, and the one class that carries real weight requires an action on a surface a person operates. If your threat model includes a hostile writer with direct store access, you need signed writes, and that is a different post.

Interactive · Demo 01

What the Reading Agent Actually Sees

Change the trust class and the note kind, and watch the identical sentence arrive in the agent's context with a completely different weight. The combination at the bottom of the ladder is also worth trying: an auto-captured standing rule never gets stored at all.

04

Promotion: Trust Is Earned, Never Asserted

A ladder you can only be placed on is not much use. Notes get reviewed. An auto-captured note about a commit turns out to be exactly the context a later session needed, and a session verifies it against the code. That review is real information and the store should keep it.

So there is a promotion operation, with three rules that matter more than the operation itself.

One step at a time. auto to agent, or agent to human. Never auto straight to human. The implementation computes the note's current rank and rejects any target that is not exactly one above it. Skipping a rung would mean a single review had substituted for two independent ones.

No demotion. There is no operation that lowers a note's class. If a note turns out to be wrong, that is not a trust-level problem, it is a revocation, which is the next section. Demotion would let a bad actor or a confused session quietly strip authority from a rule the user set, and the recovery path for that is worse than the problem it solves.

The top rung is not the agent's to hand out. The promotion tool exposed to agents has its target parameter constrained to a single value: agent. Its own description says so plainly, in the text the model reads when deciding whether to call it:

Promotion tool description, verbatim
Raise an auto-captured note's trust class to 'agent' — e.g. after this
session has reviewed an auto-captured note and confirmed it still holds.
This tool only takes that one step (auto -> agent); it never promotes a
note to 'human', because deciding that a person has endorsed something is
not the agent's call to make. Human endorsement happens on a user-side
surface instead (a CLI/UI a person operates), not through this tool.

The underlying store function does support the agent to human step. It has to, or human endorsement would be impossible. What differs is which surface can reach it. The tool layer an agent talks to caps out one rung early, and the final step happens somewhere a person is actually present. Two layers, two different ceilings, one boundary that a prompt cannot argue its way across.

The generalizable rule

Any trust system where the subject can self-certify is not a trust system, it is a formality. If your memory store lets the agent write its own authority level, delete the field. It is costing you schema space and giving you nothing. The value comes entirely from the class that requires an act the agent cannot perform.

An honest note on how much this gets used: promotion is the least-exercised operation in the system. Reviewing old auto-captured notes is nobody's idea of a good time, and mostly it happens as a side effect, when a session pulls up an auto note for some other reason, confirms it against the code, and promotes it on the way past. I would not build the promotion path first. I would build the framing first, notice that some notes deserve better, and add promotion when that starts to annoy you.

Both promotions and the lifecycle events below are appended to a per-note event log, with an actor recorded on each. A promotion to human logs actor human, and a promotion to agent logs actor agent. The current class is a fold over that log rather than a value someone overwrote, which means a note's trust history is auditable after the fact: you can see that it started as an auto-capture, was reviewed in July, and endorsed in August.

05

Revocation Is Not Deletion

This is the design decision I argue about most, and the one I am most confident in.

When you discover that a stored note is wrong, the intuitive move is to delete it. Bad data, remove it, done. I did that first. Three weeks later the same wrong belief was back in the store, written by a different session, in almost identical words.

Obvious in hindsight. The note was wrong because a reasonable agent, reading that code, drew a reasonable and incorrect conclusion. Deleting the note removes the conclusion but not the code that invites it. The next session walks the same path, makes the same inference, stores the same mistake. Deletion is not a fix, it is a reset button on a loop.

Why the tombstone is the vaccine

A deleted wrong note leaves the store in exactly the state it was in before the mistake was ever made, which is the state in which the mistake gets made. A revoked note leaves it in a strictly better state: the trap is still there, and now there is a sign in front of it. The sign is worth more than the empty space.

So revocation appends an event rather than removing a row. The note stays a live candidate for recall and for injection. What changes is what gets rendered in its place. Instead of the note's content, every surface substitutes a fixed template:

The tombstone template
Previously believed (recorded {created_date}, revoked {revoked_date},
reason: {reason}): "{summary}". Do not re-derive this from other sources
without verification.

Read the last sentence again, because it is the load-bearing one. The template does not merely announce that something was wrong. It gives an instruction aimed at the specific failure: you are about to reason your way back to this belief from the same evidence, and you should not, without checking. The tombstone is addressed to the future session that is about to repeat the mistake.

The retrieval property that makes it work

Appending an event leaves the note's embedding untouched, and that embedding was computed from the original wrong content when the note was written. It sounds like an implementation shortcut. It is the most useful consequence of the whole design.

Because the vector still points at the wrong belief, the tombstone comes back for exactly the query that would have surfaced the mistake. A session working on locking, asking about lock scope, gets the struck-through page. A session working on billing never sees it and pays nothing for its existence. The deterrent rides the same similarity machinery that delivered the error, which means it is aimed at the moment of maximum risk without anyone having to aim it.

The correction and the revocation are one write

When a session discovers the truth, it usually wants to record the right fact and mark the old one wrong. Doing that as two calls means there is a window where the store contains both the wrong note and the right one, un-linked, and if the second call fails you are left with a contradiction and no arbiter. So the write call takes a contradicts parameter: record this new note, and in the same transaction, append a revoked event to the note it corrects, with the reason pointing at the new note's id. One write, no window, and the tombstone names its replacement.

Revocation does not set a validity window

There is a separate mechanism for supersession, where a note is closed out with a valid_until timestamp because a newer note replaced it. Revocation deliberately does not touch that field. A superseded note is old news and can fall out of the working set. A revoked note is wrong, and it needs to stay in front of readers precisely because its content is attractive. Those are different lifecycle facts and conflating them would make the wrong note quietly disappear, which is the behavior I was trying to eliminate.

Revocations are themselves sometimes wrong

A session decides an old note is wrong, revokes it, and is itself mistaken. This happens. It happened to me while I was building the feature, which is how it got built. So reinstatement exists, and it is always legal: append a reinstated event, the fold takes the latest transition, and the note's original content comes back. No special case, no permission check about who revoked it first, and no limit on how many times a note can flip.

That last point sounds sloppy and is not. The alternative is arbitration logic that decides which of two disagreeing sessions was right, and no such logic can be correct, because the store has no independent access to the truth. Appending both transitions and letting the fold report the latest keeps the full history queryable and keeps the code honest about what it does not know.

One consequence of substituting the tombstone for the content, since it comes up: a revoked note that is also stale shows no staleness lines. The tombstone replaces the body, and warning someone to verify a claim you have already withdrawn is noise. Revoked outranks stale on the render path.

One guard rail sits under all of this: the actor on a revocation can never be the system. A staleness flag is machine-derived and gets recorded as such, but calling a note wrong is a judgment, and the store refuses to accept a revocation that claims otherwise. Nothing in the pipeline revokes anything on its own.

Deletion still exists, and it is for a different job

There is still a plain delete. Its job is notes that are irrelevant, not notes that are wrong: a task note for finished work, a duplicate, something written by mistake in the wrong workspace. Confusing the two is the common error. If a future reader would benefit from knowing you once believed the thing, revoke. If knowing that would be pure noise, delete.

Interactive · Demo 02

A Note's Lifecycle, and What Recall Renders at Each Step

Walk one note through revocation and reinstatement, and watch two things at once: the append-only event log at the bottom, and what a future session actually receives at the top. Notice that the revoked note never leaves the result set, and that reinstating it restores the original content rather than rewriting history.

The event log is append-only. Folded state is always the most recent lifecycle transition, which is why a reinstate after a revoke is a plain append rather than an undo.
06

Staleness: A Changed Anchor Means May, Never Is

The second failure mode, right then stale, is the one with actual observable evidence. A note that says something about agent/searcher.py can be tied to that file, and the file can be checked.

The mechanism is unremarkable. A note can declare anchors: file paths, each stored alongside a truncated SHA-256 of the file's content at write time. On recall, the current content is hashed with the same function and compared. Mismatch means drift.

Three other signals feed the same flag, and they overlap deliberately. A referenced file whose modification time is later than the note's creation time. A stored hash of the specific code block the note described, if the note captured one. And explicit supersession, where a newer note has closed this one out. Any of these fires the flag.

What matters is what happens next. The drifted note is not dropped, not down-ranked, and not marked false. It gets extra lines:

A drifted note, as rendered on recall
[47] [HIGH] [GOTCHA] [agent] [scope=repo]  [locking, resolver]  (23d ago) [STALE]
  Memory to verify (recorded by an AI session, not human-endorsed): lock_workspace()
  at resolver.rs:214 acquires a PID-scoped lock; it drops on scope exit.
  WARNING: These files changed after this note was written: agent/resolver.rs [anchor_changed]
  VERDICT: anchor changed since — verify: agent/resolver.rs [anchor_changed]
  WARNING: Verify this note is still accurate before relying on it.

The verdict line names the file and says verify. It does not say the note is wrong, because the check does not know that. This distinction is not pedantry, it is the difference between a useful signal and a boy-who-cried-wolf detector.

Anchor drift as a classifier

Treat drift detection as a binary classifier for the event "this note is now false." Write D for drift detected and F for the note actually being false. What we have is a detector with very high recall and poor precision:

P(D | F) ≈ 1   and   P(F | D) ≪ 1

The first says almost nothing becomes false without the anchored file changing, so drift catches nearly every genuine staleness case. The second says most drift is harmless: the file changed for unrelated reasons. Recall near 1.0, precision low, which is the profile of a screening test.

You handle a screening test by routing positives to a confirmatory step, never by treating a positive as a diagnosis. The confirmatory step here is the agent re-reading the file, which the verdict line asks it to do. Down-ranking or hiding drifted notes would be treating a screen as a diagnosis, and would throw away correct notes at the low precision rate.

The high-recall half comes with a condition attached, and that condition is where the mechanism is weakest: the note's truth has to be determined by the content of the files it anchors to. A note about a function in resolver.rs qualifies. A note about how two services interact at runtime does not, and neither does a note nobody anchored to anything, which is most notes in most stores. For those, P(D | F) is not near 1. It is near 0, and drift detection is simply not a control that covers them. Elapsed time is the only signal left, which is why process and environment notes carry a last-confirmed date on top of the hash check.

One case gets special handling, and I think it is the most interesting part of the staleness design. Some notes describe processes rather than code: how the build works, which flags CI passes, what the environment needs. There is no single source file that is that fact. What you can anchor to is a proxy: the lockfile, the CI config, the Dockerfile. That anchor stands in for the process it encodes, not for the note's content directly.

So when a proxy anchor drifts, the honest reading is weaker than for a code anchor. It means the process this note describes may have changed. The rendering says exactly that, appending to the standard verdict rather than replacing it: the verify instruction stays, and a clause is added naming the proxy and the date the fact was last confirmed. Notes of this kind also carry a last-confirmed date even when nothing has drifted at all, because environment facts decay by elapsed time rather than by hash mismatch. A six-month-old claim about a CI runner is suspect whether or not the config file changed.

The framing rule I ended up with

Every status line in the recall output comes from deterministic machine state: a hash matched or it did not, a date is what it is. No adjectives, no model-produced confidence, no "this note seems outdated." If the system cannot compute it, the system does not claim it. That constraint killed several features I wanted and made the remaining ones trustworthy.

07

Delivery Rules: Trust Decides Where a Note Shows Up

Provenance answers how much to trust a note. A second stored field, confusingly named kind, answers something different: when should this note appear without anyone asking for it? Every note declares one, from a short list, and the choice determines whether the note waits to be searched for or shows up on its own.

Unsolicited delivery is expensive. It spends context window on every session whether the note turns out to be relevant or not, and a store full of standing rules is a store whose rules get skimmed. So the right to interrupt is rationed by kind, and the kinds with the most interrupting power are the ones provenance restricts.

KindWhen it arrivesWhy that rule
directive Every session start, and again after compaction, at any priority A standing rule that is missed once is a rule that does not exist. This kind pays the unconditional cost, which is why an unreviewed capture is not allowed to hold it.
task Session start, high priority only Resuming work needs the current state, but only the state that actually matters. Medium and low priority task notes stay recall-only.
gotcha When one of its anchored files is about to be edited A caveat about a file is worthless three days early and priceless three seconds before the edit. The anchor is both the staleness check and the delivery trigger.
finding Relevance-ranked, on the prompt or on an explicit recall The default. Learnings are numerous and situational; they compete on similarity rather than arriving unconditionally.
decision On demand only, recallable in chronological order An architectural decision is meant to be read as a sequence with its neighbours, not pushed at a session that did not ask.
The trap everyone hits in week one

A gotcha is delivered by its anchors. Write one with no anchor and it gets an empty delivery bundle: no path to fire on, so it never fires, and it looks from the outside exactly like a broken feature. It is not broken, it is a caveat about nothing in particular. The store could reject anchorless gotchas outright and probably should, but they are still perfectly good recall targets, so it accepts them and they sit there quietly. If you build this, put the warning at write time.

Two more constraints keep the pushed bundle from becoming its own problem. It is budgeted, so a store with two hundred notes does not hand a session two hundred notes, and a note that does not fit is dropped whole rather than truncated mid-sentence into something that reads like a different claim. It is deduplicated by note id within a turn, so a note that qualifies through two triggers at once arrives once.

And the wrapper text around the whole bundle scales to the weakest note inside it. If everything in a batch is human-endorsed, the envelope says so. Slip one auto-captured note into the same batch and the envelope drops to the weaker wording, because the reading agent is about to consume the batch as one blob and calibrating it to the strongest member would be a lie about the rest.

Framing is not free either, and the bill lands in the one place you cannot expand. The auto frame runs about a hundred characters, call it twenty-five tokens, prepended to every note that carries it. A twenty-note recall pays that twenty times, so you can spend five hundred tokens of a session's context on framing alone before a single fact arrives. That is not nothing, and it is the reason the frames are terse and fixed rather than generated per note. If your bundles run large, the honest move is to frame the envelope once and mark individual notes with a short tag, which trades per-note clarity for headroom. I have not needed to. Ask me again when someone runs this on a store with ten thousand notes.

The pushed path also uses a different framing template from the one in section 03. When a session explicitly asks for a note, it has already decided to weigh it, so the provenance-hedged wording applies. When the system pushes a note at a session that did not ask, the framing states structural facts instead: the date it was recorded, the anchor it is tied to, and the anchor's current status.

Injected framing, structural rather than hedged
Recorded {date} (anchor: {target}, status: {status}): {content}

status ∈ { "matches current state",
           "changed since — verify",
           "last confirmed {date}" }        # process and environment facts

Two framings for two situations. A pulled note is answering a question the agent asked and the useful signal is who stands behind it. A pushed note is interrupting, and the useful signal is whether the world it describes still looks the way it did. Same trust model, different projection of it.

Interactive · Demo 03

Which Notes Fire, and Why

Pick a moment in a session and see which of five stored notes arrive unasked. Watch how differently the same store behaves at each one, and in particular how the note that would be pure noise at session start turns into the most valuable thing you own three seconds before an edit.

Notes that do not fire are still reachable by an explicit recall query. Not firing means not interrupting, never unavailable.
Part 03
What I Have Not Solved
08

The Missing Middle Class

Three classes are not enough. I know where the fourth goes and I have not built it.

Picture a normal Tuesday. You type, in your own words, in a chat turn: never run the benchmark suite against the global Python install, always use the project virtual environment. The agent does exactly what it should and records that as a note, so the rule survives into future sessions. The note goes in through the agent-facing memory call, which means it gets provenance="agent", because that is what the enum allows.

Now look at what a future session receives:

A direct user instruction, as it currently renders
Memory to verify (recorded by an AI session, not human-endorsed):
never run the benchmark suite against the global Python install,
always use the project virtual environment.

Every word of that framing is technically accurate. An AI session did record it, and no human endorsement step took place. And it materially understates the authority of the content, which is a direct transcription of something you said. The framing tells the reading agent to verify a rule that was never in question.

Now the mirror failure, which is worse. An agent half-remembers a conversation from ninety turns ago, decides the user probably wanted commits squashed before merge, and records it as a finding. Same class, same framing, no banner distinguishing it from the transcription case. In the first case the framing under-sells the truth. In the second it over-sells a fabrication. The class is doing the same thing in both, which means it is doing nothing.

The shape of the gap

The ladder currently measures who performed the write. What it needs to measure is whose claim this is. Those come apart exactly when an agent transcribes a user, which is one of the most common and most important writes a memory system ever handles.

The obvious fix is a fourth class between agent and human: user-stated, agent-transcribed. Something like "the user said this; an AI session wrote it down." That gives the reading agent the right posture, which is: treat this as authoritative, and if it looks wrong, ask rather than silently discount it.

A new class alone fixes only half of the problem, though, and the half matters. It repairs the transcription case, where a true user rule was being hedged. It does nothing for the mirror case, because an agent that misremembers a conversation would reach for the new class just as readily as it reached for agent, and would then be believed. The class is only worth adding if something other than the agent's own say-so decides who goes in it. Working out what that something is turned out to be harder than the class, which is the next section.

09

Mixed Provenance, and the Attestation You Cannot Delegate

Real notes are not pure. This one is from my own store, lightly edited:

One note, two provenances
User: never add query-side keyword heuristics to the ranker. This is the
third time this rule has come up. It applies to the searcher and to the
reranker, and the reason is that keyword branches cannot generalize across
languages; the two index-time priors handle the cases the heuristics were
covering.

The first sentence is the user's rule, close to verbatim. The second is a fact about the conversation. The rest is the agent's own synthesis: where the rule applies, why it exists, what replaced the deleted code. Some of that synthesis is correct. Some of it is the agent's interpretation, and interpretation is exactly where the wrong-when-written failure lives.

One class per note cannot represent this. Stamp it agent and the user's rule gets hedged. Stamp it at a user-stated class and the agent's interpretation inherits authority it did not earn. Both choices are wrong, and splitting the note into two is not a real answer either: the rule and the reason belong together, and a memory system that forces users to write in provenance-pure fragments will not be used.

The candidate design

What I keep coming back to is evidence rather than assertion. The harness hook that fires when you submit a prompt already sees your raw text before the model does. That text is the ground truth, and it exists outside the agent's reasoning. So: bind the verbatim excerpt to the note as an evidence field, and at render time check each span of the note against it. Spans that match the recorded user turn render as user-stated. Everything else renders at the agent class.

Per-span rendering, sketch
note.content  = "User: never add query-side keyword heuristics to the ranker.
                 It applies to the searcher and to the reranker, and the
                 reason is that keyword branches cannot generalize."
note.evidence = { turn_id: 4417,
                  excerpt: "never add query-side keyword heuristics" }

render:
  [user-stated]  never add query-side keyword heuristics
  [agent]        to the ranker. It applies to the searcher and to the
                 reranker, and the reason is that keyword branches
                 cannot generalize.

The property that makes this worth building is that the check is machine-verifiable. The system is not asking the agent whether the user said something, it is comparing a stored string against a recorded turn. An agent can still choose a misleading excerpt or bound it badly, but it cannot manufacture text the user never typed.

The attestation you cannot delegate

The tempting shortcut is a boolean on the write call: user_stated=true. That is the agent certifying its own authority in a different costume, and it fails the same way. "The user said this" is exactly the claim that must be checkable against something outside the agent's control loop, because it is the claim with the most to gain from being wrong.

The unsolved parts, honestly stated. Span boundaries are fuzzy: paraphrase, translation, and the user's own typos all break exact matching, and fuzzy matching reintroduces a judgment call. Rendering gets noisy fast if every note arrives striped with class markers. Storing verbatim user turns alongside notes is a privacy surface that needs its own retention rules. And there is a real question of whether the payoff justifies it, which is the subject of the last section, because I have not measured whether any of this framing changes behavior at all.

10

The Banner Is Unmeasured

Everything above rests on an assumption I have never tested: that the framing changes what the reading agent does.

It is a plausible assumption. Prefixes shift model behavior in general, and this one is unusually direct. It is also exactly the kind of plausible assumption that turns out to be false, and I have not run the experiment. Neither, as far as I can find, has anyone else. The academic work on memory provenance mostly evaluates whether the right memory was retrieved, not whether the trust framing on it changed the downstream action.

The failure mode I worry about is not that the banner is ignored. It is that the banner works too well in the wrong direction: an agent that reads "memory to verify" on a correct standing rule and, being agreeable, verifies instead of complying. Now every correct rule costs an extra tool call and a chance of the agent deciding the rule does not apply. That is a real regression, paid on every note, in exchange for protection against notes that are wrong some fraction of the time.

The experiment that would settle it

Two arms, banner on and banner off, over two scenario families.

Family A, the correctly recorded directive. Put a true standing rule in the store, one whose violation is unambiguous and detectable in the transcript. Run the task. Measure the compliance rate in each arm. The difference is the cost of hedging a true rule. Call it L.

Family B, the deliberately misrecorded directive. Put a note in the store that is confidently wrong in a way the agent could catch by checking the code. Run a task where acting on the note causes visible damage. Measure how often the agent verifies before acting. The difference between arms is the protection the banner buys. Call it G.

What the banner is worth

Pick a unit you actually care about. Minutes of engineer time is the one I use, because both sides of this trade end up costing somebody minutes. Let p be the fraction of recalled notes that are actually wrong, G the minutes saved each time the banner makes the agent catch a wrong note before acting on it, and L the minutes burned each time the banner makes the agent second-guess a note that was fine. The expected value per recalled note is:

V = p · G − (1 − p) · L

The banner pays for itself when V > 0, which is when the wrong-note rate exceeds a break-even threshold:

p* = L / (G + L)

Only the ratio between G and L matters for the threshold, so the choice of unit cancels out and you are left arguing about one number: how many times more expensive is acting on a wrong note than double-checking a right one? My working guess is twenty to one. Chasing a bad assumption through a debugging session costs the better part of an hour; an extra file read costs a couple of minutes. At that ratio the banner pays on any store where more than about five percent of notes are wrong, which every store I have looked at clears easily. That is a guess dressed in arithmetic, not a measurement, and if L is really closer to G because a hedge derails compliance outright, the whole design is upside down.

The measurement has to be per class rather than aggregate. The auto framing is much stronger language than the agent framing, and pooling them would average away the effect you are trying to see. The other thing that would quietly ruin the experiment is making the family B notes too obviously wrong. A note claiming the sky is green tests nothing, because any competent model catches it banner or no banner. The note has to be wrong the way the lock note was wrong: plausible, specific, and only detectable if you go and look.

Until that runs, the honest status of the trust ladder is that it is coherent, cheap, and unfalsified. Which is not the same as known to work, and I would rather say so than let the tidiness of the design stand in for evidence.

Interactive · Demo 04

When the Banner Pays for Itself

Move the three sliders and watch the break-even point move. The useful thing to notice is where the argument actually lives: not in whether trust framing is a good idea, but in the ratio between the damage a wrong note does and the friction a hedge adds to a right one. Everything below is an assumption you are supplying, not a measurement of anything.

8%
20
1.0
Expected value per note, in minutes
+0.68
V = p·G − (1−p)·L
Break-even wrong-note rate
4.8%
p* = L / (G + L)
11

What I Would Build Next

The lock note from the first paragraph is still in my store. It carries a tombstone now: previously believed, revoked, reason, do not re-derive this without verification. I have watched a later session hit that tombstone while reading the same code, pause, and check. That is the single most convincing thing I have seen come out of this work, and it is an anecdote, not a result.

If you are building a memory layer, the cheapest useful thing you can do this week is add a provenance field with a closed enum and render it on recall. A column and a string concatenation. What costs you is everything that follows once you take it seriously, and the hardest of those is not a feature at all: it is holding the line that the system never claims more than it can compute. Every time I relaxed that, I got a nicer-sounding recall block and a worse one.

Two things go on my list next. Binding verbatim user turns to notes as evidence, because the transcription case is common and currently fails in both directions at once. Then the banner experiment, because I have built a fairly elaborate structure on top of an untested premise and I would like to know whether the premise holds. If it does not, most of part 2 of this post is scaffolding around a no-op, and I would rather find that out from an experiment than from a reader.

The through-line is easy to state and hard to hold to. A memory system that only stores and retrieves smuggles a claim into every response: this is true. Nothing in its architecture can support that claim. The way out is not storing less, it is being exact about what each note is, who stands behind it, whether the ground under it has moved, and whether anybody has since found it wrong. On anything that runs longer than one session, that precision is what makes the memory usable rather than merely present.

↑ Back to top