This project note is part of my attempt to understand open-source AI systems by building with them, reading their migrations, and writing down where the abstraction holds—and where it leaks. Project learning list
The problem is not remembering more
An LLM can carry a conversation while the relevant messages still fit inside its context window. That is short-term continuity, not memory in the human sense and not durable application state. When the session ends, the model does not naturally retain that a user prefers terse answers, changed jobs last month, or abandoned an earlier plan.
The naive answer is to send the entire transcript again. It works for a demo and decays quickly in a product: prompts grow, latency rises, old details compete with the current question, and sensitive history travels farther than necessary.
Mem0 occupies the space between raw chat history and an application database. It tries to extract a small set of reusable facts from interactions, store them under an identity boundary, and retrieve only the memories relevant to the next request.
That distinction matters. The purpose of a memory layer is not to preserve everything. It is to forget most things deliberately and recall a few things reliably.
This article reflects the Mem0 OSS v3 and Platform documentation available in July 2026. Older examples built around enable_graph, graph_store, or Neo4j describe a previous architecture and should not be copied into a new OSS deployment.
Mem0 OSS v3: the current mental model
The shortest useful model of OSS v3 has four parts:
- An extraction model turns a message or conversation into durable memory candidates.
- A vector store holds memory text, embeddings, metadata, and an additional entity collection.
- A history store records memory operations for audit and inspection.
- A retrieval pipeline ranks semantic candidates, then boosts them with keyword and entity signals when available.
The defaults are convenient for local exploration, not a production architecture recommendation. The Python library can use local Qdrant for vectors and SQLite for history; the self-hosted server stack has different defaults. Both components are configurable. The important point is that these are a vector store and an audit/history store—not a mandatory vector-plus-graph-database pair.
Write path: single-pass ADD-only
OSS v3 removed the older second LLM pass that decided whether a candidate should be added, updated, or deleted. The current write path is approximately:
messages
→ retrieve related existing memories for context
→ one LLM call extracts distinct durable facts
→ batch embedding
→ exact hash deduplication
→ insert memories into the vector store
→ extract and link entities
→ record operations in the history store
The result of automatic extraction is now ADD-only. A new statement does not silently rewrite or delete an older memory. That reduces extraction work and preserves evidence, but it shifts responsibility to retrieval and lifecycle policy. If a user first says “I live in Shenzhen” and later says “I moved to Shanghai,” both memories may exist. Temporal wording, metadata, explicit correction flows, or application-side consolidation must decide what is current.
ADD-only does not mean the SDK has no administrative update or delete operations. It means the extraction algorithm no longer emits UPDATE and DELETE decisions while processing a new conversation. Keep that boundary clear in code reviews.
Read path: semantic recall with two boosts
Search begins with semantic vector retrieval. The returned candidates can then receive two additional signals:
- BM25 keyword boost rewards lexical overlap after preprocessing. With Qdrant, the OSS migration guide notes that
fastembedis needed for sparse keyword search; without it, Mem0 logs a warning and falls back without BM25. - Entity boost extracts entities from the query, matches them against the parallel entity collection, and raises the rank of memories linked to those entities.
These signals are fused into the public score. BM25 and entity matching are boosts, not independent recall channels: they reorder semantic candidates rather than adding arbitrary keyword-only results. If the entity collection or keyword support is unavailable, semantic search still works.
Entity linking also creates linked_memory_ids, which are useful for understanding which memories share an entity. They should not be mistaken for a queryable knowledge graph. The old relations response is no longer populated, and there is no OSS traversal API hiding behind the ranking score.
The graph boundary that old tutorials blur
Mem0’s graph story changed enough to deserve an explicit warning.
In current OSS v3, these pieces are gone:
enable_graph/enableGraphgraph_store/graphStore- direct Mem0 integrations with Neo4j, Memgraph, Kuzu, Apache AGE, or Neptune
- a separate graph retrieval path and exposed relationship results
Entity linking replaces that machinery inside OSS. It extracts names and noun phrases, stores them in a companion collection in the existing vector store, and uses entity matches to influence ranking. There is no second database to provision solely for Mem0, and old graph configuration is ignored or rejected.
Mem0 Platform is a different product boundary. Managed projects can expose native relationship or Graph Memory capabilities without asking the customer to provide an external graph store. Platform documentation and feature flags have evolved alongside the v3 rollout, so I would verify the capability on the intended plan rather than design against an old dashboard screenshot. Either way, a Platform feature is not evidence that graph_store still belongs in OSS configuration.
If the product genuinely needs graph traversal—“find every supplier two hops from this incident,” for example—use a graph database as an application dependency with an explicit domain schema. A hidden retrieval boost is not a substitute for a source-of-truth graph.
A minimal, correctly scoped OSS loop
Install and configure providers according to the official quickstart. The essential Python flow is small:
from mem0 import Memory
memory = Memory()
memory.add(
[
{
"role": "user",
"content": "I prefer release notes that begin with risks, not features.",
},
{
"role": "assistant",
"content": "I will put operational risks first.",
},
],
user_id="user_42",
)
results = memory.search(
"How should I structure this release note?",
filters={"user_id": "user_42"},
top_k=5,
)
for item in results["results"]:
print(item["memory"], item["score"])
The asymmetry is intentional in v3: add() accepts the entity ID at the top level, while search() and get_all() require entity IDs inside filters. Passing user_id as a top-level search argument now raises an error.
Do not treat the filter as optional decoration. In a multi-user service, filters={"user_id": ...} is the primary tenant boundary for recall. Derive it from authenticated server-side identity, never from an untrusted model response. Add agent_id, run_id, or metadata filters when memory ownership needs a narrower scope.
The same caution applies to result injection. A search hit is untrusted context, not a command. Place memories in a clearly delimited prompt section, cap their number and total tokens, and do not allow stored text to override system policy.
Which persistence tool should own what?
Memory infrastructure becomes simpler when each store has one job.
| Need | Best starting point | Why | What it should not own |
|---|---|---|---|
| Resume an interrupted LangGraph run | LangGraph checkpointer | Persists graph state, messages, interrupts, and execution position by thread | Cross-session semantic personalization |
| Remember preferences and recurring facts | Mem0 OSS or Platform | Extracts sparse memories and retrieves them by meaning across sessions | Orders, permissions, balances, or workflow truth |
| Store authoritative product data | PostgreSQL or another ordinary database | Transactions, schemas, constraints, migrations, and deterministic queries | Probabilistic memory extraction |
| Use managed memory at production scale | Mem0 Platform | Hosted operations, richer filters and managed capabilities | Avoiding vendor, residency, or cost review |
| Control providers and data plane | Mem0 OSS | Self-hosting and configurable LLM, embedder, vector, and history stores | Zero-maintenance operations |
A durable agent often uses three layers at once: a checkpointer for the current run, Mem0 for selective cross-session recall, and a normal database for facts the business cannot afford to misremember. Duplication is acceptable when the ownership rule is explicit. Confusion begins when a preference store becomes the customer ledger.
Privacy and deletion are part of the architecture
A useful memory is usually personal, which means memory quality and privacy risk rise together.
Start with a written retention policy. Decide which categories may be extracted, which are forbidden, and how long each can live. Health details, credentials, precise location, and inferred personal traits should not enter memory merely because an LLM considers them “useful.”
Then make the lifecycle observable:
- Record the authenticated actor, source message, extraction version, memory ID, and timestamp for each write.
- Offer a user-visible page to inspect, correct, and delete memories.
- Delete by the same identity scope used for search, and verify removal from the vector store, entity collection, history policy, backups, and analytics copies.
- Keep administrative audit data only as long as its legal and operational purpose requires.
- Test tenant isolation with adversarial identifiers and cross-user queries.
The history store is helpful for debugging, but an audit trail can itself retain deleted personal text. “Delete from retrieval” and “erase personal data” are different operations. Product, legal, and infrastructure owners need one agreed answer before launch.
For sensitive deployments, self-hosting may reduce data movement, but it does not remove the LLM call from the threat model. Verify where extraction and embedding requests go, what providers log, how keys are rotated, and whether local models meet the quality threshold.
Latency and cost: measure the full turn
Mem0 adds work on both sides of a conversation.
On write, extraction requires an LLM call, embeddings, vector insertion, and entity processing. On read, the system embeds or preprocesses the query, searches, may apply keyword and entity boosts, and can optionally rerank. A user experiences the sum, not the impressive number from one component.
Measure at least:
- p50, p95, and p99 add latency
- p50 and p95 search latency by
top_k - extraction-model tokens and cost per conversation
- embedding and reranker cost
- memories written per active user per month
- vector and history storage growth
- extra prompt tokens caused by retrieved memories
Writes need not always block the response. An asynchronous pipeline can improve conversational latency, but it introduces consistency delay: the next turn may arrive before the new memory is searchable. Decide whether each use case prefers immediacy or throughput, and expose the delay in tests.
Reranking is another deliberate trade-off. OSS v3 defaults changed, and enabling a reranker can improve precision while adding network time and cost. Tune it against your evaluation set rather than enabling it because “more ranking” sounds safer.
How I would evaluate Mem0 locally
Mem0 reports substantial gains for the v3 algorithm on LoCoMo and LongMemEval, along with lower extraction latency. Those are project-published benchmark results. They are useful evidence of direction, not a purchasing decision, because your users, languages, entity names, and cost constraints will differ.
Build a small evaluation corpus from representative, consented, or synthetic conversations. For each case, label:
- facts that should become memories
- details that must not be stored
- corrections and time-dependent facts
- queries that should retrieve each memory
- confusing near-neighbor memories that should stay below it
Then score three stages separately.
Extraction
Measure precision, recall, exact duplicates, sensitive-memory rate, and contradictions introduced per 100 conversations. Inspect whether ADD-only behavior leaves stale statements that the product presents without temporal context.
Retrieval
Track recall@k, precision@k, mean reciprocal rank, and “harmful recall”: a plausible but wrong memory injected into a response. Run ablations with semantic-only, BM25 enabled, entity support enabled, and reranking enabled. This reveals whether a dependency improves your data or only the public benchmark.
Entity tests deserve their own slice. Use aliases, misspellings, people with the same name, multilingual names, and compound product names. Confirm that linked_memory_ids join the right records and that an entity boost does not overwhelm stronger semantic evidence.
Product outcome
Finally, compare task success, user correction rate, time to answer, total turn cost, and p95 latency against a baseline with no long-term memory. A retrieval score can improve while the product gets worse because stale personalization feels more intrusive than a generic response.
Version the corpus and run it on every change to the extraction model, embedding model, custom instructions, vector backend, or threshold. Memory quality is behavior, and behavior needs regression tests.
Limits I would design around
Mem0 is useful precisely because it hides machinery. That convenience creates several sharp edges:
- ADD-only accumulation: contradictions can coexist, so current truth needs timestamps or explicit lifecycle logic.
- Probabilistic extraction: the LLM can omit a fact, invent a cleaner formulation, or retain something inappropriate.
- Semantic candidate ceiling: BM25 and entity signals boost vector candidates; they do not guarantee lexical recall outside that pool.
- Provider drift: changing an LLM or embedder can alter extraction and ranking even when application code is unchanged.
- Identity mistakes: one incorrect filter can create a serious privacy incident.
- No OSS graph traversal: entity links improve recall but cannot answer general relationship queries.
- Operational surface: self-hosting still means monitoring vector persistence, history storage, model providers, migrations, backups, and deletion.
The response is not to avoid memory. It is to give memory less authority. Retrieve a few candidates, preserve provenance, allow correction, and keep consequential facts in deterministic systems.
My current verdict
Mem0 OSS v3 is more coherent than the older “LLM plus vectors plus Neo4j” description. Single-pass ADD-only extraction makes the write path easier to reason about. Hybrid ranking acknowledges that semantic similarity alone is not enough. Built-in entity linking removes a graph dependency while preserving some entity-aware retrieval value.
The trade-off is also clearer: Mem0 is a relevance system, not a truth system.
I would adopt it when cross-session personalization is visible enough to justify dedicated evaluation and governance. I would not add it merely because an agent “should have memory.” A checkpointer may solve the actual problem; a database may already contain the facts; or a well-designed prompt may need only the current task.
Good memory is not maximal retention. It is selective continuity under the user’s control.





Responses