Chapter 7 of 9 · 14 min read
Correcting a stored fact begins with finding it from the wrong answer
On this page
A ticket arrived at Arbor reading: "Your assistant told my colleague on Tuesday that our plan includes SSO. It doesn't, and now he's told the whole team. Please make it stop saying that."
Everything hard about correction is in that sentence, and almost none of it concerns SSO itself — single sign-on, where staff reach a product through their company's identity provider instead of a password held by the product, which here is merely the entitlement the assistant got wrong. The complainant is not the person who talked to the assistant. The wrong claim is not in front of anyone — it is inside a store, in a namespace belonging to an account rather than to either of them, expressed in wording nobody has seen. The requested action is "make it stop", which is not an operation the store supports. And the damage that prompted the ticket happened outside the system entirely, in a conversation between colleagues that no correction will reach.
The read log is what makes the search finite
Going backwards from an answer to a record is only possible if you wrote down which records went into that answer, and it is worth being concrete about why the alternative fails rather than merely being slower. Retrieval is a function of the query text, so re-running a similar question samples a different set of records; there is no sequence of guesses that converges on the set a particular past response actually saw. The information is not hard to recover, it is gone.
Keeping it is cheap. The log entry is small, and the volume is unalarming — at Arbor's 12,000 turns a day, roughly 300 bytes per entry is 3.6 MB a day, or under 1.3 GB a year with a 90-day retention window that costs less than the coffee budget. A ninety-day window also means the store never holds a whole year of it; at any moment it is carrying about a quarter of that annual figure.
{
"response_id": "rsp_5b81c9",
"session_id": "ses_2fa0",
"user_id": "usr_7734",
"account_id": "acc_318",
"at": "2026-04-14T10:07:52Z",
"retrieved": [
{"id": "mem_c81f30", "rank": 1, "score": 0.86, "in_prompt": true},
{"id": "mem_44a9b1", "rank": 2, "score": 0.79, "in_prompt": true},
{"id": "mem_0d2e77", "rank": 3, "score": 0.71, "in_prompt": false}
],
"tools": ["lookup_plan_features"],
"answer_sha": "9f2c…"
}in_prompt earns its place as a separate field from rank, because assemblers drop records for token budget and a record that was retrieved but never shown cannot have caused anything. Without the distinction, the third record in that entry would be a suspect in every investigation it appears in, and it was never in front of the model.
With this log, a vague complaint becomes a query. Filter responses to the account and a time window, keep those whose answer text matches the disputed claim, intersect the record sets, and rank by how often each record appears:
SELECT r.id, r.statement, COUNT(*) AS appearances
FROM read_log l
JOIN LATERAL jsonb_array_elements(l.retrieved) AS ret ON TRUE
JOIN memories r ON r.id = ret->>'id'
WHERE l.account_id = 'acc_318'
AND l.at BETWEEN '2026-04-13' AND '2026-04-16'
AND (ret->>'in_prompt')::bool
AND l.answer_text ILIKE '%SSO%'
GROUP BY r.id, r.statement
ORDER BY appearances DESC;Each clause of that query is doing one job in the narrowing. The account filter bounds the search to one tenant. The date range bounds it to the window the complaint describes. The in_prompt filter removes records that could not have caused anything. The text match on the answer keeps only responses that actually made the disputed claim. And the count-and-order at the end ranks the survivors by how consistently they were present when the claim was made, which is the closest thing to a causal signal available without re-running anything. That query returned two rows in eleven seconds.
Two properties of the log make the difference between one that is useful and one that disappoints, and both follow from what a complaint looks like when it arrives. It has to store the record ids rather than the rendered memory text, because ids survive edits and let you ask what happened to a record after the fact, while a copy of the text tells you only what was shown. And it has to be queryable by answer content, which means either keeping the answer text for the retention window or keeping a searchable digest of it, since almost every real complaint arrives as a paraphrase of something the assistant said and never as a record id. Teams that log only ids and hashes can confirm a hypothesis and cannot form one — with hashes you can ask whether a given response contained a given record, and you cannot ask which responses said something about SSO, which is the only question a ticket ever poses.
Without the log, this investigation is a person typing SSO questions into staging and hoping the same records surface. That does not converge, and the usual outcome is a ticket closed as a model hallucination with a prompt tweak attached, which leaves the record in place and produces the same complaint from a different colleague a month later. The read log is therefore not observability nicety; it is the difference between a store that has a repair path and one that does not.
The retention window on the log has a consequence that is easy to miss and hard to fix afterwards. Records have no expiry unless somebody gave them one, and the log does. So a record written last spring and disputed this autumn cannot be traced at all. The search is no harder than before; the evidence has simply aged out, and the investigation falls back to the non-converging version above. The two lifetimes should be chosen together: either the log's window covers the period a record can plausibly stay live, or the store's records carry an expiry short enough to fall inside it, and a system where the record outlives its own audit trail has a repair path with a date on it.
Who is allowed to change it
Finding the record raises a question the schema does not answer, which is who may act on it. The complainant is an account admin, the record sits in the account namespace, and the person whose sentence produced it is a colleague who is not on the ticket. Four parties can plausibly hold the pen, and they need different permissions.
The user whose namespace it is can always delete, and should be able to, since a record they cannot remove is a record you had no business writing under the consent rules from chapter 1. Editing is a different matter, and the reason is provenance rather than trust. A record's arguability comes from the pairing of a statement with the quote it was extracted from, so a user who rewrites a record's statement destroys the provenance that made it arguable. The quote underneath no longer supports what the record now says, and the record has become an assertion with a citation that does not check out. So the clean version is that users delete and restate rather than edit, which produces a new record with its own honest provenance.
Support engineers acting on a ticket need the same powers plus the ability to see the source quote, which is exactly the audit trail that lets them tell a wrong extraction from a user who has changed their mind — two situations that look identical from the record alone and call for opposite actions. An automated contradiction detector can flag and downgrade — mark a record disputed so retrieval deprioritises or excludes it — and should not resolve, because resolving requires knowing which side is true and a detector by construction knows only that two things disagree.
The model is the party to be careful with. Letting the agent edit its own store closes a loop with no human in it, and the seeded-instruction result from chapter 5 shows what that loop is exposed to: user text becomes a record, the record becomes trusted context, and trusted context drives the next edit. An agent that can write and rewrite memory can be talked into rewriting memory. The workable arrangement lets the model propose corrections as candidates with the same gate and the same provenance requirements as any other write, and reserves the destructive verbs for humans and for deterministic rules.
The namespace the record sits in is doing quiet work in all four cases, and it is worth naming because it decides who counts as "the user". A record in an account namespace is visible to and actionable by every admin on that account, which is what let the complainant act on a colleague's sentence. A record in a personal namespace is not, and a support engineer moving one to the other is changing who may delete it. That choice is made when the schema is designed and it is very hard to revisit, because the answer determines which deletions a data subject request is entitled to.
Three actions, three different histories
Having found the record and established who may act, there are three things to do with it, and they differ in what remains afterwards.
| Edit in place | Supersede | Delete | |
|---|---|---|---|
| What the store holds after | One record, new text | Two records, old marked superseded | Nothing (or a tombstone) |
| History of the error | Lost unless separately audited | Preserved and linked | Lost, except in the audit log |
| Retrieval behaviour | New text competes immediately | Old excluded by filter, new competes | Nothing competes |
| Answers "why did it think that?" | No | Yes | No |
| Good for | Typos, wrong scope, wrong expiry | A claim that was true and changed, or a disputed correction | Never-should-have-existed records, and erasure requests |
| Failure mode | Silent rewrites nobody can audit | Filter forgotten, both records retrieved | Re-extraction writes it again next week |
Read the table across rather than down and one row separates the three actions where the others merely rank them. On what the store holds, on retrieval behaviour and on storage the three differ by degree, and on "why did it think that?" supersession is alone on one side: it is the only action that leaves the store able to explain an answer it gave last month. Everything else supersession costs — a second row, a filter that has to be right, a chain somebody has to walk during an erasure request — is the price of that one column, which is the right way to decide whether you want it.
Choosing between them is mostly a question of whether the old value was ever true, and that single test decides the row. A claim that was correct and then stopped being correct is history, and history is worth keeping because it explains past answers to anyone reviewing them; supersession is the right verb. A claim that was never correct is an extraction bug wearing a record's clothes, and preserving it in a store users can read means showing them something the system invented about them. Editing in place sits between the two and should be restricted to fields that do not change what the record claims — a wrong scope, a missing expires_at, a misspelled name — because a rewritten statement with the original source quote underneath it is a record whose provenance now lies.
Deletion leaves behind at most a tombstone, a stub row noting that a record existed and was removed without retaining what it said, and it has the failure mode that catches people out. The mechanism is worth spelling out because it is not obvious that deletion can fail. Extraction runs over conversations, conversations are retained on their own schedule, and that schedule is longer than the memory store's. So a record that was extracted once from a conversation that is still in your retention window will be extracted again the next time anything re-processes that conversation, and deletion without a suppression entry is a loop rather than a fix.
Arbor's fix was a small suppressed_claims table keyed by user and normalised claim, consulted by the gate before writing, populated automatically whenever a user deletes a record. It is eleven rows for most accounts and it stops the most infuriating class of bug, where the assistant forgets a correction and re-learns the mistake. The design decision hiding in there is the normalisation: the table has to key on a canonical form of the claim rather than on the deleted record's exact text, or the next extraction phrases it slightly differently and walks straight past the suppression.
The disputed state is the one most implementations omit, and it is what lets an automated detector act without deciding. A record in disputed is excluded from retrieval and visible in the user's memory settings with the conflict shown, which converts a silent wrong answer into a question the user can settle in one click. The full machinery of supersession chains, of reconciling versions across namespaces, and of propagating erasure through derived copies belongs to later topics; what the write decision needs from this diagram is narrower — every state an admitted record can reach has to be reachable by somebody. Read the diagram for arrows with no actor behind them, because a state you can enter and nobody can leave is where records go to become permanent.
What no correction retracts
Deleting mem_c81f30 on Wednesday does nothing about Tuesday. The read log said the record had reached 23 responses across four sessions and three colleagues, and the assistant had told at least one of them, in writing, that SSO was included. The team had planned a rollout around it.
This is where the tempting belief fails, and it is worth naming because it is the belief the whole correction machinery invites. Having deleted the record, the state of the store is now correct, and correctness of the store is a quantity you can check and confirm. The quantity that actually varies is what the record has already caused, which the store cannot report on and which does not change when the row does. A team that measures the repair by inspecting the store afterwards will find it clean every time, whether the record shaped one answer or a hundred.
Correction is therefore two jobs, and the second one is usually skipped. Enumerating affected outputs is mechanical once the read log exists — the same query, without the time filter — and the useful step is sorting them by consequence rather than counting them, because the count is dominated by responses where nothing turned on the record and the tail is where the obligations are. Arbor's rule sorts responses into three classes. Informational responses, where the record was in context and the answer did not turn on it, need nothing. Acted-on responses, where the user did something because of the answer, need a notification. Committed responses, where the assistant stated something the company would be held to, need a human to decide what the company is going to honour. The sorting has to be done by reading, which is the expensive part, and the read log makes it finite rather than cheap. Of the 23 SSO responses, 19 were informational, 3 were acted-on, and 1 was committed, and that single response is the reason a CSM called the account rather than sending a templated correction email.
Those proportions are the argument for sorting rather than counting. Nineteen of the twenty-three needed nothing at all, so a policy that treats the whole set uniformly either wastes effort on the nineteen or under-serves the one, and the one is the only response with money attached to it.
The obligation this creates reaches back into the write decision, which is the point of tracing it. Admitting a fact commits you to a repair path that somebody can actually walk, and the path has five parts rather than one: a read log that connects answers to records, provenance that makes a record arguable, an authorisation model that says who may act, a suppression list so corrections stick, and a way of finding the outputs the record already shaped. They are not independent. A read log without provenance finds the record and cannot tell you whether it was ever right. Provenance without a suppression list gets the record deleted and re-learned. A gate whose records carry no source quote, in a system with no read log, has no repair path at all, which means its precision is the only thing standing between a bad write and permanence.
Every one of those mechanisms is work that the cheapest repair avoids entirely, which is the repair for the record that was never needed. The SSO claim is a good example: whether a plan includes SSO is a field in the billing system, current, authoritative and one call away. That whole category of fact should never have been a memory candidate, and separating the facts that belong in a store from the ones your own systems already hold is the next chapter.
Memory — the write decision — read the current version at https://daviddominguez.dev/learn/en/memory-the-write-decision/correcting-a-stored-fact-begins-with-finding-it-from-the-wrong-answer