docs/migration/oss-v2-to-v3.mdx
The new Mem0 release redesigns both extraction and retrieval, and cleans up the SDK surface across Python and TypeScript:
add() / delete_all(), inside filters for search() / get_all()These changes produce a +20 point improvement on LoCoMo (71.4 → 91.6) and +26 point improvement on LongMemEval (67.8 → 93.4), while cutting extraction latency roughly in half.
| Change | Old | New | Migration |
|---|---|---|---|
search() / get_all() entity IDs | Top-level kwargs (user_id="...") | Inside filters dict | m.search("q", filters={"user_id": "..."}) — top-level kwargs now raise ValueError |
top_k default | 100 | 20 | Pass top_k=100 explicitly to restore |
threshold default | None (no filtering) | 0.1 (filters low-relevance) | Pass threshold=0.0 for old behavior |
threshold validation | Any float | Must be in [0, 1] | Out-of-range values now raise ValueError |
rerank default | True | False | Pass rerank=True to restore |
| Entity ID validation | Accepted any string | Trimmed; empty / whitespace-only rejected (ValueError) | Pass a non-empty identifier without internal spaces |
messages in add() | Could be None | Must be str / dict / list[dict] — other types raise Mem0ValidationError (code VALIDATION_003) | Always pass a string, dict, or list of messages |
add() events | Returns ADD, UPDATE, DELETE | Returns ADD only | Update code expecting UPDATE/DELETE |
| Custom extraction prompt | custom_fact_extraction_prompt | custom_instructions | Rename in config |
| Custom update prompt | custom_update_memory_prompt | Deprecated | Use custom_instructions instead |
| Graph memory | enable_graph + graph_store in config | Removed | Graph store support has been removed entirely |
| Qdrant client | >=1.9.1 | >=1.12.0 | Update dependency |
| Upstash client | >=0.1.0 | >=0.6.0 | Update dependency |
| Change | Old | New | Migration |
|---|---|---|---|
| Search parameter | search(query, { limit: 10 }) | search(query, { topK: 10 }) | Rename limit → topK |
topK default | 100 | 20 | Pass topK: 100 explicitly to restore |
search() / getAll() entity IDs | Top-level options (userId: "...") | Inside filters object | m.search("q", { filters: { userId: "..." } }) |
threshold validation | Any number | Must be in [0, 1] | Out-of-range values now throw |
| Entity ID validation | Any string | Trimmed; empty / whitespace-only rejected | Pass non-empty identifiers without internal spaces |
messages in add() | Could be null / undefined | Required — throws on null/undefined | Always pass a string or array |
| Payload key for lemmatized text | text_lemmatized (snake_case) | textLemmatized (camelCase) | TS-only internal field. If you share a vector store collection between Python and TS SDKs, lemma-based BM25 will not resolve across languages — keep collections language-scoped. |
| Custom prompt | customPrompt | customInstructions | Rename in config |
| Graph memory | enableGraph + graphStore in config | Removed | Graph store support has been removed entirely |
| Default graph config | Neo4j default config applied | No default graph config | Graph store config is no longer used |
| Change | Old | New | Migration |
|---|---|---|---|
| Constructor | MemoryClient(api_key, org_id, project_id) | MemoryClient(api_key) | Remove org_id, project_id from constructor |
| Method options | client.add(messages, **kwargs) | client.add(messages, options=AddMemoryOptions(...)) | Use typed option classes (or **kwargs still works) |
| Removed params | api_version, output_format, async_mode, filter_memories, expiration_date, keyword_search, force_add_only, batch_size, immutable, includes, excludes, enable_graph, org_name, project_name | — | Remove from all calls |
| Change | Old | New | Migration |
|---|---|---|---|
| Constructor | new MemoryClient({ apiKey, organizationId, projectId }) | new MemoryClient({ apiKey }) | Remove organizationId, projectId, organizationName, projectName |
| All params | snake_case: user_id, agent_id, top_k | camelCase: userId, agentId, topK | Rename all params to camelCase |
| Removed params | api_version, output_format, async_mode, enable_graph, org_id, project_id, org_name, project_name, filter_memories, batch_size, force_add_only, immutable, expiration_date, includes, excludes, keyword_search | — | Remove from all calls |
| Output format enum | OutputFormat.V1, OutputFormat.V1_1 | Removed | v1.1 is now always used |
| API version enum | API_VERSION.V1, API_VERSION.V2 | Removed | Handled internally |
# For hybrid search + entity extraction (recommended)
pip install --upgrade "mem0ai[nlp]"
python -m spacy download en_core_web_sm
# Qdrant users: also install fastembed to enable BM25 keyword search
pip install fastembed
```
<Info>
**Supported Python versions for `[nlp]` extras: 3.10 – 3.12.** spaCy and its `blis` / `thinc` dependencies do not yet ship prebuilt wheels for Python 3.13, so installs on 3.13 will fail at build time. Use Python 3.12 (or older) for the `[nlp]` extras until upstream support lands. The base `mem0ai` package works on all supported Python versions; only the NLP extras are constrained.
</Info>
pip install fastembed
# After
config = {
"custom_instructions": "Focus on user preferences", # [OK] New name
# custom_update_memory_prompt removed — use custom_instructions
# enable_graph and graph_store removed — graph store support has been removed
}
```
// After
const config = {
customInstructions: "Focus on user preferences", // [OK] New name
// enableGraph and graphStore removed — graph store support has been removed
};
```
# After — entity IDs go inside `filters` (matches Platform API)
results = m.search(
"what meetings did I attend?",
filters={"user_id": "alice"}, # [REMOVED top-level kwarg, use filters]
top_k=20, # New default is 20 (was 100)
threshold=0.1, # New default (pass 0.0 to disable)
rerank=False # New default (pass True to restore)
)
for r in results:
print(r["score"])
```
<Warning>
Passing `user_id`, `agent_id`, or `run_id` as a top-level kwarg to `search()` or `get_all()` now raises `ValueError`. They must be inside the `filters` dict. The change aligns the OSS SDK with the Platform API contract.
</Warning>
// After — entity IDs go inside `filters` (matches Platform API)
const results = await m.search("what meetings did I attend?", {
filters: { userId: "alice" }, // [REMOVED top-level option, use filters]
topK: 20 // [OK] Renamed from 'limit'
});
```
# Before
client = MemoryClient(api_key="...", org_id="org-1", project_id="proj-1")
results = client.search("query", user_id="alice", top_k=20, enable_graph=True)
# After
client = MemoryClient(api_key="...") # org_id, project_id removed
results = client.search(
"query",
options=SearchMemoryOptions(
filters={"user_id": "alice"},
top_k=20
)
)
```
// After
const client = new MemoryClient({ apiKey: "..." });
const results = await client.search("query", {
filters: { userId: "alice" },
topK: 20
});
```
# After — only ADD events
result = m.add("I love hiking and my dog's name is Max", user_id="alice")
for item in result["results"]:
print("New memory:", item["memory"]) # Only ADD events
```
# Before
client.add(messages, user_id="alice", async_mode=True, output_format="v1.1")
# After — async_mode and output_format removed (async by default, v1.1 always)
client.add(
messages,
options=AddMemoryOptions(user_id="alice")
)
# Or using **kwargs
client.add(messages, user_id="alice")
```
// After
await client.add(messages, {
userId: "alice" // [OK] camelCase
});
```
If you're using Qdrant or Upstash, update your client libraries:
# Qdrant users
pip install "qdrant-client>=1.12.0"
# Upstash users
pip install "upstash-vector>=0.6.0"
The new algorithm automatically creates a parallel entity store collection named {your_collection}_entities. No manual setup is required — it's created on first use.
Graph store support has been removed from the open-source SDK. It is replaced by built-in entity linking, which runs natively with no external dependencies.
What was removed:
enable_graph / enableGraph config flaggraph_store / graphStore configuration block (Neo4j, Memgraph, Kuzu, Apache AGE, Neptune)What replaces it:
Entity linking extracts entities (proper nouns, quoted text, compound noun phrases) from every memory during the add pipeline and stores them in a parallel collection ({collection}_entities) inside your existing vector store. At search time, entities from the query are matched against this collection and used to boost relevant memories. The boost is folded into the combined score on each result.
Migration:
enable_graph / enableGraph from your configgraph_store / graphStore block — it is no longer readadd() call.Input conversation
→ Retrieve top-10 related existing memories (for deduplication context)
→ Single LLM call: extract all distinct new facts
→ Batch embed extracted memories
→ Hash-based deduplication (MD5, prevents exact duplicates)
→ Batch insert into vector store
→ Entity extraction + linking
The previous algorithm used two LLM calls — one to extract candidate facts, one to decide ADD/UPDATE/DELETE actions against existing memories. The new algorithm collapses this into a single call that only adds. The model spends its capacity on understanding the input rather than diffing against existing state.
Query
→ Preprocess (lemmatize keywords, extract entities)
→ Parallel scoring:
1. Semantic search (vector similarity)
2. BM25 keyword search (normalized term matching)
3. Entity matching (entity graph boost)
→ Score fusion → Top-K selection
Scoring: The three signals are normalized and fused into a single combined score per result. The fusion adapts based on which signals are available at runtime (semantic-only, semantic + BM25, or all three when spaCy + the entity store are active).
BM25 is a boost signal, not a recall expander. Only semantic search results are candidates — BM25 and entity scores boost ranking but don't add new candidates.
All 15 supported vector stores have been enhanced with two new capabilities:
| Capability | Purpose | Fallback if Unsupported |
|---|---|---|
keyword_search() | BM25/full-text keyword matching | Falls back to semantic-only search |
search_batch() | Batch search for entity matching | Falls back to sequential search |
Qdrant-specific changes:
fastembed library for BM25 encoding (lazy-loaded, gracefully degrades)pip install fastembedAll other vector stores:
keyword_search() methods using their native full-text capabilitiesThe new features degrade gracefully when optional dependencies are missing:
| Missing Dependency | Impact | Search Still Works? |
|---|---|---|
spaCy (mem0ai[nlp]) | No entity extraction, no BM25 lemmatization | Yes (semantic-only) |
fastembed (Qdrant) | No BM25 keyword search | Yes (semantic + entity) |
| Entity store unavailable | No entity boosting | Yes (semantic + BM25) |
You always get semantic search. Hybrid search features layer on top when available.
These parameters have been removed across all SDKs. Remove them from your code:
Constructor: org_id, project_id
All methods: api_version, output_format, async_mode, org_name, project_name, org_id, project_id
add(): enable_graph, immutable, expiration_date, filter_memories, batch_size, force_add_only, includes, excludes, keyword_search
search(): enable_graph
get_all(): enable_graph
project.update(): enable_graph
Constructor: organizationId, projectId, organizationName, projectName
All methods: OutputFormat enum, API_VERSION enum
add(): enable_graph / enableGraph, async_mode / asyncMode, output_format / outputFormat, immutable, expiration_date / expirationDate, filter_memories / filterMemories, batch_size / batchSize, force_add_only / forceAddOnly, includes, excludes, keyword_search / keywordSearch
search(): enable_graph / enableGraph
get_all(): enable_graph / enableGraph
Config: custom_fact_extraction_prompt → renamed to custom_instructions
Config: custom_update_memory_prompt → deprecated, use custom_instructions
Config: enable_graph + graph_store → removed (graph store support removed entirely)
Config: customPrompt → renamed to customInstructions
Config: enableGraph + graphStore → removed (graph store support removed entirely)
search(): limit → renamed to topK
limit is not a valid parameterThe limit parameter has been renamed to topK in the TypeScript OSS:
// Before
const results = await m.search("query", { userId: "alice", limit: 20 });
// After
const results = await m.search("query", { filters: { userId: "alice" }, topK: 20 });
All TypeScript Client SDK parameters now use camelCase. The SDK handles conversion to/from the API automatically:
// Before
await client.search("query", { user_id: "alice", top_k: 20 });
// After
await client.search("query", { filters: { userId: "alice" }, topK: 20 });
ValueError: Top-level entity parameters not supported in search() / get_all()search() and get_all() now require entity IDs inside filters. Top-level kwargs raise ValueError. This aligns the OSS SDK with the Platform API.
# Before
results = m.search("query", user_id="alice", top_k=20)
# After
results = m.search("query", filters={"user_id": "alice"}, top_k=20)
add() and delete_all() continue to accept entity IDs as top-level kwargs.
The default threshold changed from None to 0.1. Low-relevance results that were previously included are now filtered out. To restore the old behavior:
results = m.search("query", filters={"user_id": "alice"}, threshold=0.0)
If you see errors about missing spaCy models, download the required model:
python -m spacy download en_core_web_sm
If spaCy is not installed at all, install the NLP extras:
pip install "mem0ai[nlp]"
The entity store tries to create a {collection_name}_entities collection automatically. If your vector database has restricted permissions, pre-create this collection with the same embedding dimensions as your main collection.
The top-level score still ranges [0, 1], but it is computed differently in v3. Relative ranking between results stays comparable, but absolute numbers shift — retune any hard thresholds in your app against representative queries.
If you need the raw cosine similarity for a specific use case, run an unboosted vector query directly against your vector store via vector_store.search(...).