File-Based and Reflexive Memory
Ask a memory system "Banshee Labyrinth capacity" — a venue name plus an exact term, not a paraphrase — and a system built only on vector search (finding memories by meaning, via embeddings) gets a shakier answer than you'd expect: it finds the right venue through a loose semantic match, but its confidence sits close to unrelated "underground pub" venues, because embeddings are built to catch meaning, not exact names. A search that also checks for exact keyword matches (BM25, the decades-old exact-term-match algorithm underneath most search engines) nails this one instantly, with a much wider confidence margin. Neither approach alone is enough — production systems fuse both. That gap, between what most systems that call themselves "memory" actually implement (write it down, then search it by meaning) and what a real memory system needs (nine distinct operations, this lesson's real subject), is where this lesson starts.
Prerequisite: Lesson 20, "Associative, hierarchical, graph, and vector memory: choosing the right structure" — you should have the full eleven-type taxonomy in hand and know that most 2024-era "memory" systems only implemented two of the operations this lesson covers. After this lesson, you can: name all nine CRUD++ operations a real memory system needs beyond simple write-and-search, and explain — with a worked comparison — why pure vector search fails on exact-match and constrained queries in ways hybrid retrieval doesn't.
Every lesson so far has answered "what gets stored, and in what shape." This one answers a different question: once something is stored, what operations does a memory system actually need to support? Rod Rivera's own framing of the gap is blunt: most 2024-era "memory" products only really did Write and Search.
Write: the hardest decision in memory engineering
What gets stored shapes everything downstream, and the decision of what constitutes a fact worth storing is harder than where to put it once decided.
| Strategy | Example | Trade-off |
|---|---|---|
| Raw storage | Letta recall memory | Exhaustive, expensive at scale |
| LLM extraction | Mem0's fact extractor | Controllable, opinionated |
| Rule-based | Regex, NER | Fast, brittle |
| Agent-driven | MemGPT's memory tools | Flexible, non-deterministic |
| Hybrid | Most production systems | Best of all worlds, more code |
Before writing a line of extraction code, three questions need answers: what counts as an atomic fact versus a compound one? When does extraction happen — per message, per session, asynchronously? Who decides — a rule, the model, or the agent itself? If you can't write a one-sentence success criterion for a memory, don't store it. The extractor's job is not to hoard everything said; it's to capture what will actually influence a future decision.
Read and search: why naive top-k mostly doesn't work
The basic loop — embed the query, find nearest neighbors, take the top-k, inject into context — is what most systems shipped in 2023. In production, it mostly doesn't work, for the same reason lesson 19's associative-retrieval trap doesn't work: topical similarity is not the same thing as relevance.
What actually works as table stakes in 2026: hybrid retrieval (vectors plus BM25, fused), reranking (a cross-encoder reorders the top 50 down to a final 5), metadata filtering (WHERE user_id = ... AND created_at > ...), query rewriting, and budget control (retrieve 50, rerank, keep 5-10). Anthropic's contextual-retrieval work is the 2026 baseline for how much this actually buys: 35% fewer retrieval failures from contextual embeddings alone, 49% with BM25 added, and 67% once reranking is layered on top. Lab 1, below, is where these numbers stop being abstract.
Update: harder than write, because facts change
"The user now lives in Bangalore" should replace "the user lives in Mumbai," not sit next to it. Getting this right — recognizing the same entity across different phrasings, updating one attribute without clobbering unrelated ones, handling concurrent writes — is genuinely one of the hardest operations in the whole list.
| Strategy | System | How it works |
|---|---|---|
| Four-way decision | Mem0 | An LLM picks ADD / UPDATE / DELETE / NOOP per candidate fact against the top-10 similar existing memories |
| Temporal invalidation | Zep / Graphiti | Don't delete — mark the old edge invalid via a timestamp |
| Agent edit | Letta | The agent calls core_memory_replace(block, old, new) directly |
| Append-only | Naive systems | Add the new fact and hope retrieval favors the right one |
Mem0's four-way decision is worth noting specifically because it names the actual decision space: a candidate fact against the ten most similar existing memories is not always a simple overwrite. Sometimes it's genuinely new (ADD), sometimes it revises something old (UPDATE), sometimes it contradicts and should remove the old entry (DELETE), and sometimes it's already known (NOOP). Zep's approach — never delete, just mark invalid with a timestamp — is the bi-temporal strategy from lesson 19's graph memory, applied here as an update mechanism rather than a storage shape.
Delete: the operation everyone ignores until they can't
Deletion is a legal requirement (GDPR's right to be forgotten), an engineering optimization (cost and signal-to-noise), and the easiest way to introduce a silent bug — deleting a memory the agent was quietly relying on.
- Hard delete — gone forever. Simple, irreversible, dangerous.
- Soft delete (tombstone) — moved to a trash directory. Reversible.
- Cascade — if memory A references B and B is deleted, what happens to A? This needs an explicit answer, not an assumption.
- TTL — every memory expires, even if the expiration is a year out. This prevents unbounded accumulation by default rather than by discipline.
Every memory should carry a TTL, even a generous one. Infinite-lifetime memories are how a system accumulates contradictions, stale facts, and GDPR liabilities it never planned for.
Consolidate, reflect, decay: the three operations that turn a store into a memory
Consolidation merges redundant memories into a canonical one, usually on a periodic background pass — Letta's sleep-time compute and Claude Code's Auto Dream are both this pattern. Reflection generates meta-memories from base memories, typically triggered at task completion, tagged kind: reflection, and retrieved on similar future tasks — this is lesson 20's Reflexion pattern, applied as an operation rather than introduced as a type. Decay is time-weighted relevance scoring at retrieval time, tunable per memory type: procedural knowledge should decay slowly, episodic memory should decay fast.
Conflict resolution and provenance: the two nobody loves
Two memories disagree. Now what?
| Strategy | When to use |
|---|---|
| Recency wins | Default — usually right, occasionally wrong |
| Source priority | User statements beat agent inferences |
| Confidence score | Per-memory confidence, higher wins |
| Bi-temporal | Don't resolve — keep both, timestamped (Zep) |
| LLM-adjudicated | Pass both to a model and let it decide |
None of these strategies matter without provenance: every memory should know its source (message ID, tool call, URL), its extraction method (user-stated, LLM-extracted, inferred), and its timestamp. No anonymous memories — that rule has come up in this course before, and it holds here for the same reason: an unattributed memory can't be verified, updated, or trusted when it conflicts with something else.
Lab 1: naive RAG against hybrid retrieval, on the same eight venues
The claim under test: some queries favor semantic similarity, some favor exact match, and naive vector search is bad at the second kind. Three retrievers — pure vector, pure BM25, and a hybrid fused with reciprocal rank fusion (RRF) — run against the same eight-venue Edinburgh corpus and the same three queries.
VENUES = [
{"id": "v1", "name": "The Banshee Labyrinth",
"notes": "Underground pub in Old Town. Capacity up to 250. Limited vegan options."},
{"id": "v3", "name": "The Albanach",
"notes": "Royal Mile pub. Capacity 180. Full vegan menu."},
{"id": "v4", "name": "Hemma",
"notes": "Scandinavian-style bar in Holyrood. Capacity 150. Full vegan menu."},
# ... 5 more venues
]
def rrf_merge(lists_of_results, k_const=60):
"""Reciprocal Rank Fusion: 1/(k+rank) per list, summed across lists."""
scores = {}
for results in lists_of_results:
for rank, (item, _) in enumerate(results):
scores[item["id"]] = scores.get(item["id"], 0) + 1 / (k_const + rank)
id_to_venue = {v["id"]: v for v in VENUES}
return sorted([(id_to_venue[k], v) for k, v in scores.items()], key=lambda x: -x[1])
def hybrid_search(query, k=5):
return rrf_merge([vector_search(query, k=10), bm25_search(query, k=10)])[:k]python lab1_hybrid_retrieval.py
Three queries, run through all three retrievers, tell the whole story:
Q1, semantic — "vegan-friendly venue for 160 people." Vectors correctly rank The Albanach, Hemma, and The Haymarket Vaults at the top. BM25 misses Hemma entirely, because the query says "vegan-friendly" and the venue's notes say "full vegan menu" — different surface words, same meaning. This is exactly where vectors earn their place.
Q2, lexical — "Banshee Labyrinth capacity." This is the query from the top of this lesson: vectors find The Banshee Labyrinth through a loose semantic match, but the score sits close to other "underground pub" venues. BM25 nails it first with a much larger confidence margin, because "Banshee Labyrinth" is an exact term match. Vectors can find it; they don't dominate it.
Q3, mixed — "Old Town venue with vegan menu and capacity over 150." Pure vector search misses "Old Town" as a hard constraint — embeddings don't enforce filters. Pure BM25 catches "Old Town" as a term but misses the semantic implication of "vegan menu." Hybrid wins here specifically because RRF rewards venues that rank well across both lists.
The fusion constant matters: RRF's 1/(k+rank) with k=60 is the canonical value from the original paper (Cormack, Clarke, Buettcher, 2009) — lower k lets top results dominate more, higher k flattens the contribution curve. In production, the top 20 from RRF would typically pass through a cross-encoder reranker next, adding 100-300ms of latency in exchange for roughly another 30% cut in retrieval failures on top of what hybrid alone buys.
What to carry into the next lesson
Nine operations, and most production "memory" in 2024 implemented two of them. The next lesson takes a break from mechanics to ask a different question: given all of this, what should you actually build, or buy? Letta, Mem0, and Zep represent three genuinely different architectural bets, and the next lesson is where those bets get compared as shipped products.
The three production architecture archetypes — hierarchical OS-style, extract-store-retrieve, and temporal knowledge graph — and the benchmark dispute that shows why picking one by leaderboard position is a mistake.
Reply here and it goes straight to Rod. Same as replying to one of his emails.