Chapter 98 · Memory and state
Letting a user see and edit what it remembers
A memory the user cannot see is a model forming opinions about them in private. The page, the inline controls, what people actually do with them, and the write path that makes it honest.
7 min read
Everything in this section so far has been machinery the user never sees. That is the problem.
A system storing notes about someone, using them to shape answers, and offering no way to look at them is a system forming private opinions about a person who cannot check them. The engineering case is narrower and just as strong: users are the only reliable detector of a wrong memory. Chapter 96's sweep catches contradictions against the corpus. It cannot catch "prefers detailed answers" stored for someone who said that once, about one topic, in frustration.
Two surfaces
| Surface | Answers | Who uses it |
|---|---|---|
| A memory page | "What does it know about me?" | Rarely visited, heavily used when it is |
| Inline, at the answer | "Why did it say that?" | Where almost all editing happens |
Most implementations build the first and stop. Atlas's numbers across three months:
| Action | Via the page | Inline |
|---|---|---|
| Memories viewed | 1,240 | 8,900 |
| Memories deleted | 38 | 211 |
| Memories corrected | 9 | 97 |
Inline editing accounts for 85% of deletions and 92% of corrections, for an unsurprising reason: a wrong memory is noticed when it produces a wrong answer, and that is the moment it is cheap to fix. Asking someone to remember the problem, navigate to a settings page and find the row is asking them to do the work later.
The inline control
// Rendered under any answer where recall contributed. The same block the
// model saw, with the same wording, so there is no gap between what
// influenced the answer and what the user is shown.
interface RecalledDisplay {
statement: string
kind: MemoryKind
writtenAt: Date
sourceTurn: { id: string; question: string; at: Date } // where it came from
influence: 'shaped-format' | 'narrowed-retrieval' | 'in-context'
}<details className="recalled">
<summary>Used 2 notes from earlier conversations</summary>
{recalled.map((m) => (
<div key={m.id}>
<p>{m.statement}</p>
<p className="meta">
noted {fmtDate(m.writtenAt)} from “{m.sourceTurn.question}”
</p>
<button onClick={() => forget(m.id)}>Forget this</button>
<button onClick={() => edit(m.id)}>Correct it</button>
</div>
))}
</details>The sourceTurn line is what makes it actionable. "Prefers detailed answers" is unarguable in isolation; "prefers detailed answers — noted 14 August from 'can you give me the full breakdown on this one'" is obviously an over-generalisation from one request, and that is what the user clicks Forget on. Showing provenance roughly tripled the correction rate against an earlier version that showed the statement alone.
What people actually delete
Of Atlas's 211 inline deletions:
| Reason | Share |
|---|---|
| Over-generalised from one instance | 44% |
| True once, no longer true | 27% |
| Correct but unwanted ("stop tracking that") | 18% |
| Never true | 11% |
The top row is the design feedback. Forty-four percent of deletions are the system inferring a standing preference from a single request — exactly the failure chapter 93 traded recall away to avoid, still happening at this rate with the stricter rules. It pushed the preference rule to require either an explicit standing phrase ("always", "from now on") or three occurrences, which cut that share to 19%.
The third row is the one that matters ethically rather than mechanically. "Correct but unwanted" means the user is fine with the fact and objects to it being stored. There is no engineering answer to that, only a product one: honour it, and do not re-learn it.
// A deletion is a signal, not just a row removal. Deleting without recording
// the refusal means the same memory is written again next week, which reads
// to the user as the system ignoring them.
export async function forget(memoryId: string, userId: string, reason?: ForgetReason) {
const m = await get(memoryId, userId)
await hardDelete(memoryId)
await suppress({ userId, statementHash: semanticHash(m.statement), until: addDays(new Date(), 365) })
await auditLog.record('memory.forget', { userId, kind: m.kind, reason })
}// Suppression is checked at write time, by semantic similarity rather than
// exact text, because the second attempt is rarely worded identically.
async function isSuppressed(userId: string, statement: string): Promise<boolean> {
const hits = await nearestSuppressions(userId, await embed(statement), { limit: 3 })
return hits.some((h) => h.score > 0.88)
}Before suppression, 23 of the 211 deleted memories were re-written within a month. After, that fell to one, which turned out to be a genuinely different statement that happened to clear the threshold.
Correction is a write, so it goes through the write path
// A corrected memory is a new memory superseding the old one, with the user
// recorded as its author. It does not edit the row in place.
export async function correct(memoryId: string, userId: string, statement: string) {
const old = await get(memoryId, userId)
const id = await writeMemory(userId, {
kind: old.kind,
statement,
author: 'user', // never expires on the model's schedule
confidence: 1.0,
})
await update(memoryId, { supersededBy: id })
return id
}Two consequences of author: 'user'. The memory is exempt from kind-based TTL, because a person stated it deliberately and expiring it in seven days would be absurd. And it outranks a model-written memory in chapter 96's supersession check, so a later inference cannot quietly overwrite it.
The corpus still wins over both. A user-authored note saying Northline is on 30-day terms loses to the contract, and the answer says so. The ranking is corpus, then user-authored memory, then model-written memory, and it holds everywhere.
The page, for the questions inline cannot answer
// Low traffic, high stakes. It exists for three questions.
// - everything stored, grouped by kind, with dates and provenance
// - export, as JSON and as plain text
// - delete everything, with one confirmation and no soft deleteThirty-eight deletions in three months makes it look unused. It is the page people visit once, when they first learn the assistant remembers things, and what they find there decides whether they keep using it. Chapter 72 argued that a confirmation's value is not in how often it is used; the same is true here.
A memory page is read like a privacy policy: rarely, carefully, and at exactly the moment the reader is deciding whether to trust the thing.
Key points
- Users are the only reliable detector of a wrong memory. The corpus sweep cannot catch an over-generalised preference.
- Build the inline control, not just the settings page. Inline accounted for 85% of deletions and 92% of corrections.
- Show where the memory came from. Provenance roughly tripled the correction rate against showing the statement alone.
- 44% of deletions were over-generalisation from one instance. Requiring a standing phrase or three occurrences cut it to 19%.
- Record a deletion as a suppression, matched semantically. Re-writing a deleted memory reads as the system ignoring the user: 23 cases fell to one.
- A correction is a new memory superseding the old, authored by the user, exempt from model TTL and outranking model-written notes.
- The ranking holds everywhere: corpus, then user-authored memory, then model-written memory.
- The page is visited rarely and decides trust. Export and delete-everything belong on it.
Next: memory as an attack surface — what an attacker does with a store that writes itself.