TAI BUI
Back to curriculum
BuildPython

Hybrid Memory: Vector + Graph + KV (Mem0)

Mem0 (Chhikara et al., 2025) runs memory as three stores in parallel: vector for semantic similarity, KV for fast fact lookup, graph for entity relationships. A scoring layer fuses them on retrieval. This is the 2026 production standard for external memory.

TIME: ~75 minutesPREREQUISITES: Phase 14 · 07 (MemGPT), Phase 14 · 08 (Letta Blocks)

Learning Objectives

  • Explain why a single store (vector only, graph only, KV only) is insufficient for agent memory.
  • Name Mem0's three parallel stores and what each one optimizes for.
  • Describe Mem0's fusion scoring — relevance, importance, recency — and why it is a weighted sum, not a hierarchy.
  • Implement a toy three-store memory in stdlib with an `add()` that writes to all three and a `search()` that fuses results.

The Problem

Agents ask three different kinds of questions, and each one is answered well by exactly one kind of store:

  • Semantic similarity: "what did we discuss about agent drift last week?" Vector wins. KV and graph miss.
  • Fact lookup: "what is the user's phone number?" KV wins. Vector is wasteful and graph is overkill.
  • Relationship reasoning: "which customers share the same billing entity?" Graph wins. Vector and KV can't answer it at all.

A production agent issues all three in a single session, so a single-store memory is wrong for two of them, every time. Mem0's contribution is putting all three stores behind one add/search surface, with a scoring function that fuses what comes back.

Warm-up0/3
1. In a MemGPT-style agent, why is memory kept outside the context window?Warm-up
2. Which structure gives the fastest exact lookup when you already know the key?Warm-up
3. In a graph database, what does an edge between two nodes represent?Warm-up
Check: The Problem0/3
1. Which query class does a KV store handle best?Define
2. A team uses only a vector store for agent memory. Why does it fail on 'which customers share the same billing entity?'Explain
3. A support agent must (a) fetch a customer's phone number, (b) find customers sharing a billing entity, and (c) recall earlier chats about a similar complaint. Which store mapping fits?Design

The Concept

Three stores in parallel

Hybrid memory is a memory layer that writes every fact to three stores at once (vector, KV, graph) and merges their results when you search. Mem0 (arXiv:2504.19413, April 2025) is the reference implementation. On add(text, user_id, metadata) it does this:

  1. Extract candidate facts from the text (an LLM-driven step).
  2. Write each fact to the vector store as an embedding, for semantic search.
  3. Write each fact to the KV store, keyed on (user_id, fact_type, entity), for O(1) lookup.
  4. Write each fact to the graph store (called Mem0g) as typed edges, for relationship queries.

On search(query, user_id), each store answers in its own way:

  1. The vector store returns the top-k by embedding cosine.
  2. The KV store returns direct hits keyed on the (user_id, type, entity) derived from the query.
  3. The graph store returns the subgraph reachable from the entities in the query.
  4. A scoring layer fuses the three result sets.

Fusion scoring

score = w_relevance * relevance(q, record)
      + w_importance * importance(record)
      + w_recency * recency(record)
  • Relevance is vector cosine, KV exact match, or graph path weight, depending on which store the record came from.
  • Importance is tagged at write time or learned. Some facts matter more than others: names, IDs, policies.
  • Recency is exponential decay over time since last write or read.

The weights are a per-product decision. Chat agents want a higher w_recency, compliance agents a higher w_importance, retrieval agents a higher w_relevance.

Mem0g and temporal reasoning

The graph store, Mem0g, includes a conflict detector. When a new fact contradicts an existing edge, the old edge is marked invalid but never deleted. A temporal query ("what was the user's city in March?") then traverses the subgraph that was valid at that time.

This is the compliance-grade behavior that Letta's invalidation pattern generalizes.

Benchmark numbers

The Mem0 paper reports (2025):

  • LoCoMo (long-form conversation memory): 91.6
  • LongMemEval (long-horizon episodic memory): 93.4
  • BEAM 1M (1M-token memory benchmark): 64.1

The baselines (full-context 128k LLM, flat vector store, flat KV) all lose by 10+ points. Benchmarks alone shouldn't pick your architecture, since operational shape matters more. But the gap tells you the fusion design is doing real work.

Scope taxonomy

Mem0 splits memory by scope:

  • User memory persists across sessions, keyed on user_id.
  • Session memory persists within one thread.
  • Agent memory is per-agent instance state.

Every write picks exactly one scope, and retrieval can query across scopes with per-scope weights. Mix scopes carelessly and you get the "assistant told Alice about Bob's project" incident.

Where this pattern goes wrong

  • Embedding drift. Vector results that look right on the first hundred queries degrade as the corpus grows. Re-embed the top-N most-used records on a schedule.
  • KV schema creep. (user_id, type, entity) looks simple until every team invents its own type. Audit the type set quarterly.
  • Graph explosion. One noisy extractor can add 50 edges per message. Cap graph writes per add call and drop low-confidence edges.
