for a while, "memory" in ai products meant saving the conversation.
store every message. embed it. retrieve the closest chunks later. call the result personalization.
this works in a demo.
then the user changes jobs, drops the project, or corrects something the system saved months ago.
the assistant still retrieves the old detail and acts as if nothing changed.
now memory is creating work instead of removing it.
* good memory saves the details that prevent repeated work and leaves the rest alone.
memory is not chat history
chat history tells you what happened. durable memory decides which parts are still worth using later.
a useful logical split is:
- working memory for the task happening right now
- profile memory for stable preferences and constraints
- episodic memory for past decisions and outcomes
- procedural memory for methods that should be reused
these categories can share one database. they still need different schemas, lifetimes, and retrieval rules.
put everything in one vector index and old task chatter starts outranking instructions that should have stayed stable.
safety and authorization rules do not belong in ordinary memory. "never push without approval" needs an enforceable policy layer. forgetting a retrieved preference must never grant permission.
raw storage may be cheap. maintaining memory that is secure, testable, and actually deletable is not.
the filter matters more than the database
save a candidate only when it is likely to matter again, its scope is clear, and storing it does not expose unrelated context.
if it conflicts with an older record or needs fresh consent, stop and review it before writing either version back.
def classify_memory(candidate, existing) -> MemoryDecision:
if candidate.contains_credential:
return REJECT
if candidate.needs_consent or conflicts(candidate, existing):
return NEEDS_REVIEW
if candidate.expires_at and candidate.expires_at <= now():
return REJECT
if candidate.user_confirmed and candidate.expected_reuse:
return PERSIST
return IGNORE
this is policy pseudocode, not a production permission system.
credentials belong in a secret manager, not assistant memory. sensitive personal data needs a clear purpose, narrow access, and user control.
rejecting most candidates keeps the store small enough for users and developers to audit.
forgetting is a feature
a deployment date can expire after the release. a repository preference should stay until the user changes it.
an assistant that only adds records becomes less accurate over time, even if every record was true when written.
useful memory needs expiry, supersession, and deletion.
consider these two records:
user is deploying the app on friday.
user prefers private repositories by default.
the first record may need to survive restarts, but only inside that project and only until the release is finished. the second may deserve durable profile memory.
without scope and expiry, the assistant may still behave as if the friday deployment is active months later.
deletion also has to reach vector indexes, caches, summaries, and other derived copies. backups need a retention policy the user can understand.
retrieval is where memory becomes a product
saving a useful fact does not help if the retriever injects it into every task.
retrieve too broadly and the prompt fills with noise. retrieve too narrowly and the user keeps repeating the same instruction.
a practical retrieval policy can stay simple:
candidates = semantic_search(query)
eligible = [
record for record in candidates
if record.user_id == task.user_id
and record.tenant_id == task.tenant_id
and record.status == "active"
and not record.is_superseded
and record.scope.allows(task)
and record.permissions.allow(task.actor)
and not record.is_expired()
]
ranked = rank(
eligible,
by=("task_relevance", "specificity", "user_confirmed")
)
return [record for record in ranked if record.score >= MIN_SCORE][:3]
freshness should depend on the record type. an old project deadline is suspicious. an old accessibility preference may still be valid.
log which memories were retrieved and whether they helped. the policy needs to be visible enough to test against real failures.
if a preference keeps appearing in unrelated tasks, retrieval is too broad. if the user repeats the same correction every week, the write, update, or retrieval pipeline is failing.
wrong memory is often worse than no memory
when an assistant forgets, the user repeats a detail.
when it remembers incorrectly, it can take the wrong action without asking again. the risk grows when the action is consequential.
that is why every durable record should carry provenance:
- where it came from
- when it was last confirmed
- whether the user stated it or the system inferred it
- what older record it replaced
inferred records should expire sooner. they should not authorize consequential actions, and ambiguous details should be confirmed before use.
"the user chose python for this project" is not the same as "the user always prefers python."
if the system collapses those statements into one preference, it will carry a project choice into unrelated work.
memory should be inspectable
users need a plain list of saved records with edit and delete controls. they should not have to negotiate with the model.
this is partly a privacy feature. it is also a product quality feature.
user review catches changed assumptions that offline evals miss. it should complement automated tests and incident monitoring, not replace them.
a record can stay boring:
{
"id": "mem_01",
"user_id": "user_42",
"fact": "user prefers private repositories by default",
"kind": "preference",
"source_type": "explicit_user_message",
"source_ref": "message_8f2a",
"scope": {
"tasks": ["repository_creation"]
},
"status": "active",
"sensitivity": "low",
"created_at": "2026-07-24",
"updated_at": "2026-07-24",
"confirmed_at": "2026-07-24",
"expires_at": null,
"supersedes": null
}
this is not a complete production schema. it is enough to tell who the record belongs to, where it came from, where it applies, and whether it was replaced.
what builders should measure
count the failures memory is supposed to prevent:
- repeated corrections per active user
- irrelevant or harmful retrievals
- stale or superseded records that reach the prompt
- correction-to-update latency
- expiry and deletion completion rate
- task success with memory compared with memory disabled
- token and latency overhead from retrieved context
a high edit rate is not automatically bad. it may mean the controls are easy to find. "memory changed the answer" is not enough either; influence is not correctness.
if users still repeat the same preference every week, storing more messages is not helping.
closing thought
personal ai should save the details that prevent repeated setup.
it should show those details, expire temporary ones, and make deletion easy.
retrieved memory is still fallible context. consequential actions need policy checks and confirmation outside the memory system.
save less, attach an expiry when the fact can go stale, and keep every durable record easy to remove.