docs(skills): refresh mem0 skills against SDK 2.2.1 / TS 3.3.1 (#7548)

This commit is contained in:
Kartik
2026-10-07 18:36:15 +05:30
committed by GitHub
parent d62ce07fac
commit b7ad69afda
33 changed files with 1239 additions and 814 deletions
@@ -1,16 +1,17 @@
# OSS → Platform API mapping
Exact translation of the mem0 OSS (self-hosted `Memory`) API to the hosted `MemoryClient` API.
**Always confirm against the installed package** (see SKILL.md Phase 2) — versions drift. The facts
below match `mem0ai` 2.0.x (the v3 platform API) and the official guide:
https://docs.mem0.ai/migration/oss-v2-to-v3
**Always confirm against the installed package** (see SKILL.md Phase 2), since versions drift. The
facts below match Python `mem0ai` 2.2.x and TypeScript `mem0ai` 3.3.x (the `/v3/memories/*` platform
API). Moving stored data: https://docs.mem0.ai/migration/oss-to-platform. OSS upgrade changes
(Python 1.x to 2.x, TS 2.x to 3.x): https://docs.mem0.ai/migration/oss-v2-to-v3
## Contents
- [Python](#python)
- [TypeScript / JavaScript](#typescript--javascript)
- [Return shapes](#return-shapes)
- [Dependencies & environment](#dependencies--environment)
- [v2→v3 default/behavior changes](#v2v3-defaultbehavior-changes)
- [OSS vs Platform defaults and behavior](#oss-vs-platform-defaults-and-behavior)
---
@@ -35,28 +36,38 @@ memory = MemoryClient() # reads MEM0_API_KEY from the env
```
Notes:
- The client reads `MEM0_API_KEY` from the environment when `api_key` is omitted.
- **Drop** `vector_store`, `llm`, `embedder`, `graph_store`, `history_db_path` — these are managed
server-side now.
- **Drop** `org_id` / `project_id` constructor args if present — they're resolved from the API key
in v3.
- **Drop** `vector_store`, `llm`, `embedder`, `reranker`, `history_db_path`, `version` (and
`graph_store` if a pre-2.0 config still has it): these are managed server-side now.
- `custom_instructions` does have a hosted equivalent: `client.project.update(custom_instructions=...)`
(project-wide) or `custom_instructions=` on `add()`.
- **Drop** `org_id` / `project_id` constructor args if present, they're resolved from the API key.
- For async codebases, use `AsyncMemoryClient` (same methods, `await`-ed).
### Method calls
| Operation | OSS `Memory` | Hosted `MemoryClient` |
|---|---|---|
| add | `memory.add(messages, user_id="u")` | `memory.add(messages, user_id="u")` — unchanged (top-level entity IDs accepted) |
| search | `memory.search(q, user_id="u", limit=N)` *(older)* or `…, filters={"user_id":"u"}, top_k=N` *(newer)* | `memory.search(q, filters={"user_id": "u"}, top_k=N)` — entity IDs **must** be inside `filters`; top-level `user_id`/`agent_id`/`app_id`/`run_id` raise `ValueError` |
| get_all | `memory.get_all(user_id="u")` or `…, filters={"user_id":"u"}` | `memory.get_all(filters={"user_id": "u"}, page=1, page_size=N)` — entity IDs in `filters`; paginated with `page`/`page_size` (**not** `top_k`) |
| delete_all | `memory.delete_all(user_id="u")` | `memory.delete_all(user_id="u")` — unchanged |
| add | `memory.add(messages, user_id="u")` | `memory.add(messages, user_id="u")`: same call (top-level entity IDs accepted, plus `app_id`), but the return value differs (see Return shapes) |
| search | `memory.search(q, filters={"user_id": "u"}, top_k=N)` (pre-2.0 code used top-level `user_id=` and `limit=`) | `memory.search(q, filters={"user_id": "u"}, top_k=N)`: entity IDs **must** be inside `filters`; top-level `user_id`/`agent_id`/`app_id`/`run_id` raise `ValueError` (on OSS 2.x too) |
| get_all | `memory.get_all(filters={"user_id": "u"}, top_k=N)` (not paginated) | `memory.get_all(filters={"user_id": "u"}, page=1, page_size=N)`: entity IDs in `filters`; paginated with `page`/`page_size` (**not** `top_k`) |
| delete_all | `memory.delete_all(user_id="u")` | `memory.delete_all(user_id="u")`: same call (entity IDs are query params, at least one is required, `"*"` is a wildcard). `delete_all(filters=...)` is **not** a Platform form |
| get | `memory.get(memory_id)` | `memory.get(memory_id)` |
| update | `memory.update(memory_id, text=...)` *(`data=` is a deprecated alias)* | `memory.update(memory_id, text=...)` — unchanged |
| update | `memory.update(memory_id, text=...)` *(`data=` is a deprecated alias)* | `memory.update(memory_id, text=..., metadata=...)`: use keyword `text=`. A positional string (`update(id, "new text")`) breaks because the second positional is `options`, and there is no `data=` alias |
| delete | `memory.delete(memory_id)` | `memory.delete(memory_id)` |
| reset | `memory.reset()` (wipes the local store) | **No global reset.** Use `memory.delete_all(filters=...)` scoped to the relevant entity. Flag this. |
| history | `memory.history(memory_id)` | `memory.history(memory_id)`: same call, extra fields on each entry (`input`, `user_id`, `categories`, `metadata`); OSS-only `is_deleted`/`actor_id`/`role` are absent (see Return shapes) |
| reset | `memory.reset()` (wipes the local store) | `memory.reset()` exists but calls `delete_users()`, which deletes **all** users, agents, sessions and memories (first page of entities only, see gotchas). Prefer `delete_all` scoped to an entity. Flag this |
Key rule: for **search** and **get_all**, the hosted client requires entity IDs (`user_id`,
`agent_id`, `app_id`, `run_id`) inside a `filters` dict and will raise if you pass them top-level.
For **add** and **delete_all**, top-level entity IDs are accepted.
Filter differences: Platform validates each top-level filter key against a fixed allowlist
(`AND`/`OR`/`NOT`, `user_id`, `agent_id`, `app_id`, `run_id`, `created_at`, `updated_at`, `timestamp`,
`expiration_date`, `categories`, `metadata`, `memory_ids`, `keywords`) and
returns 400 for anything else, so custom metadata keys must be nested under `"metadata"`
(`{"metadata": {"plan": "pro"}}`). Platform `metadata` supports only `eq`/`ne`/`contains` (`contains` is case-sensitive and matches the whole value or one list member, not a substring), and there is
no `nin` (use `{"NOT": [{"categories": {"in": [...]}}]}`; `NOT` must be a list). OSS accepts arbitrary
metadata keys and a wider operator set. Flag any OSS filter that depends on either. Do not filter on `text`: search fails with a 503 and `get_all` rejects it. Pass the text as the search `query`.
---
## TypeScript / JavaScript
@@ -66,36 +77,64 @@ Confirm option names against `node_modules/mem0ai/` types.
### Import & client construction
```typescript
// OSS (self-hosted) — note the "/oss" subpath
// OSS (self-hosted): note the "/oss" subpath
import { Memory } from "mem0ai/oss";
const memory = new Memory({ /* vectorStore, embedder, llm, historyStore … */ });
const memory = new Memory({ /* vectorStore, embedder, llm, historyStore, reranker, customInstructions … */ });
// Platform (hosted) — default export from the package root
// Platform (hosted): default export from the package root
import MemoryClient from "mem0ai";
const memory = new MemoryClient({ apiKey: process.env.MEM0_API_KEY });
// Drop organizationId / projectId — resolved from the API key in v3.
const memory = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });
```
Notes:
- Unlike Python, the TS client does **not** read `MEM0_API_KEY` itself: `apiKey` is required and the
constructor throws if it is empty. Pass it explicitly.
- Drop `organizationId` / `projectId` if present, they're resolved from the API key.
- Drop `vectorStore`, `embedder`, `llm`, `historyStore`, `historyDbPath`, `reranker`, `disableHistory`,
`version`. `customInstructions` maps to `client.updateProject({ customInstructions })` or
`customInstructions` on `add()`.
### Method calls (option-object differences)
| Operation | OSS / old client | Hosted client (v3) |
| Operation | OSS `Memory` | Hosted client |
|---|---|---|
| add | `memory.add(messages, { userId: "u" })` | `memory.add(messages, { userId: "u" })` — unchanged |
| search | `memory.search(q, { userId: "u", limit: 20 })` | `memory.search(q, { filters: { userId: "u" }, topK: 20 })` — entity IDs into `filters`; `limit` → `topK` |
| getAll | `memory.getAll({ userId: "u" })` | `memory.getAll({ filters: { userId: "u" } })` — entity IDs into `filters` |
| deleteAll | `memory.deleteAll({ userId: "u" })` | `memory.deleteAll({ userId: "u" })` |
| get / update / delete | `memory.get(id)` etc. | same, by memory id |
| add | `memory.add(messages, { userId: "u" })` (`messages` may be a string or `Message[]`) | `memory.add(messages, { userId: "u" })`: `messages` must be `Message[]` (wrap a bare string as `[{ role: "user", content: str }]`); the response is an async event (see Return shapes) |
| search | `memory.search(q, { filters: { user_id: "u" }, topK: 20 })` (pre-3.0 code used top-level `userId` and `limit`) | `memory.search(q, { filters: { user_id: "u" }, topK: 20 })`: entity IDs go inside `filters` with **snake_case** keys (`user_id`, not `userId`); top-level `userId` throws; `limit` → `topK` |
| getAll | `memory.getAll({ filters: { user_id: "u" }, topK: 20 })` (not paginated) | `memory.getAll({ filters: { user_id: "u" }, page: 1, pageSize: 50 })`: paginated with `page`/`pageSize` (not `topK`) |
| deleteAll | `memory.deleteAll({ userId: "u" })` | `memory.deleteAll({ userId: "u" })`: same call (at least one entity ID is required) |
| update | `memory.update(id, "new text")` or `memory.update(id, { text, metadata })` | `memory.update(id, { text: "new text" })`: the options object is required, a bare string throws |
| get / delete / history | `memory.get(id)` etc. | same, by memory id (`history` field names differ, see Return shapes) |
| reset | `memory.reset()` (wipes the local store) | No `reset()` on the TS client. `deleteUsers()` with no arguments deletes all users, agents, sessions and memories (first page of entities only, see gotchas). Prefer `deleteAll` scoped to an entity. Flag this |
Also drop legacy options that no longer apply on v3: `async_mode`, `output_format`, `enable_graph`.
Also drop legacy options that no longer apply: `async_mode`, `output_format`, `enable_graph`.
---
## Return shapes
- `search(...)` and `get_all(...)` return `{"results": [...]}`; each item has at least a `memory`
(text) field, plus `id` and (for search) `score`. Code that reads `result["results"]` and pulls
`item["memory"]` keeps working.
- `get_all(...)` on the hosted client is paginated: `{"count", "next", "previous", "results": [...]}`.
- `add(...)` returns the created memories. On v3 it returns **only ADD events** — if the old code
branched on `event == "UPDATE"` / `"DELETE"` from `add()` results, that branch is now dead.
- `search(...)` returns `{"results": [...]}` on both sides; each item has at least a `memory` (text)
field, plus `id` and `score`. Code that reads `result["results"]` and pulls `item["memory"]` keeps
working.
- `get_all(...)`: OSS returns `{"results": [...]}` (no pagination). The hosted client is paginated:
`{"count", "next", "previous", "results": [...]}`.
- `add(...)`: OSS extracts synchronously on both runtimes, but the event sits in a different place:
Python OSS returns `{"results": [{"id", "memory", "event": "ADD"}]}`, TS OSS returns
`{ results: [{ id, memory, metadata: { event: "ADD" } }] }` (no top-level `event`).
The hosted `add` is **asynchronous by default** and returns `{"status": "PENDING", "event_id": "..."}`
from Python. The TS hosted client camelCases response keys, so it returns
`{ status: "PENDING", eventId: "..." }` (its typed return `Array<Memory>` does not reflect this).
New memories may not be searchable yet. Only `infer=False` is synchronous and returns `results`.
Neither SDK has an event-poll method: poll `GET /v1/event/{event_id}/` over REST if you must wait.
Code that reads `id`/`memory` from `add()` results must change, and any 1.x-era branch on
`event == "UPDATE"` / `"DELETE"` is dead on both sides (add is ADD-only since 2.0).
- `history(...)` field names differ on all four runtimes:
| Runtime | Entry fields |
|---|---|
| Python OSS | `id`, `memory_id`, `old_memory`, `new_memory`, `event`, `created_at`, `updated_at`, `is_deleted`, `actor_id`, `role` (oldest first) |
| TS OSS | `id`, `memory_id`, `previous_value`, `new_value`, `action`, `created_at`, `updated_at`, `is_deleted` (raw rows, snake_case, newest first) |
| Hosted Python | `id`, `memory_id`, `input`, `old_memory`, `new_memory`, `event`, `user_id`, `categories`, `metadata`, `created_at`, `updated_at` |
| Hosted TS | `id`, `memoryId`, `input`, `oldMemory`, `newMemory`, `event`, `userId`, `categories`, `metadata`, `createdAt`, `updatedAt` |
Code that reads `previous_value` / `new_value` / `action` from TS OSS history must switch to
`oldMemory` / `newMemory` / `event` on the hosted TS client.
---
@@ -109,9 +148,18 @@ Also drop legacy options that no longer apply on v3: `async_mode`, `output_forma
- Local-infra services (e.g. a Qdrant docker-compose service) that existed only for mem0 can be
retired — flag this rather than deleting infrastructure unilaterally.
## v2→v3 default/behavior changes
From the official migration guide — surface any that affect the project:
- Python `top_k` default changed 100 → 20; TS `limit` renamed to `topK`.
- New `threshold` default `0.1` (was none); new `rerank` default `false` (was true).
- `custom_fact_extraction_prompt` → `custom_instructions`; `custom_update_memory_prompt` deprecated.
- Graph memory (`enable_graph`, `graph_store`) removed from the OSS v3 surface — see gotchas.
## OSS vs Platform defaults and behavior
Surface any that affect the project. Defaults differ between the two sides even when a call looks
identical:
- `search`: OSS `top_k=20`, `threshold=0.1`, `rerank=False`. Platform `top_k=10` (allowed 1 to 1000),
`rerank=false`, and `threshold` is a server-side cutoff, not a floor on the returned `score`. Pass `top_k` explicitly to keep the old result count.
- `get_all`: OSS `top_k=20`, not paginated. Platform `page=1`, `page_size=100`.
- `custom_fact_extraction_prompt` (TS `customPrompt`) was renamed `custom_instructions`
(`customInstructions`) in OSS 2.0/3.0; `custom_update_memory_prompt` is deprecated. See the
constructor notes for the hosted equivalent.
- Graph memory (`enable_graph`, `graph_store`) was removed from OSS (Python 2.0.0, TS 3.0.0). On the
Platform graph is built in and always on, with no flag. See gotchas.
- Platform-only: `app_id`, webhooks, custom categories, batch update/delete, feedback, memory export,
user profiles. `timestamp`, `reference_date` and `decay` raise on OSS.
- OSS-only (no Platform option): `reranker` and `history_db_path` config, `memory_type` and `prompt`
on `add`, `explain` on `search`.
@@ -9,8 +9,19 @@ section, phrased as a decision for the developer — never silently resolved.
Migrating the *code* does not move the *memories*. Anything stored in the local vector store /
history DB stays there; the hosted account starts empty. This is the most surprising gap, so call
it out prominently. Data migration is **out of scope** unless the developer explicitly asks. If they
do, the rough path is: read everything from the OSS store (`get_all` per user/entity) and re-`add`
it to the hosted client — but treat that as a separate, opt-in task.
do, treat it as a separate, opt-in task:
- **Hosted Qdrant + Python OSS SDK:** the repo ships `scripts/oss-to-platform-migrate.sh`
(`curl -fsSL https://raw.githubusercontent.com/mem0ai/mem0/main/scripts/oss-to-platform-migrate.sh | bash`,
documented at https://docs.mem0.ai/migration/oss-to-platform). It needs `python3`, signs in to the
Platform (`--email`/`--code` or `--api-key`), exports a scope (`--user-id`, `--agent-id`, `--run-id`
or `--all`) from the Qdrant collection (`--qdrant-url`, `--qdrant-api-key`, `--qdrant-collection`,
default `mem0`) to a JSON file under `~/.mem0/migrations/`, and imports it with `infer: False` (stored
verbatim, no re-extraction). `--export-only` / `--import-only` split the two steps so the export can
be reviewed first. Don't run it unprompted: it logs in and writes under `~/.mem0/`.
- **Anything else:** other vector stores are not supported by the script (the docs say hosted Qdrant
only), and its export is written for the Python OSS SDK, so TS-written data is not documented as
supported. The fallback is to read everything from the OSS store (`get_all` per entity) and re-`add`
it with `infer=False`.
## 2. Self-hosting / data residency
A local or self-hosted vector store sometimes exists *on purpose* — compliance, data residency, air-
@@ -24,15 +35,19 @@ the platform with the platform's configuration. Memory *content and quality may
Flag where the project depended on a specific model.
## 4. Graph memory
If the project uses graph memory (`enable_graph`, `graph_store`), this changed in v3 and is handled
differently on the platform. Don't assume a drop-in mapping — verify current platform graph support
in the docs and flag the usage for the developer.
The external graph store (`graph_store`, `enable_graph`; Neo4j/Memgraph/Kuzu/AGE) was removed from OSS
(Python 2.0.0, TS 3.0.0). On the platform graph memory is built in and always on: nothing to enable or
configure, and it only influences ranking (no separate `relations` payload, no typed relationships,
no direct graph queries). Flag any project code that queried its own graph store or read `relations`.
See https://docs.mem0.ai/platform/features/graph-memory.
## 5. Custom prompts / extraction config
`custom_fact_extraction_prompt` → `custom_instructions`, and `custom_update_memory_prompt` is
deprecated. On the platform these tend to be **project-level settings configured in the dashboard**
rather than passed in code. Flag any custom prompt the project relied on so the developer can re-
apply it in the dashboard.
`custom_fact_extraction_prompt` (TS `customPrompt`) was renamed `custom_instructions`
(`customInstructions`), and `custom_update_memory_prompt` is deprecated (fold it into
`custom_instructions`). On the platform `custom_instructions` is a project-level setting:
`client.project.update(custom_instructions=...)` (TS `client.updateProject({ customInstructions })`),
or per call via `custom_instructions=` on `add()`.
Flag any custom prompt the project relied on so the developer can decide where to re-apply it.
## 6. Every call is now a network request
Local calls become remote API calls. That introduces latency, network failures, timeouts, rate
@@ -41,23 +56,44 @@ error handling / retries / timeouts where the old local calls were effectively i
apps, use `AsyncMemoryClient` (Python) so calls don't block the event loop.
## 7. API key & secrets
The hosted client needs `MEM0_API_KEY`. It must come from the environment / a secrets manager — never
The hosted client needs `MEM0_API_KEY`. It must come from the environment / a secrets manager, never
hardcoded. Ensure it's added to `.env.example`, local `.env`, CI, and deployment config. Without it
the client fails to initialize.
the client fails to initialize. The Python client reads `MEM0_API_KEY` itself when `api_key` is
omitted; the TS client does not, so pass `apiKey: process.env.MEM0_API_KEY!` explicitly.
## 8. Dropped constructor args & legacy options
`org_id` / `project_id` (Python) and `organizationId` / `projectId` (TS) are no longer passed to the
constructor in v3 — they're resolved from the API key. Per-call legacy options like `async_mode`,
constructor, they're resolved from the API key. Per-call legacy options like `async_mode`,
`output_format`, and `enable_graph` are gone. Remove them rather than leaving dead args.
## 9. Return-shape drift
- `add()` returns **only ADD events** on v3. Code that inspected `add()` results for `UPDATE` /
`DELETE` events has dead branches now.
- `search` / `get_all` return `{"results": [...]}`; `get_all` is paginated (`count`/`next`/
`previous`/`results`). Code that limited via `top_k` on `get_all` should move to `page`/`page_size`.
- Default `top_k` dropped 100 → 20, `threshold` now `0.1`, `rerank` now `false` — result counts and
ordering can change even when the call looks equivalent.
## 9. Return-shape and default drift
- `add()` is **asynchronous** on the platform: it returns `{"status": "PENDING", "event_id": "..."}`
(`eventId` on the TS client, which camelCases response keys) instead of the created memories, so new memories may not be searchable yet when it returns
(`infer=False` is synchronous and returns `results`). Code that read `id`/`memory` from `add()`
results, or searched immediately after adding (tests, read-your-writes flows), needs a decision.
Neither SDK exposes an event-poll method; `GET /v1/event/{event_id}/` is REST only.
- `search` returns `{"results": [...]}` on both sides; `get_all` is paginated on the platform
(`count`/`next`/`previous`/`results`). Code that limited via `top_k` on `get_all` should move to
`page`/`page_size` (default `page_size` 100).
- Defaults differ: OSS `search` uses `top_k=20`, the platform uses `top_k=10`. Both default to
`rerank=false`. OSS `threshold` defaults to 0.1; on the platform it is a server-side cutoff, not a floor on the returned `score`. Result counts can change even when the call looks equivalent;
pass `top_k` explicitly.
## 10. No global `reset()`
The OSS `reset()` wipes the whole local store. There's no hosted equivalent that nukes everything;
use `delete_all` scoped by `filters`. Flag any `reset()` call.
## 10. `reset()` is much more destructive
The OSS `reset()` wipes the local store. Python `MemoryClient.reset()` exists but calls `delete_users()`,
which deletes all users, agents, sessions and memories on the platform. The TS client has no `reset()`
(`deleteUsers()` with no arguments does the same). Flag any `reset()` call (often test teardown) and
suggest `delete_all` scoped to the test entity instead. `delete_all(filters=...)` is not a valid
platform form: pass `user_id`/`agent_id`/`app_id`/`run_id` directly.
The hosted wipe is also incomplete: Python `reset()` and TS `deleteUsers()` with no arguments call
`users()` once and delete only the entities on that first page, so a project with more entities than
one page keeps the rest. Re-run until it raises `No entities to delete`, or delete per entity
(`delete_users(user_id=...)` / `deleteUsers({ userId })`, paging TS with `users({ page, pageSize })`).
## 11. Filters and update/add signatures
- Platform `filters` only accept an allowlist of top-level keys; custom metadata must be nested under
`"metadata"` and supports only `eq`/`ne`/`contains` (whole value or list member, not a substring; no `nin`; use a `NOT` list, e.g. `{"NOT": [{"categories": {"in": [...]}}]}`). OSS filters on arbitrary metadata
keys or richer operators need rewriting or a decision.
- Python `update` takes `text=` as a keyword (a positional string breaks); TS `update` requires an
options object and TS `add` requires `Message[]`, not a bare string.
@@ -51,13 +51,15 @@ unambiguous. Example:
## Concerns & decisions needed
The non-1:1 items from the gotchas that apply here, each phrased as a decision for the developer.
Cover, where relevant: data not migrating, self-hosting/data-residency, local models moving server-
side, graph memory, custom prompts (now dashboard settings), network/latency/cost on hot paths,
return-shape changes (`add` ADD-only, `get_all` pagination, default `top_k`/threshold/rerank), and
any `reset()` usage. Be specific about which file/line each concern affects.
side, graph memory, custom prompts (now project-level `custom_instructions`), network/latency/cost on
hot paths, return-shape changes (`add` is async and returns an `event_id` (`eventId` on TS), `get_all` pagination,
default `top_k` 20 vs 10), metadata filters that need rewriting, and any `reset()` usage. Be specific
about which file/line each concern affects.
## Out of scope
- Existing memory **data** is not migrated (code only). If wanted, it's a separate opt-in task
(export from the OSS store, re-add to the hosted client).
(for hosted Qdrant + Python OSS, `scripts/oss-to-platform-migrate.sh`; otherwise export from the
OSS store and re-add to the hosted client).
- No unrelated refactors, renames, or behavior changes.
## Verification plan
@@ -65,7 +67,8 @@ How execution will be confirmed end-to-end:
- Imports / type-checks / byte-compiles cleanly.
- Smoke test against the hosted API with a real `MEM0_API_KEY`: `add` a fact → `search`/`get_all`
returns it → `delete_all` clears it. Plus: the app's own entry point still runs.
- Confirm no local mem0 storage dir is created anymore (e.g. `.mem0/`) — proof memory is hosted.
- Confirm no local OSS storage is created anymore (e.g. `~/.mem0/history.db`, a local Qdrant path).
The client still creates `~/.mem0/config.json`, so the bare `.mem0/` directory is not the check.
## Rollback
- All changes are in version control; revert with git if needed. Note the branch/commit strategy.