Chapter 7 of 8 · 14 min read
Provenance is what makes a memory inspection view worth shipping
On this page
The first version of the memory screen was a list of sentences under the heading "What the assistant remembers about you":
- Works in logistics operations
- Prefers concise answers
- Is evaluating a migration away from the legacy scheduler
- Has a strong preference against phone calls
The product analytics for its first month were unambiguous. Of the 2,140 users who opened the screen, 38 percent used "delete everything" rather than deleting any individual item, and a further 21 percent left without touching anything and did not come back. The support queue picked up a category of ticket that had not existed before, whose representative example was "the assistant says I'm evaluating a migration and I have never said that — where is it getting this?" Nobody could answer, because the record was a sentence and a user id, and finding its origin meant a support engineer grepping conversation logs for the word "scheduler".
The screen was not badly designed as an interface. It was under-specified as data, and the two numbers in the paragraph above are the same defect producing two different responses.
Why the only available actions were all-or-nothing
Work through what a user can actually do with one of those four lines. "Is evaluating a migration away from the legacy scheduler" is either true or it is not, and the screen offers nothing with which to decide. There is no date, so it might describe last week or last year. There is no source, so there is no way to tell whether the user said it or the system worked it out. There is no indication of what it affects. A statement with no origin cannot be evaluated, only believed or rejected wholesale, and a user faced with four statements they cannot evaluate will rationally reject all four.
That accounts for the 38 percent who deleted everything. The 21 percent who touched nothing and did not return are the same reasoning arriving at the opposite action, and it is worth following because it is the half that gets misread as apathy. If you cannot evaluate a line, you also cannot safely remove it, since removing a true and useful line degrades the product for you. Faced with four unevaluable claims and no way to tell the good ones from the bad, deleting all of them and deleting none of them are both defensible, and which one a person picks says more about their temperament than about the records. The screen had made per-item judgement impossible, so it got aggregate judgement instead, in both directions.
The requirement that follows is not "show more" but "show enough to check one line without checking the others". The comparison that clarifies the requirement is a bank statement: a list of amounts is unusable, and the same list with a date, a merchant and a reference becomes something you can audit line by line, dispute one entry of, and leave the rest alone. It is a comparison rather than a claim about how memory works, and the property being borrowed is per-line evaluability, obtained by attaching to each line the facts you would need to check that line alone.
The analogy breaks at the word "dispute". A bank statement records events that definitely occurred, and each has a counterparty holding its own record of the same event, so a disputed line has an adjudicator and a fact of the matter. A memory record is frequently an inference drawn by a model from something a person said, and there is no second ledger to reconcile it against — the only evidence that the inference was reasonable is the conversation it came from. That is precisely why the field distinguishing a stated fact from an inferred one carries so much of the weight below, and why an interface that presents both in the same typeface has removed the only distinction a user could have used.
Provenance is that supporting detail: the record, stored alongside each remembered fact, of where it came from — when it was written, out of which conversation and which turn of it, whether the user said it or something inferred it, and which rule decided to keep it. Provenance is what converts a claim into a line item.
The fields that make a record evaluable
Each field earns its place by answering a question someone actually asks in front of the screen.
| Field | The question it answers | Who needs it |
|---|---|---|
written_at | Is this still current, or is it from eight months ago? | User |
source_conversation + turn | Where did I say this? | User, support |
derivation (stated / inferred) | Did I say it, or did something guess? | User |
evidence (the quoted turn) | What exactly was I responding to? | User, support |
written_by (extractor and gate version) | Which rule produced this, and did it change? | Support, engineering |
scope and visible_to | Who else can see this about me? | User |
last_used_at, use_count | Is this actually affecting my answers? | User, support |
updated_from | What did this say before, and why did it change? | Support |
The third column is worth reading on its own, because the split it shows is the argument for building all eight rather than the four a user-facing screen appears to need. Five rows are wanted by the user, five by support, and two by both. The overlap is where the design pays for itself twice: source_conversation and evidence are simultaneously the fields that let a person check a claim and the fields that let an engineer answer "where did this come from" without a text search. A team that builds only the user-facing subset ships a screen that cannot be debugged, and a team that builds only the support subset ships fields nobody surfaces.
The derivation field changes user reaction more than any of the others. The mechanism is the one the bank-statement comparison exposed. "Is evaluating a migration away from the legacy scheduler" was inferred from a user asking how to export schedules in bulk, and the inference was not unreasonable — it was simply presented in the same typeface, with the same authority, as "prefers concise answers", which the user had said in as many words. A user who cannot tell the two apart has to assume the worst reading of both, and the worst reading of an inferred claim presented as a fact is that the system is watching them. Marking inferences as inferences and showing the turn they were drawn from moved that support-ticket category to nearly zero, because a user who sees "inferred from: how do I export all schedules at once?" can tell the system is wrong without concluding that it is spying on them. On the rebuilt screen, bulk deletion fell from 38 percent of sessions to 9 percent, and per-item deletion rose to 27 percent, which is the outcome worth optimising for: users pruning what is wrong and keeping what is useful.
updated_from connects this screen to the conflict handling from 10.12. When a newer statement supersedes an older one, the user is looking at the winner of a resolution they never saw, and showing the superseded version with its date is what lets them say that the resolution went the wrong way. Without it, a memory that flipped last week is indistinguishable from one that has been stable for a year.
The response, and the control path around it
GET /v1/memory?scope=user
Authorization: Bearer <session token>
{
"records": [
{
"id": "mem_9f3a21",
"text": "Is evaluating a migration away from the legacy scheduler.",
"scope": "user",
"visible_to": "you only",
"derivation": "inferred",
"evidence": {
"conversation_id": "thr_88f012",
"turn": 6,
"quote": "how do I export all schedules at once?",
"deep_link": "/conversations/thr_88f012#turn-6"
},
"written_at": "2026-01-22T14:06:11Z",
"written_by": "extractor:v7 / gate:promotion-v3",
"last_used_at": "2026-04-02T09:12:44Z",
"use_count": 11
}
],
"next_cursor": null
}There is no user_id in that request, and its absence is the same design decision as chapter 4's tool schema rather than a coincidence of REST style. The endpoint reads the principal, and a user who wants someone else's memory has no field to put the request in. The one case that legitimately needs another subject — a support engineer investigating a complaint, an admin exercising a request on behalf of a departing employee — comes through the impersonation token from chapter 4, which means the same handler serves both and the audit trail distinguishes them.
Every call to this endpoint is itself an event worth recording, which surprises teams who think of it as a read-only settings page. Reading a person's remembered facts is an access to personal data, and when the reader is staff rather than the subject, the record of that access is the thing that makes the impersonation path safe to offer at all. The audit row wants the acting principal, the subject, the time, the number of records returned, and the reason string from the token; it does not want the record contents, since copying the data into the audit log to prove you read the data creates a second copy governed by different retention.
Deletion goes through the same path and returns a receipt rather than a status code, because "204 No Content" tells the user nothing they can hold onto:
POST /v1/memory/mem_9f3a21/delete
{
"receipt_id": "del_2f81c4",
"requested_at": "2026-04-11T10:02:30Z",
"requested_by": "usr_4471",
"primary_deleted": 1,
"derived": [
{ "store": "vector_index", "status": "queued", "job_id": "job_7c31" },
{ "store": "response_cache", "status": "purged" },
{ "store": "traces", "status": "aged_out_by", "date": "2026-05-11" },
{ "store": "backups", "status": "aged_out_by", "date": "2026-07-10" }
]
}That receipt is the interface contract for chapter 8, and the reason to introduce it here is that the inspection view is where a user forms their expectation of what deletion means. A screen that shows a record disappearing instantly has promised something the system has not yet done.
What provenance costs, and why it cannot be added later
Most of these fields are free at write time — the extractor already knows the conversation, the turn and its own version, and the cost of persisting them is a few hundred bytes on a row that already exists. Two of them are not free.
last_used_at and use_count require writing on the read path, which is the expensive direction. A naive implementation appends a retrieval event per record per retrieval, and at the vendor's volume — roughly 900,000 agent turns a month, each retrieving eight to ten records — that is about 8 million rows a month, growing without bound and touched only by a screen a small fraction of users open. The layout that works is a counter update rather than an event log: a single UPDATE … SET use_count = use_count + 1, last_used_at = now() batched per turn, which costs one write per retrieval instead of ten rows, and keeps thirty days of full event detail only for records under active investigation. The ratio is the whole saving, and it is close to an order of magnitude because it is set by how many records a turn retrieves. The full history is worth having when someone disputes a specific answer, and it is not worth having for every record forever.
The evidence quote is the other one with a real cost, and its cost is governance rather than storage. Copying a turn of conversation onto the memory record duplicates user content into a store with a different lifetime, so a user who deletes the conversation still has the quoted fragment sitting on a memory row. Holding a reference — conversation id and turn number — and resolving it when the screen is rendered avoids the duplication and introduces a dangling reference when the conversation is gone. That is the better failure of the two, and the reason is that the two designs fail in opposite directions with respect to what the user asked for. A copy silently loses nothing and keeps text the user believed they removed; a reference silently loses evidence the user might have wanted. Only one of those contradicts an action the user actually took, which is why the dangling reference is the better failure: the screen can then say "the source conversation has been deleted" rather than quietly retaining text the user believed they removed.
Neither field can be reconstructed after the fact, which is the constraint that makes this a decision to take before shipping memory rather than after. The reason is mechanical: the origin of a record exists only in the conversation logs, those logs have a retention window, and once the window passes there is nothing left to recover the origin from. The vendor discovered this with 1.2 million existing records whose origin was genuinely unrecoverable, since the conversation logs had a 90-day retention and most of the records were older. Their options were to show those records with an honest "origin not recorded (written before March 2026)" label, or to delete them and let the system relearn. They shipped the label and deleted anything in that population that had not been retrieved in six months, which removed 61 percent of it and made the remaining screen defensible.
Three things the screen gets confused with
It is not a conversation log. Users conflate the two immediately, and the distinction is worth making in the interface copy: the transcript is what was said, and memory is the small set of durable statements the system extracted from it, which is why deleting a memory does not delete the conversation and deleting a conversation does not necessarily remove memories already derived from it. Products that quietly couple the two produce the surprise where clearing chat history leaves the assistant still confidently asserting a fact from a conversation the user believes they erased.
It is not an explanation of the model's behaviour. Showing a record does not establish that it influenced any particular answer, which is why last_used_at and use_count are on the list — retrieval events are the closest available evidence, and they are honest about being retrieval rather than causation. The gap between the two is real: a record can be retrieved into a prompt and have no effect on the output, and an answer can be shaped by the system prompt, the conversation or the tools rather than by anything in memory. A user who deletes a memory and sees the same behaviour persist will conclude the deletion did nothing, so it is worth being explicit that context includes more than memory.
It is not a subject access request export. A DSAR — a data subject access request, made under Europe's General Data Protection Regulation, or its equivalent under California's Consumer Privacy Act — covers everything you hold about the person, including transcripts, telemetry, support tickets and billing, on a statutory clock. The memory screen covers one store, in real time, in a form designed for pruning rather than for completeness. Presenting it as the fulfilment of a legal right is a compliance error, and pointing to it from the DSAR process as one of several sources is correct.
The screen also raises a question that chapter 2 set up and did not close, and it is the one case where more provenance is worse. An organisation-scoped record carries source_user, so showing full provenance to every colleague who can read the record would disclose who told the assistant — which is precisely the personal fact the scope decision was supposed to protect. The resolution is that provenance is visible to the subject and to authorised staff, while other readers of a shared record see the text, the scope and the date without the author. Getting this backwards turns a well-designed transparency feature into the disclosure it was meant to prevent. The general form of the rule is that provenance is disclosed to the person it is about rather than to everyone who can read what it explains.
Provenance is what makes a record evaluable, and evaluable is what makes deletion an act rather than a gesture. The remaining problem is the one the receipt above hints at. The screen makes deletion available and the user expects it to mean what the word means, and a delete against the primary record is the first of five or six things that have to happen.
Memory — scope and privacy — read the current version at https://daviddominguez.dev/learn/en/memory-scope-and-privacy/provenance-is-what-makes-a-memory-inspection-view-worth-shipping