skip to content

A user revises a fact you already stored in memory — how do you resolve the write?

level: seniorimportance: should knowfreq 45%

answer

  1. writing is resolution, not insertion
  2. four outcomes, not one
  3. supersede rather than accumulate
  4. the boring outcome is the common one
  5. two live versions means random answers

basics

~20 s

Compare the candidate fact against what is already stored and choose one outcome: add it as new, update the existing entry in place, delete an entry it invalidates, or do nothing when it is already known. Blind appending leaves both versions alive.

solid answer

~50 s

Writing to memory is a resolution step, not an append. The standard shape — popularised by Mem0-style semantic memory layers — is a four-way decision per candidate fact: **ADD** when it is genuinely new, **UPDATE** when it supersedes an existing entry, **DELETE** when it invalidates one, and **NOOP** when the store already says this. If a personal-finance assistant stored "monthly budget is 2,000" and the user now says 3,000, the correct write is UPDATE — not a second ADD. Appending instead leaves two contradictory budget facts, and whichever one lands in a later window wins, so the assistant becomes non-deterministically wrong. NOOP is the most common outcome in practice and the one people forget to implement; without it every turn churns the store. Because the decision usually needs a model call over the neighbouring existing facts, run it at end of turn or asynchronously rather than in the user's latency path.

code

json · 8 lines
json
{
  "candidate": { "subject": "user", "attribute": "monthly_budget", "value": "3000" },
  "existing": [
    { "id": "m_41", "subject": "user", "attribute": "monthly_budget", "value": "2000" },
    { "id": "m_57", "subject": "user", "attribute": "filing_status", "value": "joint" }
  ],
  "decision": { "op": "UPDATE", "target": "m_41", "value": "3000" }
}

go deeper

for a junior

Know that saving a memory is not a plain insert: if the user changes a fact you already stored, the old entry has to be replaced rather than kept alongside the new one.

for a middle

Explain the four outcomes — add, update, delete, do nothing — and why an append-only store ends up holding two live versions of the same fact. Be able to walk the budget-revision example end to end.

for a senior

Show the engineering judgement: resolution needs a model call over neighbouring facts, so it belongs off the response path, and the schema should make same-attribute collisions detectable instead of relying on prose comparison. Name context clash as the concrete failure.

for a principal

Own the policy questions: what counts as a single-valued attribute, whether superseded values are retained at all, who can see and delete them, and how you keep a correction from taking a full session to take effect when it matters.

## Append-only memory is the default mistake The naive memory write is "extract facts, insert rows." It works for a week and then rots, because users revise themselves constantly: budgets change, job titles change, preferences reverse, a stated constraint stops applying. An append-only store accumulates every version of every fact with no marker saying which is current. The damage shows up later, in the context window. Retrieval brings back some subset of the stored facts; if that subset contains both "monthly budget is 2,000" and "monthly budget is 3,000", the model is holding two contradictory statements at once — the *context clash* failure mode. There is no principled way for it to choose, so the answer depends on ordering, phrasing and luck. A memory store that produces confidently inconsistent answers is worse than no memory at all, because users trusted it. ## The four-way decision The accepted shape resolves each candidate fact against what is already stored: - **ADD** — the fact is new and does not contradict anything. Insert it. - **UPDATE** — the fact concerns the same subject and attribute as an existing entry but carries a different value. Replace the value in place, keeping one current entry for that attribute. - **DELETE** — the fact invalidates an existing entry without replacing it ("I no longer have a car", "we cancelled that account"). Remove the stale entry. - **NOOP** — the store already carries this. Do nothing. The budget example: `"my budget is now 3000"` against a store holding `budget = 2000` is an UPDATE. Getting this wrong in either direction is instructive. ADD gives you the contradiction above. DELETE-then-ADD churns the entry and loses whatever history the row carried. NOOP silently ignores the user's correction, which is the most annoying failure of the four because the user watched themselves say it. ## Why NOOP is the interesting one In a long-lived assistant, most candidate facts extracted from a turn are things the store already knows — the user restating their situation, the extractor re-firing on the same sentence in a re-read transcript. Systems without an explicit NOOP path write anyway, and the store grows with near-duplicates that all say the same thing in slightly different words. That inflates the store, dilutes anything ranked against it, and multiplies the surface for future contradictions. NOOP is not a degenerate case; it is the modal outcome and it must be cheap. ## How the decision is actually made Resolution needs comparison against the *relevant* existing entries, not the whole store. In practice: pull the small set of stored facts about the same subject or attribute, then have a model decide the operation for each candidate against that set. This means a write costs an extra model call and some latency. That cost drives the timing. Doing resolution inline, before responding, taxes every user turn for a benefit that is only felt later. The common design is to resolve at end of turn or end of session, or fully asynchronously in a background consolidation pass. The tradeoff is a window where a just-stated correction is not yet reflected — usually acceptable, occasionally not (a user correcting a safety-relevant constraint should take effect immediately, which argues for a fast path on explicitly-flagged corrections). ## Structural aids A few design choices make resolution tractable rather than heroic: - **Give facts a subject and an attribute.** "user / monthly_budget / 3000" makes the collision detectable by key. Free-text-only memories force the model to notice contradictions by reading, which it does imperfectly. - **Distinguish superseding from coexisting.** "Prefers window seats" and "prefers aisle seats" conflict; "speaks Spanish" and "speaks Portuguese" do not. Attribute cardinality — single-valued versus multi-valued — encodes this so the resolver does not have to guess. - **Keep one current row per single-valued attribute.** If you need the prior value for audit, keep it out of the retrieval path so it can never re-enter a window as a live fact. ## What to say in the interview Name the four operations, insist that NOOP is a first-class outcome, and explain the failure you are preventing: two live versions of one fact producing non-deterministic answers. Then show the engineering judgement — resolution costs a model call, so it runs off the response path, and the schema should make same-attribute collisions detectable rather than leaving them to prose comparison.

  • How do you decide whether two facts actually conflict rather than simply coexist?
    Model the attribute's cardinality. A single-valued attribute like monthly budget or filing status can hold one current value, so a new value supersedes the old one. A multi-valued attribute like languages spoken or dietary restrictions accumulates, and two entries are not a contradiction. Encoding this in the schema removes most of the judgement from the resolver; only genuinely ambiguous attributes need a model to adjudicate.
  • What is the cost of running this resolution on every turn, and how do you manage it?
    Each write costs a lookup of neighbouring facts plus a model call to decide the operation, so doing it inline taxes every user turn. The usual design moves resolution to end of turn or a background pass, accepting a short window where a correction is not yet stored. Keep a fast path for explicit corrections the user expects to take effect immediately, and skip resolution entirely for turns that yielded no candidate facts.
  • If a user later disputes something the assistant believed, what does this design give you?
    A single current row per attribute, with a stated value you can show them and delete on request. That is only possible because facts live in an inspectable store rather than in weights or in a prose blob. It is also why superseded values, if kept at all for audit, must sit outside the retrieval path — anything reachable by retrieval can re-enter a window and be presented as current.

saying these in an interview costs you the question

  • Appends every extracted fact and lets recency sort it out
  • Has no NOOP path, so duplicates accumulate every turn
  • Treats all attributes as multi-valued, so nothing ever supersedes
  • Runs the resolution model call inline and blames memory for latency
  • Keeps superseded values in the same retrievable pool as current ones

context