Check: The Concept0/17
1. What are the three stores that hybrid memory in Mem0 writes to in parallel on each add()?Define
2. What is fact extraction in Mem0's add() path?Define
3. What three dimensions feed Mem0's fusion score?Define
4. What is the scope taxonomy Mem0 uses to decide who sees a memory?Define
5. What is Mem0g?Define
6. When an incoming fact contradicts an existing edge in the graph store, what does temporal invalidation do?Define
7. What is embedding drift, and how does the lesson recommend mitigating it?Define
8. What is KV schema creep?Define
9. What is graph explosion, and what is the fix?Define
10. Why does Mem0 combine relevance, importance and recency as a weighted sum instead of a strict priority order?Explain
11. Why does Mem0g keep an invalidated edge instead of deleting it?Explain
12. What goes wrong if writes do not pick one scope and retrieval mixes scopes carelessly?Explain
13. Mem0's baselines (a 128k full-context LLM, a flat vector store, a flat KV store) all trail it by 10+ points on the reported benchmarks. What does the lesson conclude?Explain
14. A user states a preference in Monday's chat thread, but in a new thread on Tuesday the agent has no memory of it. Which write-time mistake best explains this?Analyze
15. Lookups on (user_id, type, entity) keep missing facts that exist, because the same kind of fact is stored as 'city' by one team and 'location' by another. Which failure is this, and what is the fix?Analyze
16. A team keeps tuning the three fusion weights, but results stay noisy because the extractor emits many low-quality facts. What does the author recommend?Analyze
17. A compliance agent must answer 'what address did this customer have on 1 March?' after they moved. Which design fits?Design

Build It

code/main.py implements the three-store pattern in stdlib:

  • VectorStore uses naive token-overlap similarity as an embedding stand-in.
  • KVStore is a dict keyed on (user_id, fact_type, entity).
  • GraphStore holds typed edges (subject, relation, object, valid).
  • Mem0 is the top-level facade: add(), search(), fusion scoring, and scope-aware retrieval.
  • A worked trace runs a multi-user, multi-session conversation through all of it.

Run it:

python3 code/main.py

The output shows the three recall paths separately, then the fused top-k. Change the scoring weights at the top of main() and watch the ranking move.

Check: Build It0/7
1. After the five add() calls in the worked trace, which 'lives_in' edges for ava are still valid?Apply
2. After the worked trace, how many records does mem.kv.by_user('ava') return?Apply
3. In the worked trace, what does mem.search('refund invoice', user_id='ava', top_k=5) return?Apply
4. With the default weights 0.6, 0.2 and 0.2, what is the fused score of a vector hit with relevance 0.5, importance 0.9 and recency 1.0?Apply
5. A record is found only through the KV path, with importance 0.8 and recency 0.5. With default weights, what is its fused score?Apply
6. With a 86400 second half-life, what is the recency score of a record written 48 hours ago?Apply
7. ava moved from Berlin to Lisbon, and both facts were written with a 'city' KV triple. KV recall for ava now returns both cities, while the graph shows only Lisbon as valid. Why?Analyze

Use It

  • Mem0 (Apache 2.0) is production-ready. Self-host it with Postgres, Qdrant, and Neo4j, or use the managed cloud.
  • Letta uses a three-tier core/recall/archival design. You bring your own vector and graph backends.
  • Zep is the commercial alternative, with a temporal knowledge graph and fact extraction.
  • Custom builds make sense when you need exact control over the extractor (compliance) or the fusion weights (voice agents, where recency dominates).
Check: Use It0/2
1. How does Zep differ from Mem0 and Letta?Define
2. A voice agent must keep ranking dominated by what was said most recently, and you want exact control over the weights. Which approach fits?Design

Ship It

outputs/skill-hybrid-memory.md generates a three-store memory scaffold with a fusion scorer, scope taxonomy, and temporal invalidation already wired in.

Author's Note

Retrieval gets all the attention, but I think the write path is the part of this design worth getting right first. KV schema creep and graph explosion both start at add() time, and so do scope leaks. Each one only shows up later as a bad search result, which makes it easy to blame the read side. If your extractor is noisy, no amount of weight tuning on retrieval will rescue you. My recommendation: get the write path disciplined before anything else. One scope per write, a capped number of edges, a short and audited list of KV types. Then tune the fusion weights, which is the fun part and matters least.

Exercises

  1. Replace the toy vector similarity with a real embedding model (sentence-transformers, Ollama, OpenAI embeddings). Measure recall@10 on a synthetic long conversation. Does the ranking drift over 1000 writes?
  2. Add a temporal query: search(query, as_of=timestamp). Return only records valid at or before that time. Which store needs the most work?
  3. Implement a conflict detector: if an incoming fact contradicts a graph edge, invalidate the old edge and log both. Test it on "user lives in Berlin" -> "user lives in Lisbon."
  4. Extend the fusion scorer with a user_feedback dimension (thumbs-up on retrieved records). How do you prevent gaming, where the agent only returns records it already liked?
  5. Read the Mem0 docs (docs.mem0.ai) and port the toy to mem0 client calls. Compare retrieval quality on the same 20 test queries.

Key Terms

TermWhat people sayWhat it actually means
Hybrid memory"Vector plus graph plus KV"Three stores written in parallel, fused on retrieval
Fact extraction"Memory ingestion"LLM step that breaks text into (entity, relation, fact) tuples
Fusion scoring"Relevance ranking"Weighted sum of relevance, importance, recency
Scope"Memory namespace"user / session / agent — determines who sees what
Mem0g"Memory graph"Mem0's graph store: typed edges with temporal validity, and a conflict detector that invalidates contradicted edges
Temporal invalidation"Soft delete"Mark contradicted edges invalid; never delete
Embedding drift"Retrieval rot"Vector quality degrades as corpus grows; re-embed periodically

Further Reading

Code

Ships With