Compare commits

..

3 Commits

49 changed files with 1392 additions and 945 deletions
-7
View File
@@ -366,13 +366,6 @@
{
"tab": "Agent Plugins",
"groups": [
{
"group": "Overview",
"icon": "puzzle-piece",
"pages": [
"integrations/agent-plugins"
]
},
{
"group": "Coding Agents",
"icon": "terminal",
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.5 KiB

@@ -1 +0,0 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Claude</title><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" fill="#D97757" fill-rule="nonzero"></path></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1 +0,0 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Claude Code</title><path clip-rule="evenodd" d="M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z" fill="#D97757" fill-rule="evenodd"></path></svg>

Before

Width:  |  Height:  |  Size: 424 B

@@ -1 +0,0 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Codex</title><path d="M19.503 0H4.496A4.496 4.496 0 000 4.496v15.007A4.496 4.496 0 004.496 24h15.007A4.496 4.496 0 0024 19.503V4.496A4.496 4.496 0 0019.503 0z" fill="#fff"></path><path d="M9.064 3.344a4.578 4.578 0 012.285-.312c1 .115 1.891.54 2.673 1.275.01.01.024.017.037.021a.09.09 0 00.043 0 4.55 4.55 0 013.046.275l.047.022.116.057a4.581 4.581 0 012.188 2.399c.209.51.313 1.041.315 1.595a4.24 4.24 0 01-.134 1.223.123.123 0 00.03.115c.594.607.988 1.33 1.183 2.17.289 1.425-.007 2.71-.887 3.854l-.136.166a4.548 4.548 0 01-2.201 1.388.123.123 0 00-.081.076c-.191.551-.383 1.023-.74 1.494-.9 1.187-2.222 1.846-3.711 1.838-1.187-.006-2.239-.44-3.157-1.302a.107.107 0 00-.105-.024c-.388.125-.78.143-1.204.138a4.441 4.441 0 01-1.945-.466 4.544 4.544 0 01-1.61-1.335c-.152-.202-.303-.392-.414-.617a5.81 5.81 0 01-.37-.961 4.582 4.582 0 01-.014-2.298.124.124 0 00.006-.056.085.085 0 00-.027-.048 4.467 4.467 0 01-1.034-1.651 3.896 3.896 0 01-.251-1.192 5.189 5.189 0 01.141-1.6c.337-1.112.982-1.985 1.933-2.618.212-.141.413-.251.601-.33.215-.089.43-.164.646-.227a.098.098 0 00.065-.066 4.51 4.51 0 01.829-1.615 4.535 4.535 0 011.837-1.388zm3.482 10.565a.637.637 0 000 1.272h3.636a.637.637 0 100-1.272h-3.636zM8.462 9.23a.637.637 0 00-1.106.631l1.272 2.224-1.266 2.136a.636.636 0 101.095.649l1.454-2.455a.636.636 0 00.005-.64L8.462 9.23z" fill="url(#lobe-icons-codex-_R_0_)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-codex-_R_0_" x1="12" x2="12" y1="3" y2="21"><stop stop-color="#B1A7FF"></stop><stop offset=".5" stop-color="#7A9DFF"></stop><stop offset="1" stop-color="#3941FF"></stop></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>Cursor</title><rect x="0.25" y="0.25" width="23.5" height="23.5" rx="5.5" fill="#161616" stroke="#3a3a3a" stroke-width="0.5"/><g transform="translate(4.000 4.000) scale(0.66667)" fill="#ffffff" fill-rule="evenodd"><path d="M22.106 5.68L12.5.135a.998.998 0 00-.998 0L1.893 5.68a.84.84 0 00-.419.726v11.186c0 .3.16.577.42.727l9.607 5.547a.999.999 0 00.998 0l9.608-5.547a.84.84 0 00.42-.727V6.407a.84.84 0 00-.42-.726zm-.603 1.176L12.228 22.92c-.063.108-.228.064-.228-.061V12.34a.59.59 0 00-.295-.51l-9.11-5.26c-.107-.062-.063-.228.062-.228h18.55c.264 0 .428.286.296.514z"/></g></svg>

Before

Width:  |  Height:  |  Size: 671 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 19 KiB

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>Kimi</title><rect x="0.25" y="0.25" width="23.5" height="23.5" rx="5.5" fill="#161616" stroke="#3a3a3a" stroke-width="0.5"/><g transform="translate(4 4) scale(0.66667)"><path d="M21.846 0a1.923 1.923 0 110 3.846H20.15a.226.226 0 01-.227-.226V1.923C19.923.861 20.784 0 21.846 0z" fill="#1783FF"></path><path d="M11.065 11.199l7.257-7.2c.137-.136.06-.41-.116-.41H14.3a.164.164 0 00-.117.051l-7.82 7.756c-.122.12-.302.013-.302-.179V3.82c0-.127-.083-.23-.185-.23H3.186c-.103 0-.186.103-.186.23V19.77c0 .128.083.23.186.23h2.69c.103 0 .186-.102.186-.23v-3.25c0-.069.025-.135.069-.178l2.424-2.406a.158.158 0 01.205-.023l6.484 4.772a7.677 7.677 0 003.453 1.283c.108.012.2-.095.2-.23v-3.06c0-.117-.07-.212-.164-.227a5.028 5.028 0 01-2.027-.807l-5.613-4.064c-.117-.078-.132-.279-.028-.381z" fill="#fff"></path></g></svg>

Before

Width:  |  Height:  |  Size: 900 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>opencode</title><rect x="0.25" y="0.25" width="23.5" height="23.5" rx="5.5" fill="#161616" stroke="#3a3a3a" stroke-width="0.5"/><g transform="translate(4.000 4.000) scale(0.66667)" fill="#ffffff" fill-rule="evenodd"><path d="M16 6H8v12h8V6zm4 16H4V2h16v20z"/></g></svg>

Before

Width:  |  Height:  |  Size: 359 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>Pi</title><rect x="0.25" y="0.25" width="23.5" height="23.5" rx="5.5" fill="#161616" stroke="#3a3a3a" stroke-width="0.5"/><g transform="translate(4.000 4.000) scale(0.02857)" fill="#ffffff" fill-rule="evenodd"><path d="M420 280H280V140H0V0H420V280Z"/><path d="M560 560H420V280H560V560Z"/><path d="M140 560H0V140H140V280H280V420H140V560Z"/></g></svg>

Before

Width:  |  Height:  |  Size: 439 B

-111
View File
@@ -1,111 +0,0 @@
---
title: "Agent Plugins Overview"
sidebarTitle: "Overview"
description: "Add persistent memory to the coding agent or agent harness you already use. Compare the Mem0 plugins and pick yours."
---
Coding agents forget everything when a session ends: your conventions, the bug you fixed yesterday, the command that finally worked. A Mem0 plugin fixes that inside the tool you already use. It captures what matters while you work and brings it back in the next session.
<Info>
Plugins use a [Mem0 Platform](https://app.mem0.ai?utm_source=oss&utm_medium=agent-plugins-overview) account. OpenClaw and Hermes can also run fully self-hosted.
</Info>
## How a plugin works
<Steps>
<Step title="Capture">
While you work, the plugin records your prompts, the agent's answers and useful tool results. Credentials are redacted before anything leaves your machine.
</Step>
<Step title="Extract">
Mem0 turns each session into short, durable memories: project conventions, decisions, fixes that worked, and your personal preferences.
</Step>
<Step title="Recall">
In the next session, relevant memories are added to the agent's context automatically or found with a search tool, depending on the plugin.
</Step>
</Steps>
## Coding agents
<CardGroup cols={3}>
<Card title="Claude Code" icon="/images/provider-icons/claudecode-color.svg" href="/integrations/claude-code">
Automatic capture and recall, six commands, and the Sidekick agent.
</Card>
<Card title="Codex" icon="/images/provider-icons/codex-color.svg" href="/integrations/codex">
Automatic capture and recall in the Codex CLI and app.
</Card>
<Card title="Cursor" icon="/images/provider-icons/cursor.svg" href="/integrations/cursor">
Automatic capture, with recall through a search tool.
</Card>
<Card title="OpenCode" icon="/images/provider-icons/opencode.svg" href="/integrations/opencode">
Recall before every prompt and ten native memory tools.
</Card>
<Card title="Kimi Code" icon="/images/provider-icons/kimi-color.svg" href="/integrations/kimi">
Automatic capture and recall in the Kimi Code CLI.
</Card>
<Card title="Antigravity" icon="/images/provider-icons/antigravity-color.svg" href="/integrations/antigravity">
Automatic capture, with recall through a search tool.
</Card>
</CardGroup>
## Agent harnesses and chat apps
<CardGroup cols={3}>
<Card title="OpenClaw" icon="/images/provider-icons/openclaw.svg" href="/integrations/openclaw">
Recall and capture on every turn. Platform or self-hosted.
</Card>
<Card title="Hermes Agent" icon="/images/provider-icons/hermes-tile.svg" href="/integrations/hermes">
Mem0 as Hermes' memory provider. Platform or self-hosted.
</Card>
<Card title="Pi Agent" icon="/images/provider-icons/pi.svg" href="/integrations/pi-agent">
Recall before every turn and automatic capture.
</Card>
<Card title="DeepSeek Harness" icon="/images/provider-icons/deepseek-color.svg" href="/integrations/deepseek-plugin">
Automatic recall and capture with two native tools.
</Card>
<Card title="Claude.ai" icon="/images/provider-icons/claude-color.svg" href="/integrations/claude-ai">
Add the verified Mem0 connector from Claude's directory and sign in. No API key needed.
</Card>
<Card title="Any MCP client" icon="plug" href="/platform/mem0-mcp">
Your tool is not listed? Connect it to the hosted Mem0 MCP server.
</Card>
</CardGroup>
## Compare plugins
<Tabs>
<Tab title="Coding agents">
| Plugin | Auto capture | Auto recall | Skills | Team |
| --- | --- | --- | --- | --- |
| [Claude Code](/integrations/claude-code) | Yes | First prompt | 6, plus Sidekick | Shared |
| [Codex](/integrations/codex) | Yes | First prompt | 6 | Shared |
| [Cursor](/integrations/cursor) | Yes | Search tool | 6 | Shared |
| [Kimi Code](/integrations/kimi) | Yes | First prompt | 6 | Shared |
| [Antigravity](/integrations/antigravity) | Yes | Search tool | 6 | Shared |
| [OpenCode](/integrations/opencode) | Yes | Every prompt | 7 | Per user |
**First prompt:** relevant memories are added before the agent answers the first prompt of a session. **Search tool:** the agent calls `search_memories` when it needs context. **Shared:** teammates on the same repo share project memory.
</Tab>
<Tab title="Harnesses and chat apps">
| Plugin | Auto capture | Auto recall | Tools | Self-hosted |
| --- | --- | --- | --- | --- |
| [OpenClaw](/integrations/openclaw) | Yes | Every turn | Tools and skills | Yes |
| [Hermes Agent](/integrations/hermes) | Yes | Every turn | 4 | Yes |
| [Pi Agent](/integrations/pi-agent) | Yes | Every turn | 1 | No |
| [DeepSeek Harness](/integrations/deepseek-plugin) | Yes | Every prompt | 2 | No |
| [Claude.ai](/integrations/claude-ai) | When asked | When relevant | 9 | No |
</Tab>
</Tabs>
<Note>
Claude Code, Codex, Cursor, Kimi Code and Antigravity share the same memory core. Teammates working in the same repository, in any of these tools, read and write one shared project memory while their personal preferences stay private.
</Note>
## Which plugin should I use?
- **You code in a terminal or IDE agent:** install the plugin for that tool from the cards above. Each page has its own install steps.
- **Your team uses different agents on the same repo:** pick any of Claude Code, Codex, Cursor, Kimi Code or Antigravity. They share project memory across tools.
- **You chat in Claude:** add the [Mem0 connector](https://claude.com/connectors/mem0) from Claude's directory.
- **Your tool has no plugin:** connect it to the [hosted Mem0 MCP server](/platform/mem0-mcp).
- **You need to keep data on your own infrastructure:** use OpenClaw or Hermes in self-hosted mode.
<Snippet file="star-on-github.mdx" />
-1
View File
@@ -268,7 +268,6 @@ If the user is on a pre-current major (Python < 2, TS < 3, or a Platform call st
- [Strands Agents](https://docs.mem0.ai/integrations/strands) [Both]: Use when the user is on AWS Strands and wants a native MemoryStore.
### AI Coding Tools
- [Agent Plugins Overview](https://docs.mem0.ai/integrations/agent-plugins) [Platform]: Use when choosing a Mem0 plugin for a coding agent or agent harness, or comparing what each plugin captures and recalls.
- [Claude Code](https://docs.mem0.ai/integrations/claude-code) [Platform]: Use when wiring memory into Claude Code.
- [Claude.ai](https://docs.mem0.ai/integrations/claude-ai) [Platform]: Use when connecting Mem0 to Claude.ai (the hosted web app) via a custom remote MCP connector, or when Claude's native memory seems to be crowding out mem0 tool calls.
- [Cursor](https://docs.mem0.ai/integrations/cursor) [Platform]: Use when adding lifecycle capture, explicit memory recall, and six memory skills to Cursor.
+4 -4
View File
@@ -136,8 +136,8 @@ curl -X POST 'https://api.mem0.ai/v3/memories/?page=1&page_size=50' \
| Parameter | V1/V2 | V3 | Notes |
|---|---|---|---|
| `top_k` | Supported | Supported (1-1000, default 10) | No change |
| `threshold` | Default: none | Default: `0.1` | Pass `0.0` to disable |
| `rerank` | Default: `true` | Default: `false` | Pass `true` to enable (adds latency) |
| `threshold` | Default: `0.3` (v2) | Default: `0.1` | Pass `0.3` to keep the v2 cutoff, or `0.0` to disable |
| `rerank` | Default: `false` | Default: `false` | No change. Pass `true` to enable (adds latency) |
| Entity IDs in `search` / `get_all` | Top-level | Inside `filters` dict | Top-level raises 400 |
### Response Format
@@ -265,10 +265,10 @@ If your application previously read graph relations from the API response (`rela
<Steps>
<Step title="Review your search thresholds">
The default `threshold` is now `0.1` (previously no threshold). If your application was relying on unfiltered results, explicitly pass `threshold=0.0` in your search calls to preserve the old behavior. In most cases, the new default is better: it filters out low-relevance noise.
The default `threshold` is now `0.1` (it was `0.3` on v2), so lower-scoring matches that v2 hid can now appear. Scores are computed differently in v3, so retune against representative queries. To keep a stricter cutoff, pass an explicit `threshold` in your search calls, or pass `threshold=0.0` to disable filtering.
</Step>
<Step title="Review reranking usage">
Reranking is now `false` by default. If your application depended on reranked results, add `rerank=True` to your search calls. Note that reranking adds latency (~200-400ms) but can improve ordering quality for complex queries.
Reranking is `false` by default, the same as on v2. If your application depends on reranked results, pass `rerank=True` to your search calls. Note that reranking adds latency (~200-400ms) but can improve ordering quality for complex queries.
</Step>
<Step title="Update score handling (optional)">
The top-level `score` field continues to work as before. It is now a combined multi-signal score (semantic + keyword + entity) rather than pure cosine similarity, so the absolute numbers will differ. Relative ranking remains comparable: if you have threshold-based filtering in your app, retune on a representative query set.
+3 -1
View File
@@ -768,7 +768,9 @@ export class Memory {
has_filters: !!config.filters,
infer: config.infer,
});
const { filters = {}, infer = true } = config;
// Copy: scope keys are written into this object below, and the caller's next add() must not inherit them (#6796).
const filters: SearchFilters = { ...(config.filters ?? {}) };
const { infer = true } = config;
const metadata = stripIdentityKeys(config.metadata);
// Validate and trim entity IDs
@@ -0,0 +1,115 @@
/// <reference types="jest" />
/**
* add() writes the resolved scope (user_id / agent_id / run_id) into its
* filters object. If that object is the caller's, the scope survives the call
* and leaks into the next add() that reuses it, which also defeats the
* "one of userId, agentId or runId is required" guard.
*
* search() and getAll() already build a fresh object from config.filters.
*/
jest.mock("../src/utils/telemetry", () => ({
captureClientEvent: jest.fn().mockResolvedValue(undefined),
isTelemetryEnabled: jest.fn(() => false),
}));
import { Memory } from "../src/memory";
const DIM = 1536;
let seq = 0;
function createMemory(): Memory {
const m = new Memory({
disableHistory: true,
vectorStore: {
provider: "memory",
config: {
collectionName: `test-add-filters-${seq++}`,
dimension: DIM,
dbPath: ":memory:",
},
},
historyDbPath: ":memory:",
embedder: { provider: "openai", config: { apiKey: "test-key" } },
llm: { provider: "openai", config: { apiKey: "test-key" } },
} as any);
// infer:false is enough to exercise the scope handling, so the LLM is never
// called; the embedder just has to return a well-formed vector.
(m as any).embedder = {
embed: async () => new Array(DIM).fill(0.1),
embedBatch: async (texts: string[]) =>
texts.map(() => new Array(DIM).fill(0.1)),
};
return m;
}
describe("add() does not mutate the caller's filters object", () => {
it("leaves the caller's object untouched", async () => {
const memory = createMemory();
const filters: Record<string, any> = {};
await memory.add([{ role: "user", content: "alice note" }], {
userId: "alice",
infer: false,
filters,
});
expect(filters).toEqual({});
});
it("still throws when a reused object is the only source of scope", async () => {
const memory = createMemory();
const filters: Record<string, any> = {};
await memory.add([{ role: "user", content: "alice note" }], {
userId: "alice",
infer: false,
filters,
});
// No userId/agentId/runId on this call, so the required-scope guard must
// fire rather than reading a value left behind by the previous call.
await expect(
memory.add([{ role: "user", content: "bob note" }], {
infer: false,
filters,
}),
).rejects.toThrow(
"One of the filters: userId, agentId or runId is required!",
);
});
it("does not store one user's memory under another user's scope", async () => {
const memory = createMemory();
const filters: Record<string, any> = {};
await memory.add([{ role: "user", content: "alice note" }], {
userId: "alice",
infer: false,
filters,
});
// A second caller reuses the object and supplies no scope of its own. This
// must not be silently attributed to alice.
await memory
.add([{ role: "user", content: "bob note" }], {
infer: false,
filters,
})
.catch(() => undefined);
const alice = await memory.getAll({ filters: { user_id: "alice" } });
expect(alice.results.map((r) => r.memory)).toEqual(["alice note"]);
});
it("keeps scope keys the caller passed in", async () => {
const memory = createMemory();
await memory.add([{ role: "user", content: "scoped note" }], {
infer: false,
filters: { user_id: "carol" },
});
const carol = await memory.getAll({ filters: { user_id: "carol" } });
expect(carol.results.map((r) => r.memory)).toEqual(["scoped note"]);
});
});
@@ -34,7 +34,7 @@ class OutputData(BaseModel):
class GoogleMatchingEngine(VectorStoreBase):
def __init__(self, **kwargs):
"""Initialize Google Matching Engine client."""
logger.debug("Initializing Google Matching Engine with kwargs: %s", kwargs)
logger.debug("Initializing Google Matching Engine with config keys: %s", sorted(kwargs))
# If collection_name is passed, use it as deployment_index_id if deployment_index_id is not provided
if "collection_name" in kwargs and "deployment_index_id" not in kwargs:
@@ -46,7 +46,7 @@ class GoogleMatchingEngine(VectorStoreBase):
try:
config = GoogleMatchingEngineConfig(**kwargs)
logger.debug("Config created: %s", config.model_dump())
logger.debug("Config created with fields: %s", sorted(config.model_dump()))
logger.debug("Config collection_name: %s", getattr(config, "collection_name", None))
except Exception as e:
logger.error("Failed to validate config: %s", str(e))
+13 -9
View File
@@ -50,16 +50,20 @@ Everything else, meaning full step mechanics, document templates, and verbatim s
Current sizes, longest first:
```
mem0/references/use-cases.md 720 reference, on demand
mem0-cli/references/command-reference.md 694 reference, on demand
mem0/client/python.md 487 reference, on demand
mem0-integrate/references/pipeline.md 375 reference, on demand
mem0-test-integration/SKILL.md 368 entry point, under budget
mem0-cli/references/command-reference.md 757 reference, on demand
mem0/references/use-cases.md 721 reference, on demand
mem0/client/python.md 522 reference, on demand
mem0/client/node.md 507 reference, on demand
mem0-cli/references/workflows.md 446 reference, on demand
mem0/references/features.md 406 reference, on demand
mem0/references/integration-patterns.md 403 reference, on demand
mem0-test-integration/SKILL.md 386 entry point, under budget
mem0-integrate/references/pipeline.md 380 reference, on demand
mem0-integrate/SKILL.md 220 entry point
mem0/SKILL.md 193 entry point
mem0-vercel-ai-sdk/SKILL.md 192 entry point
mem0-cli/SKILL.md 169 entry point
mem0-oss-to-platform/SKILL.md 120 entry point
mem0-vercel-ai-sdk/SKILL.md 210 entry point
mem0/SKILL.md 194 entry point
mem0-cli/SKILL.md 170 entry point
mem0-oss-to-platform/SKILL.md 132 entry point
```
`mem0-integrate` is the one skill that needed splitting: it was 620 lines, now
+1 -1
View File
@@ -6,7 +6,7 @@ Manage memories from the terminal using the [Mem0 CLI](https://docs.mem0.ai/plat
When installed, Claude can:
- **Run mem0 commands** correctly in your terminal (add, search, list, get, update, delete, import, config, init, status, entity, event)
- **Run mem0 commands** correctly in your terminal (add, search, list, get, update, delete, import, config, init, identify, whoami, status, entity, event, agent-rush, version, help)
- **Construct complex invocations** with the right flags, scoping, filters, and output formats
- **Pipe and script** mem0 commands in shell workflows, CI/CD pipelines, and agent loops
- **Debug issues** like missing API keys, entity scoping conflicts, and async processing delays
+16 -15
View File
@@ -12,9 +12,10 @@ description: >
license: Apache-2.0
metadata:
author: mem0ai
version: "1.1.0"
version: "1.2.0"
category: ai-memory
tags: "cli, terminal, memory, ai, command-line"
mem0_tested_versions: "mem0-cli (PyPI) >=0.2.13,<0.3.0; @mem0/cli (npm) >=0.2.14,<0.3.0; mem0ai (PyPI) >=2.0.0,<3.0.0; mem0ai (npm) >=3.0.0,<4.0.0"
compatibility: Node.js 18+ (npm install -g @mem0/cli) or Python 3.10+ (pip install mem0-cli), MEM0_API_KEY env var
---
@@ -34,7 +35,7 @@ npm install -g @mem0/cli
pip install mem0-cli
```
Both packages install a `mem0` binary with identical commands, options, and output formats.
Both packages install a `mem0` binary with the same commands, options, and output formats (see [Node and Python Differences](#node-and-python-differences) for the exceptions).
## Setup
@@ -105,7 +106,7 @@ mem0 delete --all --user-id alice --force
## Agent / JSON Mode
Use `--json` or `--agent` to get structured output suitable for LLM consumption. Every command wraps its response in a standard envelope:
Use `--json` or `--agent` to get structured output suitable for LLM consumption. Place the flag before the subcommand (`mem0 --json search "q"`), which works in both CLIs. Data commands (add, search, list, get, update, delete, import, config, entity, event, status) wrap their response in a standard envelope:
```json
{
@@ -114,7 +115,6 @@ Use `--json` or `--agent` to get structured output suitable for LLM consumption.
"duration_ms": 245,
"scope": { "user_id": "alice" },
"count": 3,
"error": null,
"data": [
{ "id": "mem-abc", "memory": "User prefers dark mode", "score": 0.92 }
]
@@ -126,30 +126,31 @@ On error:
{
"status": "error",
"command": "search",
"error": "Authentication failed. Your API key may be invalid or expired.",
"error": "Invalid or expired API key.",
"data": null
}
```
The `--agent` flag is an alias for `--json`. Both write spinners and progress to stderr so stdout is always clean, parseable JSON.
The `--agent` flag is an alias for `--json` (except on `mem0 init`, where `--agent` is the Agent Mode bootstrap flag). Both write spinners and progress to stderr so stdout is clean, parseable JSON (the one exception is Node `import`, see below). Optional keys: `duration_ms`, `scope`, `count` appear only where relevant, and `mem0_notice` appears when the platform flags an unclaimed Agent Mode account.
## Node and Python Parity
## Node and Python Differences
Both the Node.js (`@mem0/cli`) and Python (`mem0-cli`) CLIs are implemented from the same specification (`cli-spec.json`). They share:
Both the Node.js (`@mem0/cli`) and Python (`mem0-cli`) CLIs share the command set, flags, entity ID resolution, filter building, and the JSON envelope. Choose whichever runtime you already have installed. Known differences:
- Identical command names, arguments, and flags
- Identical output formats (text, json, table, quiet)
- Identical entity ID resolution, graph tri-state, filter building
- Identical error messages and exit codes
Choose whichever runtime you already have installed. The behavior is the same.
- **`--json` / `--agent` placement:** Python accepts the flag anywhere on the command line. Node reads it only as a global option, so put it before the subcommand (`mem0 --json list`). `mem0 init --json` and `mem0 help --json` work after the subcommand in both. For the Agent Mode bootstrap, Node starts it only from `init --agent` or `init --json` (a root-level `mem0 --json init` or `mem0 --agent init` does not start it, and without an agent runtime env var it fails with the non-TTY error), while Python also accepts those root-level forms. `mem0 init --agent --json` works in both.
- **`--limit`:** `mem0 search --limit` is a Python-only alias for `--top-k`.
- **Agent-mode `delete --all` data:** Python returns `{"deleted": true}` (plus scope for `--project`); Node returns the raw API result.
- **`import` JSON output:** Python prints the envelope with `scope`. Node omits `scope` and writes the `Importing memories... n/n` progress line to stdout before the JSON, so only Python's output pipes cleanly to `jq`.
- **Message text:** The empty-search error (`Search query cannot be empty.` in Python, `No query provided...` in Node) and the delete dry-run footers differ slightly.
## Common Edge Cases
- **Async processing delay:** After `mem0 add`, memories process asynchronously. Wait 2-3 seconds before searching for newly added content. Use `mem0 event list` to check processing status.
- **`--all` vs `--entity` delete modes:** `mem0 delete --all -u alice` deletes all memories for user alice. `mem0 delete --entity -u alice` deletes the entity itself AND all its memories (cascade). These are mutually exclusive modes.
- **`--dry-run` in `--json`/`--agent` mode:** `mem0 --json delete --all --dry-run` still requires `--force`, then prints nothing and deletes nothing (exit 0). Node also prints nothing for single and `--entity` dry runs. Use text mode to see the preview.
- **`--dry-run` does not protect `--all --project`:** `mem0 delete --all --project --dry-run` ignores the flag and deletes every memory in the project. Never use `--dry-run` to preview a project-wide delete.
- **Entity ID resolution:** If you pass any explicit scope flag (e.g. `--user-id`), the CLI uses ONLY the explicit IDs and ignores config defaults. If no scope flags are given, all configured defaults apply.
- **Stdin detection:** When no text argument is provided and input is piped (not a TTY), the CLI reads from stdin. Works with `add`, `search`, and `update`.
- **Stdin detection:** When no text argument is provided and stdin is a pipe or a redirected file (a plain non-TTY is not enough), the CLI reads from stdin. Works with `add`, `search`, and `update`. In `--json`/`--agent` mode `add` never reads stdin (Python also skips it for `search` and `update`), so pass the text as an argument there.
## References
+135 -72
View File
@@ -1,20 +1,25 @@
# Mem0 CLI Command Reference
Complete reference for every command, argument, flag, and output mode in the mem0 CLI. Both the Node.js (`@mem0/cli`) and Python (`mem0-cli`) implementations are identical in behavior.
Complete reference for every command, argument, flag, and output mode in the mem0 CLI. Both the Node.js (`@mem0/cli`) and Python (`mem0-cli`) implementations share the same commands and flags. Where they differ, the difference is called out inline.
---
## Global Options
These options are available on every command:
Only these two options are global:
| Flag | Type | Description |
|------|------|-------------|
| `--json` / `--agent` | boolean | Agent mode: wrap output in a structured JSON envelope on stdout. Spinners and progress go to stderr (except Node `import`, see its section). Put it before the subcommand (`mem0 --json list`); Python also accepts it anywhere. On `mem0 init`, `--agent` is the Agent Mode bootstrap flag instead (use `--json` there, after the subcommand). |
| `--version` | boolean | Print version and exit. |
These options are declared per command (not global) on `add`, `search`, `get`, `list`, `update`, `delete`, `import`, `status`, `entity list`, `entity delete`, `event list` and `event status`. `config show` takes only `-o`, and `init` takes only `--api-key`.
| Flag | Type | Description |
|------|------|-------------|
| `--json` / `--agent` | boolean | Agent mode: wrap all output in a structured JSON envelope on stdout. Spinners and progress go to stderr. |
| `-o, --output <format>` | string | Output format. Supported values vary per command (see matrix below). |
| `--api-key <key>` | string | Override the API key for this invocation. Takes precedence over env var and config file. |
| `--base-url <url>` | string | Override the API base URL (default: `https://api.mem0.ai`). |
| `--version` | boolean | Print version and exit. |
---
@@ -41,11 +46,12 @@ Interactive setup wizard. Configures API key and default user ID.
**Behavior:**
- If `~/.mem0/config.json` already exists with an API key, warns and asks for confirmation (or errors in non-TTY unless `--force` is set).
- **Email login flow** (`--email`): sends a 6-digit code to the email via `POST /api/v1/auth/email_code/`. If `--code` is also given, verifies immediately. On success, saves API key, org_id, and project_id. Cannot be combined with `--api-key`.
- **API key flow**: if both `--api-key` and `--user-id` are given, runs fully non-interactively. Otherwise prompts for missing values.
- **Agent Mode flow** (`--agent`): POSTs to `/api/v1/auth/agent_mode/`, mints a shadow API key in <5s with no email required. Pass `--agent-caller <your-name>` to attribute the signup to your AI agent identity. If omitted, run `mem0 identify <your-name>` afterward.
- In non-TTY without sufficient flags, prints a usage hint and exits with error.
- If `~/.mem0/config.json` already exists with an API key, warns and asks for confirmation. In non-TTY it errors with "Existing config would be overwritten." unless `--force` is set. The Agent Mode path (below) runs before this check.
- **Email login flow** (`--email`): sends a 6-digit code to the email via `POST /api/v1/auth/email_code/`. If `--code` is also given, skips sending and verifies immediately via `/api/v1/auth/email_code/verify/`. In non-TTY without `--code`, the code is sent and the command then errors; re-run with `--code`. On success, saves the API key, `user_email`, and `created_via: "email"`, and sets the default user ID to `--user-id`, else `$USER`/`$USERNAME`, else `mem0-cli`. Cannot be combined with `--api-key`. `--code` without `--email` is an error.
- **Claim flow** (`--email` while the existing config is an unclaimed Agent Mode key): runs the same code flow but claims the existing key to that email. The API key value does not change and memories are kept.
- **API key flow**: if both `--api-key` and `--user-id` are given, runs fully non-interactively (and validates the key against the API). In non-TTY, `--api-key` alone is enough (the user ID defaults to `$USER`/`$USERNAME`/`mem0-cli`). In a TTY with no flags, prompts for the auth method (email or API key), then for the missing values.
- **Agent Mode flow** (`init --agent`, `init --json`, or an agent runtime env var such as `CLAUDECODE` or `CURSOR_AGENT`, with no `--api-key`/`--email`; Python also enters it on a global `mem0 --json init` or `mem0 --agent init`, Node does not): first reuses a valid `MEM0_API_KEY` or a valid key already in config (no new key is minted). Otherwise POSTs to `/api/v1/auth/agent_mode/` and mints a shadow API key in <5s with no email required; the generated `user_<slug>` becomes `defaults.user_id`. Limited to 5 signups per day per network. Pass `--agent-caller <your-name>` to attribute the signup to your AI agent identity. If omitted, run `mem0 identify <your-name>` afterward.
- In non-TTY without `--api-key`, `--email`, or an agent signal, prints "Non-interactive terminal detected and --api-key is required." and exits with error.
**Examples:**
```bash
@@ -61,16 +67,16 @@ mem0 init --agent --agent-caller claude-code # AI agent self-identifies during
### `mem0 identify`
Tag your active Agent Mode key with the AI agent that's using it. Run this once after `mem0 init --agent` if you didn't pass `--agent-caller`. Idempotent — re-running just overwrites the value.
Tag your active Agent Mode key with the AI agent that's using it. Run this once after `mem0 init --agent` if you didn't pass `--agent-caller`. Idempotent: re-running just overwrites the value.
**Usage:** `mem0 identify <name>`
**Argument:** `<name>` — the AI agent identity (e.g. `claude-code`, `cursor`, `codex`, `cline`, `aider`, or a custom string).
**Argument:** `<name>`: the AI agent identity (e.g. `claude-code`, `cursor`, `codex`, `cline`, `aider`, or a custom string).
**Behavior:**
- PATCHes `/api/v1/auth/agent_mode/caller/` with `Authorization: Token <current-api-key>` and body `{agent_caller}`.
- Only works on unclaimed agent-mode keys (`platform.agent_mode=true` in config).
- Only works on unclaimed agent-mode keys (`platform.agent_mode=true` in config). Errors with "No API key configured." if no key is set, or "This command only works on unclaimed agent-mode keys." otherwise.
- Backend sanitizes the value: lowercases, drops anything outside `[a-z0-9._/-]`, truncates to 32 chars.
**Examples:**
@@ -82,6 +88,45 @@ mem0 identify my-custom-bot
---
### `mem0 whoami`
Print your AGENTRUSH identifier (`platform.default_user_id` from config). Errors with "No default_user_id found. Run `mem0 init --agent` first." if none is stored.
**Usage:** `mem0 whoami`
---
### `mem0 agent-rush add|search`
Commands for the AGENTRUSH event game. Memories are public to other players, so never include real names, emails, secrets, or PII.
**Usage:** `mem0 agent-rush add <content>` and `mem0 agent-rush search <query>`
- `add`: content must be 50-1000 characters with no URLs. The server requires 3 searches before adding and caps each key at 3 lifetime searches and 3 lifetime adds.
- Requires an API key from config or `MEM0_API_KEY` (otherwise errors with "Not initialized. Run `mem0 init --agent` first."). Node joins unquoted words into one string; Python takes a single quoted argument.
**Examples:**
```bash
mem0 agent-rush search "constraint satisfaction"
mem0 agent-rush add "I enjoy solving constraint-satisfaction problems and writing small solvers."
```
---
### `mem0 version`
Print the CLI version. `mem0 --version` does the same.
---
### `mem0 help`
**Usage:** `mem0 help [--json]`
Prints the command overview. `--json` (or global `--json`/`--agent`) prints a machine-readable command spec. In both CLIs this spec is hand-maintained and can lag behind the real option list, so trust `mem0 <command> --help` and this reference over it.
---
### `mem0 add`
Add a memory from text, messages, file, or stdin.
@@ -106,10 +151,17 @@ Add a memory from text, messages, file, or stdin.
| `-f, --file <path>` | path | - | Read messages from a JSON file. |
| `-m, --metadata <json>` | string | - | Custom metadata as JSON object (e.g. `'{"source":"cli"}'`). |
| `--no-infer` | boolean | false | Skip inference; store the text verbatim. |
| `--categories <cats>` | string | - | Categories as JSON array or comma-separated string. |
| `--expires <date>` | string | - | Expiration date (YYYY-MM-DD). Must be in the future. |
| `--immutable` | boolean | false | Accepted but has no effect on v3: the memory can still be updated and no marker is stored. |
| `--custom-instructions <text>` | string | - | Custom instructions for fact extraction. |
| `--agent-custom-instructions <text>` | string | - | Extraction instructions for agent-scoped memories, overriding the project setting. |
| `--custom-categories <json>` | string | - | Custom categories as a JSON array of `{name: description}` objects. |
| `--structured-data-schema <json>` | string | - | Schema for structured data extraction, as JSON. |
| `--timestamp <unix>` | integer | - | Unix timestamp for the memory. |
| `--categories <value>` | string | - | Rejected with an error. Use `--custom-categories` instead. |
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`, `quiet`. |
**Input priority:** `--file` > `--messages` > text argument > stdin (if piped and no text).
**Input priority:** `--file` > `--messages` > text argument > stdin (if piped or redirected, no text, and not in `--json`/`--agent` mode).
Text content is wrapped as `[{"role": "user", "content": "<text>"}]` before sending to the API. Messages from `--messages` or `--file` are sent as-is.
@@ -130,9 +182,8 @@ mem0 add "allergic to nuts" -u alice -m '{"source":"onboarding"}'
mem0 add --messages '[{"role":"user","content":"I like Python"}]' -u alice
mem0 add --file conversation.json -u alice -o json
echo "I prefer dark mode" | mem0 add -u alice
mem0 add "temporary note" -u alice --expires 2025-12-31
mem0 add "important fact" -u alice --immutable
mem0 add "uses vim" -u alice --categories "tools,preferences"
mem0 add "temporary note" -u alice --expires 2027-12-31
mem0 add "uses vim" -u alice --custom-categories '[{"tools":"Editors and developer tooling"}]'
```
---
@@ -147,7 +198,7 @@ Search memories by semantic query.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `query` | string | Yes | The search query. Falls back to stdin if piped. |
| `query` | string | Yes | The search query. Falls back to stdin if piped or redirected (Python skips this in `--json`/`--agent` mode; Node does not). |
**Options:**
@@ -157,11 +208,15 @@ Search memories by semantic query.
| `--agent-id <id>` | string | - | Filter by agent. |
| `--app-id <id>` | string | - | Filter by app. |
| `--run-id <id>` | string | - | Filter by run. |
| `-k, --top-k, --limit <n>` | integer | 10 | Maximum number of results to return. |
| `--threshold <score>` | float | 0.1 | Minimum similarity score (0.0 to 1.0). |
| `-k, --top-k <n>` | integer | 10 | Maximum number of results to return (must be >= 1). Python also accepts `--limit` as an alias. |
| `--threshold <score>` | float | 0.3 | Minimum similarity score (0.0 to 1.0), applied before hybrid score blending, so a returned item's displayed `score` can be lower than this value. |
| `--rerank` | boolean | false | Enable reranking for improved relevance (Platform only). |
| `--filter <json>` | string | - | Advanced filter expression as JSON (AND/OR operators). |
| `--fields <list>` | string | - | Comma-separated list of fields to return. |
| `--keyword` | boolean | false | Sent to the API as `keyword_search` but not applied by v3 search, which always blends keyword matching into hybrid scoring. |
| `--filter <json>` | string | - | Advanced filter expression as JSON. If it contains `AND` or `OR` it is sent as-is and entity IDs (including config defaults) are not merged in. |
| `--fields <list>` | string | - | Comma-separated list of fields to return. Sent to the API but not applied by v3 search. |
| `--show-expired` | boolean | false | Include expired memories. |
| `--reference-date <date>` | string | - | Reference date for relative queries (YYYY-MM-DD or unix timestamp). |
| `--latest-only` | boolean | false | Only return the latest version of each memory. |
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`, `table`. |
**Examples:**
@@ -171,6 +226,8 @@ mem0 search "tools" -u alice -o json -k 5
mem0 search "dietary restrictions" -u alice --threshold 0.5
mem0 search "project setup" -u alice --rerank
mem0 search "preferences" -u alice --filter '{"categories":{"contains":"food"}}'
mem0 search "invoices" -u alice --filter '{"AND":[{"user_id":"alice"},{"categories":{"in":["work"]}}]}'
mem0 search "plans" -u alice --latest-only
echo "preferences" | mem0 search -u alice
```
@@ -221,6 +278,8 @@ List memories with optional filters and pagination.
| `--category <name>` | string | - | Filter by category. |
| `--after <date>` | string | - | Created after (YYYY-MM-DD). |
| `--before <date>` | string | - | Created before (YYYY-MM-DD). |
| `--show-expired` | boolean | false | Include expired memories. |
| `--latest-only` | boolean | false | Only return the latest version of each memory. |
| `-o, --output <fmt>` | string | `table` | Output format: `text`, `json`, `table`. |
**Examples:**
@@ -244,13 +303,15 @@ Update a memory's text or metadata.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `memory_id` | string | Yes | The UUID of the memory to update. |
| `text` | string | No | New memory text. Falls back to stdin if piped and no `--metadata`. |
| `text` | string | No | New memory text. Falls back to stdin if piped or redirected (Python skips this in `--json`/`--agent` mode; Node does not). |
**Options:**
| Flag | Type | Default | Description |
|------|------|---------|-------------|
| `-m, --metadata <json>` | string | - | Update metadata as JSON object. |
| `--expires <date>` | string | - | Expiration date (YYYY-MM-DD). Must be in the future. |
| `--timestamp <unix>` | integer | - | Unix timestamp for the memory. |
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`, `quiet`. |
**Examples:**
@@ -282,8 +343,9 @@ Delete a memory, all memories matching a scope, or an entity. This command has t
| `--all` | boolean | false | Delete all memories matching scope filters. |
| `--entity` | boolean | false | Delete the entity itself and all its memories (cascade). |
| `--project` | boolean | false | With `--all`: delete ALL memories project-wide (sends wildcard IDs). |
| `--dry-run` | boolean | false | Show what would be deleted without actually deleting. |
| `--force` | boolean | false | Skip confirmation prompt. |
| `--dry-run` | boolean | false | Show what would be deleted without actually deleting. Ignored by `--all --project`, which deletes (see Dry-run behavior). |
| `--force` | boolean | false | Skip confirmation prompt (`--all` and `--entity` only). Required for those modes in `--json`/`--agent` mode. |
| `--delete-linked` | boolean | false | Single-memory mode: also delete memories linked to this memory. |
| `-u, --user-id <id>` | string | - | Scope to user. |
| `--agent-id <id>` | string | - | Scope to agent. |
| `--app-id <id>` | string | - | Scope to app. |
@@ -296,14 +358,17 @@ Delete a memory, all memories matching a scope, or an entity. This command has t
2. **Bulk delete:** `mem0 delete --all [scope flags]` -- deletes all memories matching the scope. Add `--project` to wipe all memories project-wide (sends wildcard `*` entity IDs).
3. **Entity cascade:** `mem0 delete --entity [scope flags]` -- deletes the entity itself AND all its memories.
You cannot combine `<memory_id>` with `--all` or `--entity`, and you cannot combine `--all` with `--entity`. If none of these are provided, the command prints a usage hint and exits with an error.
You cannot combine `<memory_id>` with `--all` or `--entity`, and you cannot combine `--all` with `--entity`. If none of these are provided, the command prints an error and exits 1.
**Entity IDs:** `--all` resolves IDs like `search` (explicit flags only, else config defaults). Single delete and `--entity` use only explicit flags, and `--entity` requires at least one.
**Dry-run behavior:**
- Single: fetches the memory, displays it, prints "No changes made."
- `--all`: lists matching memories with count, prints "No changes made."
- `--entity`: shows the affected scope without deleting.
- Single: fetches the memory, displays it, and prints "No changes made." (Python: "No changes made (dry run)."). Node also prints "Would delete memory <id8>: <text>".
- `--all`: lists matching memories, prints "Would delete N memories." and the "No changes made" line. In `--json`/`--agent` mode `--all` still requires `--force` even with `--dry-run`, and the command then exits 0 with no output and deletes nothing. Use text mode to see the preview.
- `--entity`: prints "Would delete entity <scope> and all its memories." and the "No changes made" line.
- **Warning:** `--all --project` does not honor `--dry-run`. It skips the preview and deletes every memory in the project (after the confirmation, or immediately with `--force`). Never pass `--dry-run` to `--all --project` expecting a preview. This is a known CLI bug in both CLIs, not intended behavior, so do not rely on it. To preview, run `mem0 delete --all --dry-run` per scope (for example `-u alice`) instead.
**Confirmation:** Without `--force`, all destructive modes prompt `[y/N]`. With `--all --project`, the prompt explicitly warns about project-wide deletion.
**Confirmation:** Without `--force`, `--all` and `--entity` prompt `[y/N]`. Single-memory delete never prompts and ignores `--force`. With `--all --project`, the prompt explicitly warns about project-wide deletion and the scope flags are ignored.
**`--all --project` behavior:** Sends `DELETE /v1/memories/` with `user_id=*&agent_id=*&app_id=*&run_id=*`. The API returns an async response. The CLI prints "Deletion started. Memories will be removed in the background."
@@ -339,7 +404,7 @@ Import memories from a JSON file.
| `--agent-id <id>` | string | - | Override agent ID for all imported items. |
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`. |
**File format:** A JSON array (or single object) where each item has a `memory`, `text`, or `content` field for the text, plus optional `user_id`, `agent_id`, and `metadata` fields. CLI-provided `--user-id` and `--agent-id` override per-item values.
**File format:** A JSON array (or single object) where each item has a `memory`, `text`, or `content` field for the text, plus optional `user_id`, `agent_id`, and `metadata` fields. `--user-id` and `--agent-id` override per-item values, and so do the config defaults when neither flag is given. Items with no text count as failed.
**Import format example:**
```json
@@ -350,7 +415,7 @@ Import memories from a JSON file.
]
```
**Behavior:** Iterates through items, calling the add API for each. Displays progress and reports `added` and `failed` counts on completion.
**Behavior:** Iterates through items, calling the add API for each. Displays progress and reports `added` and `failed` counts on completion (text mode writes the summary to stderr in Python and to stdout in Node). In JSON mode the Python CLI sends progress to stderr and includes `scope` in the envelope; the Node CLI writes the progress line to stdout before the JSON (so `| jq` fails) and omits `scope`. Only `-u`, `--agent-id`, `-o`, `--api-key` and `--base-url` are accepted.
**Examples:**
```bash
@@ -372,6 +437,8 @@ Display current configuration with secrets redacted.
|------|------|---------|-------------|
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`. |
**Behavior:** `-o json` (or agent mode) returns the standard envelope with `data` shaped as `{"defaults": {"user_id", "agent_id", "app_id", "run_id"}, "platform": {"api_key", "base_url"}}`. The API key is redacted and unset defaults are `null`.
**Examples:**
```bash
mem0 config show
@@ -392,9 +459,9 @@ Get a single configuration value.
|------|------|----------|-------------|
| `key` | string | Yes | Dotted config key (e.g. `platform.api_key`, `defaults.user_id`). |
**Valid keys:** `platform.api_key`, `platform.base_url`, `defaults.user_id`, `defaults.agent_id`, `defaults.app_id`, `defaults.run_id`.
**Valid keys:** `platform.api_key`, `platform.base_url`, `platform.user_email`, `defaults.user_id`, `defaults.agent_id`, `defaults.app_id`, `defaults.run_id`, plus the short forms `api_key`, `base_url`, `user_email`, `user_id`, `agent_id`, `app_id`, `run_id`. Python also resolves any other field path in the config file (e.g. `platform.agent_mode`); Node does not.
API key values are always redacted in output.
An unknown key prints "Unknown config key: <key>" and still exits 0. API key values are always redacted in output. `config get` and `config set` emit a `{key, value}` envelope only in `--json`/`--agent` mode.
**Examples:**
```bash
@@ -427,19 +494,6 @@ mem0 config set platform.base_url https://api.mem0.ai
---
### `mem0 config clear`
Clear the configuration file. Removes `~/.mem0/config.json`.
**Usage:** `mem0 config clear`
**Examples:**
```bash
mem0 config clear
```
---
### `mem0 entity list`
List all entities of a given type.
@@ -507,9 +561,9 @@ List recent background processing events.
| Flag | Type | Default | Description |
|------|------|---------|-------------|
| `-o, --output <fmt>` | string | `table` | Output format: `text` (table), `json`. |
| `-o, --output <fmt>` | string | `table` | Output format: `table`, `json`. |
**Behavior:** Fetches all events for the project. Displays a table with columns: Event ID (first 8 chars), Type, Status (color-coded), Latency, Created. Status values: `PENDING`, `SUCCEEDED`, `FAILED`, `PROCESSING`.
**Behavior:** Fetches all events for the project. Displays a table with columns: Event ID (first 8 chars), Type, Status (color-coded), Latency, Created. Status values: `PENDING`, `RUNNING`, `SUCCEEDED`, `FAILED`.
**Examples:**
```bash
@@ -585,16 +639,15 @@ mem0 status -o json
## Agent Mode Envelope Format
When `--json` or `--agent` is passed, every command wraps its output in a consistent JSON envelope on stdout:
When `--json` or `--agent` is passed, data commands (add, search, list, get, update, delete, import, config, entity, event, status) wrap their output in a consistent JSON envelope on stdout:
```json
{
"status": "success",
"command": "<command_name>",
"duration_ms": 245,
"scope": { "user_id": "alice", "agent_id": null },
"scope": { "user_id": "alice" },
"count": 10,
"error": null,
"data": { ... }
}
```
@@ -603,38 +656,47 @@ When `--json` or `--agent` is passed, every command wraps its output in a consis
- `status`: `"success"` or `"error"`.
- `command`: The command name (e.g. `"search"`, `"add"`, `"list"`).
- `duration_ms`: Elapsed time in milliseconds (optional).
- `scope`: Active entity scope, omitted if empty (optional).
- `scope`: Active entity scope with empty values dropped, omitted if empty (optional).
- `count`: Number of results, where applicable (optional).
- `error`: Error message string, or `null` on success.
- `data`: Command-specific response data, or `null` on error.
- `data`: Command-specific response data (`null` on error).
- `error`: Present only on error envelopes (see below); success envelopes have no `error` key.
- `mem0_notice`: Present when the platform flags an unclaimed Agent Mode account (optional).
**Sanitized data fields per command in agent mode:**
| Command | `data` shape |
|---------|-------------|
| `add` | `[{id, memory, event}]` or `[{status, event_id}]` for PENDING |
| `search` | `[{id, memory, score, created_at, categories}]` |
| `list` | `[{id, memory, created_at, categories}]` |
| `get` | `{id, memory, created_at, updated_at, categories, metadata}` |
| `update` | `{id, memory}` |
| `delete` | Raw API response |
| `entity list` | `[{name, type, count}]` |
| `add` | `[{id, event}]` for synchronous results (`--no-infer`) or `[{status, event_id}]` for PENDING (default) |
| `search` | `[{id, memory, score, created_at, categories, expiration_date}]` |
| `list` | `[{id, memory, created_at, categories, expiration_date}]` |
| `get` | `{id, memory, created_at, updated_at, categories, metadata, expiration_date}` |
| `update` | `{id, memory, expiration_date}` |
| `delete` (single) | `{id, deleted}` |
| `delete --all` | `{deleted}`, with `scope` in the envelope (Node: raw API result) |
| `delete --all --project` | `{deleted, scope: "project"}` (Node: raw API result) |
| `delete --entity` / `entity delete` | `{deleted}` |
| `entity list` | `[{name, type}]` |
| `event list` | `[{id, event_type, status, latency, created_at}]` |
| `event status` | `{id, event_type, status, latency, created_at, updated_at, results}` |
| `status` | `{connected, backend, base_url}` |
| `config show` | Config object (keys redacted) |
| `import` | `{added, failed, duration_s}` |
| `config show` | `{defaults: {user_id, agent_id, app_id, run_id}, platform: {api_key, base_url}}` (key redacted) |
| `config get` / `config set` | `{key, value}` (agent mode only) |
| `import` | `{added, failed}` |
**`-o json` without agent mode:** `list`, `status`, `import` and `config show` print the same envelope. `add`, `search`, `get`, `update`, `delete` and `entity delete` print the raw API JSON instead. `entity list`, `event list` and `event status` print the envelope in Node and raw JSON in Python.
**Error envelope:**
```json
{
"status": "error",
"command": "search",
"error": "Authentication failed. Your API key may be invalid or expired.",
"error": "Invalid or expired API key.",
"data": null
}
```
The `error` text varies by CLI and failure point (for example a 401 after the upfront key check passes returns "Authentication failed. Your API key may be invalid or expired."). Branch on `status`, not on the message.
---
## Entity ID Resolution
@@ -652,19 +714,20 @@ else:
use all configured defaults
```
This applies to commands with `resolveIds: true`: `add`, `search`, `list`, `delete`, `import`.
This applies to `add`, `search`, `list`, `delete --all`, and `import` (user and agent IDs only). Single `delete` and `entity delete` use only explicitly passed flags.
---
## Filter Building
For `search` and `list`, entity IDs and additional filters are composed into the API filter structure:
For `search` and `list`, entity IDs and additional filters are composed into the API filter structure. `--filter` exists only on `search`; `list` builds its extra filters from `--category`, `--after` and `--before`.
1. If the user provides a pre-built filter via `--filter` containing `AND` or `OR` keys, it is passed through to the API as-is.
1. If the user provides a pre-built filter via `--filter` containing `AND` or `OR` keys, it is passed through to the API as-is (entity IDs, including config defaults, are not merged in).
2. Otherwise, the CLI builds an array of AND conditions:
- Each entity ID becomes a condition: `{"user_id": "alice"}`, etc.
- Category filters: `{"categories": {"contains": "<category>"}}`.
- Date filters: `{"created_at": {"gte": "YYYY-MM-DD"}}` and/or `{"created_at": {"lte": "YYYY-MM-DD"}}`.
- A `--filter` without `AND`/`OR` contributes each of its top-level keys as a condition.
- Category filters (`list`): `{"categories": {"contains": "<category>"}}`.
- Date filters (`list`): one condition `{"created_at": {"gte": "YYYY-MM-DD", "lte": "YYYY-MM-DD"}}`, with only the bounds you passed.
3. If exactly 1 condition: sent as a single object (no wrapping).
4. If 2+ conditions: wrapped as `{"AND": [condition1, condition2, ...]}`.
5. If 0 conditions: no filter sent.
@@ -687,8 +750,8 @@ For `search` and `list`, entity IDs and additional filters are composed into the
| `config set` | msg | - | - | - | msg |
| `entity list` | - | Y | Y | - | `table` |
| `entity delete` | Y | Y | - | Y | `text` |
| `event list` | Y (table) | Y | - | - | `table` |
| `event list` | - | Y | Y | - | `table` |
| `event status` | Y | Y | - | - | `text` |
| `status` | Y | Y | - | - | `text` |
All commands additionally support agent mode (`--json`/`--agent`) which overrides the output format with the JSON envelope.
Agent mode (`--json`/`--agent`) overrides the output format with the JSON envelope for all data commands above (it applies to `config get` and `config set` too). See the envelope section for how `-o json` differs from agent mode.
+54 -26
View File
@@ -13,6 +13,8 @@ Everything about configuring the mem0 CLI: config file format, environment varia
The restricted permissions ensure API keys are not world-readable.
Saving an API key also updates an existing `env.MEM0_API_KEY` entry in `~/.claude/settings.json` and existing `export MEM0_API_KEY=...` lines in `~/.zshrc`, `~/.bashrc`, and `~/.bash_profile`. It never creates new entries.
---
## Config File Schema
@@ -28,7 +30,19 @@ The restricted permissions ensure API keys are not world-readable.
},
"platform": {
"api_key": "",
"base_url": "https://api.mem0.ai"
"base_url": "https://api.mem0.ai",
"user_email": "",
"agent_mode": false,
"created_via": "",
"agent_caller": "",
"claimed_at": "",
"default_user_id": ""
},
"telemetry": {
"anonymous_id": ""
},
"agent_rush": {
"acknowledged_at": ""
}
}
```
@@ -44,12 +58,20 @@ The restricted permissions ensure API keys are not world-readable.
| `defaults.run_id` | string | `""` | Default run ID for scoping commands. |
| `platform.api_key` | string | `""` | API key for the Mem0 Platform. |
| `platform.base_url` | string | `"https://api.mem0.ai"` | Base URL for API requests. |
| `platform.user_email` | string | `""` | Email used for login or claim. Also cached from the ping response during `init`. |
| `platform.agent_mode` | boolean | `false` | `true` while the key is an unclaimed Agent Mode key. |
| `platform.created_via` | string | `""` | How the key was obtained: `agent_mode`, `email`, `api_key`, or `existing_key`. |
| `platform.agent_caller` | string | `""` | Agent name set by `--agent-caller` or `mem0 identify`. |
| `platform.claimed_at` | string | `""` | ISO timestamp once an Agent Mode account is claimed. |
| `platform.default_user_id` | string | `""` | `user_<slug>` returned by the Agent Mode bootstrap. Printed by `mem0 whoami`. |
| `telemetry.anonymous_id` | string | `""` | Persistent anonymous ID used for telemetry. |
| `agent_rush.acknowledged_at` | string | `""` | ISO timestamp when the `agent-rush` public-memory warning was acknowledged. |
---
## `mem0 init` Wizard
The `init` command provides two authentication flows:
The `init` command provides three authentication flows: API key, email login, and Agent Mode (see [Agent Mode Init](#agent-mode-init)).
### API Key Flow (default)
@@ -65,17 +87,17 @@ mem0 init --api-key m0-xxx --user-id alice
1. Displays the mem0 banner.
2. Checks for existing config. If found with an API key, asks for confirmation to overwrite.
3. Prompts for API key (input masked with `*` characters; supports backspace and Ctrl+U to clear).
4. Prompts for default user ID (default value: `mem0-cli`).
5. Validates the connection by calling the status endpoint.
6. Saves config to `~/.mem0/config.json` with `0600` permissions.
7. Prints success message.
3. Asks how to authenticate (`1` email login, recommended; `2` enter an API key) unless `--api-key` was passed.
4. For option 2, prompts for the API key (input masked with `*` characters; supports backspace and Ctrl+U to clear).
5. Prompts for default user ID (default value: `$USER`, then `$USERNAME`, then `mem0-cli`) unless `--user-id` was passed.
6. Validates the connection by calling the status endpoint. A failed check prints an error but the config is still saved.
7. Saves config to `~/.mem0/config.json` with `0600` permissions.
8. Prints success message.
**Non-interactive mode:** When both `--api-key` and `--user-id` are provided, skips all prompts and saves directly. When running in a non-TTY without both flags, prints an error:
**Non-interactive mode:** When both `--api-key` and `--user-id` are provided, skips all prompts and saves directly. In a non-TTY, `--api-key` alone is enough: the user ID falls back to `$USER`, then `$USERNAME`, then `mem0-cli`. A non-TTY without `--api-key` (and without `--email` or an Agent Mode signal) prints an error:
```
Non-interactive terminal detected and missing required flags.
Usage: mem0 init --api-key <key> --user-id <id>
Non-interactive terminal detected and --api-key is required.
```
### Email Login Flow
@@ -92,14 +114,23 @@ mem0 init --email alice@company.com --code 482901
1. Sends a 6-digit verification code to the email via `POST /api/v1/auth/email_code/`.
2. If `--code` is provided, verifies immediately. Otherwise prompts for the code.
3. On success: receives API key, org_id, and project_id from the server.
4. Saves to config. Creates a new account if the email is not registered.
3. On success: receives the API key from the server and saves it with `platform.user_email` and `platform.created_via: "email"`. The default user ID is `--user-id`, else `$USER`, else `mem0-cli`.
Cannot be combined with `--api-key`.
Without `--code` in a non-TTY, `init` sends the code and exits with an error telling you to rerun with `--code`. `--code` requires `--email`, and `--email` cannot be combined with `--api-key`.
**Claiming an Agent Mode account:** If the existing config is an unclaimed Agent Mode key (`platform.agent_mode: true`), `mem0 init --email <email> [--code <code>]` claims that key for the email instead of minting a new one. The API key stays the same.
### Agent Mode Init
```bash
mem0 init --agent --agent-caller <name> --json
```
With `init --agent`, `init --json`, or an agent environment variable (such as `CLAUDECODE`, `CURSOR_AGENT`, `CODEX_CLI`, `CLINE`, `AIDER_SESSION`, `GOOSE_AGENT`, `WINDSURF_AGENT`) and no `--api-key` or `--email`, `init` first reuses a valid `MEM0_API_KEY` or a valid key already in the config. Only when there is no valid key does it mint a new Agent Mode key via `POST /api/v1/auth/agent_mode/`, which is limited to 5 signups per day per network. See [command-reference.md](command-reference.md) for the `init` flags and `identify`.
### Force Overwrite
If `~/.mem0/config.json` already exists with an API key, `mem0 init` warns and asks for confirmation. Use `--force` to skip:
If `~/.mem0/config.json` already exists with an API key, `mem0 init` warns and asks for confirmation (in a non-TTY it exits 1 with "Existing config would be overwritten." instead). Use `--force` to skip:
```bash
mem0 init --api-key m0-new-key --user-id alice --force
@@ -111,7 +142,7 @@ mem0 init --api-key m0-new-key --user-id alice --force
### `mem0 config show`
Displays the current configuration as a formatted table (text mode) or JSON envelope (json mode). API keys are always redacted.
Displays `defaults.*`, `platform.api_key` (redacted), and `platform.base_url` as a formatted table (text mode) or JSON envelope (json mode). Other `platform.*` fields are not shown.
```bash
mem0 config show
@@ -120,7 +151,7 @@ mem0 config show -o json
### `mem0 config get <key>`
Reads a single configuration value. The key uses dotted notation.
Reads a single configuration value. The key uses dotted notation or a short alias (`api_key`, `base_url`, `user_email`, `user_id`, `agent_id`, `app_id`, `run_id`).
```bash
mem0 config get platform.api_key # prints: m0-x...xxxx (redacted)
@@ -130,12 +161,13 @@ mem0 config get defaults.user_id # prints: alice
**Valid keys:**
- `platform.api_key`
- `platform.base_url`
- `platform.user_email`
- `defaults.user_id`
- `defaults.agent_id`
- `defaults.app_id`
- `defaults.run_id`
Unknown keys print an error message.
Python also resolves any other field path in the config (for example `platform.created_via`); Node accepts only the keys above and their short aliases. Unknown keys print "Unknown config key: <key>" and still exit 0, so check the output rather than the exit code. In `--json`/`--agent` mode `config get` and `config set` return `{"key": ..., "value": ...}` in the envelope.
### `mem0 config set <key> <value>`
@@ -148,17 +180,9 @@ mem0 config set platform.base_url https://api.mem0.ai
**Type coercion:**
- Boolean fields accept `true`, `1`, `yes` (case-insensitive) as true. Anything else is false.
- Integer fields are parsed with `parseInt`.
- Integer fields are parsed as integers (Node `parseInt`, Python `int`; Python rejects a non-numeric value as an unknown key).
- String fields are stored as-is.
### `mem0 config clear`
Removes the config file (`~/.mem0/config.json`).
```bash
mem0 config clear
```
---
## Environment Variables
@@ -173,6 +197,7 @@ Environment variables override config file values but are overridden by CLI flag
| `MEM0_AGENT_ID` | `defaults.agent_id` | string | `""` |
| `MEM0_APP_ID` | `defaults.app_id` | string | `""` |
| `MEM0_RUN_ID` | `defaults.run_id` | string | `""` |
| `MEM0_TELEMETRY` | n/a | string | enabled. Set to `false` to disable anonymous telemetry. |
---
@@ -218,7 +243,10 @@ The `config get` and `config set` commands use dotted key paths. Here is the ful
|------------|---------|-------|
| `platform.api_key` | platform | api_key |
| `platform.base_url` | platform | base_url |
| `platform.user_email` | platform | user_email |
| `defaults.user_id` | defaults | user_id |
| `defaults.agent_id` | defaults | agent_id |
| `defaults.app_id` | defaults | app_id |
| `defaults.run_id` | defaults | run_id |
Short aliases (`api_key`, `base_url`, `user_email`, `user_id`, `agent_id`, `app_id`, `run_id`) map to the same fields. Python additionally resolves any other dotted field path.
+44 -37
View File
@@ -6,11 +6,11 @@ Practical recipes for using the mem0 CLI in scripts, pipelines, and agent loops.
## Piping Content via Stdin
The CLI reads from stdin when no text argument is provided and input is piped (not a TTY). This works with `add`, `search`, and `update`.
The CLI reads from stdin when no text argument is provided and stdin is a pipe or a redirected file. This works with `add`, `search`, and `update`. It never reads stdin in `--json`/`--agent` mode for `add`, and Python also skips it there for `search` and `update`, so pass the text as an argument in agent mode.
**Stdin detection method:**
- Python: `not sys.stdin.isatty()`
- Node: `!process.stdin.isTTY`
- Python: `os.fstat(sys.stdin.fileno())` is a FIFO or a regular file
- Node: `fs.fstatSync(0)` is a FIFO or a regular file
### Add from pipe
@@ -69,7 +69,7 @@ The file should be a JSON array where each item has a `memory`, `text`, or `cont
]
```
CLI-provided `--user-id` overrides per-item `user_id` values.
Items may also carry `agent_id`. CLI-provided `--user-id` and `--agent-id` (or configured defaults) override per-item values. A single JSON object is treated as a one-item array, and items without `memory`, `text`, or `content` are counted as failed.
### Import with JSON output
@@ -82,21 +82,24 @@ Output:
{
"status": "success",
"command": "import",
"data": { "added": 42, "failed": 0, "duration_s": 3.14 },
"duration_ms": 3140
"duration_ms": 3140,
"scope": { "user_id": "alice" },
"data": { "added": 42, "failed": 0 }
}
```
This is the Python CLI output. The Node CLI omits `scope` and writes the `Importing memories... n/n` progress line to stdout before the JSON, so its output cannot be piped to `jq`.
---
## Agent Mode for LLM Consumption
Use `--json` or `--agent` to get structured JSON output suitable for LLM tool calling or agent frameworks. Spinners and progress always go to stderr, keeping stdout clean.
Use `--json` or `--agent` to get structured JSON output suitable for LLM tool calling or agent frameworks. Spinners and progress always go to stderr, keeping stdout clean. Put the flag before the subcommand so it works in both the Python and Node CLIs.
### Search with agent mode
```bash
mem0 search "preferences" --user-id alice --agent
mem0 --agent search "preferences" --user-id alice
```
Output (stdout):
@@ -107,7 +110,6 @@ Output (stdout):
"duration_ms": 187,
"scope": { "user_id": "alice" },
"count": 2,
"error": null,
"data": [
{ "id": "mem-abc", "memory": "User prefers dark mode", "score": 0.95, "created_at": "2025-01-15T10:00:00Z", "categories": ["preferences"] },
{ "id": "mem-def", "memory": "User likes monospace fonts", "score": 0.82, "created_at": "2025-01-15T10:01:00Z", "categories": ["preferences"] }
@@ -118,7 +120,7 @@ Output (stdout):
### Add with agent mode
```bash
mem0 add "Uses Python 3.12" --user-id alice --json
mem0 --json add "Uses Python 3.12" --user-id alice
```
### Error handling in agent mode
@@ -126,7 +128,7 @@ mem0 add "Uses Python 3.12" --user-id alice --json
Errors also return valid JSON with `"status": "error"`:
```bash
mem0 search "test" --user-id alice --api-key invalid --agent
mem0 --agent search "test" --user-id alice --api-key invalid
```
Output:
@@ -134,7 +136,7 @@ Output:
{
"status": "error",
"command": "search",
"error": "Authentication failed. Your API key may be invalid or expired.",
"error": "Invalid or expired API key.",
"data": null
}
```
@@ -143,30 +145,30 @@ Output:
## JSON Output + jq
Use `--output json` (or `-o json`) for raw JSON output, then pipe to `jq` for processing.
Use `--output json` (or `-o json`) for JSON output, then pipe to `jq` for processing. `search`, `get`, `add`, `update`, and `delete` print the raw API response. `list`, `status`, and `import` print the standard envelope instead, so read the results from `.data`. Piping `import` to `jq` works only with the Python CLI (Node writes its progress line to stdout ahead of the JSON).
### Extract just memory text
```bash
mem0 list --user-id alice --output json | jq '.[] | .memory'
mem0 list --user-id alice --output json | jq '.data[] | .memory'
```
### Get memory IDs
```bash
mem0 list --user-id alice -o json | jq '.[].id'
mem0 list --user-id alice -o json | jq '.data[].id'
```
### Count memories
```bash
mem0 list --user-id alice -o json | jq 'length'
mem0 list --user-id alice -o json | jq '.count'
```
### Filter by category in jq
```bash
mem0 list --user-id alice -o json | jq '[.[] | select(.categories[]? == "preferences")]'
mem0 list --user-id alice -o json | jq '[.data[] | select(.categories[]? == "preferences")]'
```
### Extract search scores
@@ -183,7 +185,7 @@ mem0 search "tools" --user-id alice -o json | jq '.[] | {memory, score}'
```bash
# Get IDs, then delete each one
mem0 list --user-id alice -o json | jq -r '.[].id' | while read id; do
mem0 list --user-id alice -o json | jq -r '.data[].id' | while read id; do
mem0 delete "$id" --force
done
```
@@ -199,7 +201,7 @@ done < memories.txt
### Copy memories between users
```bash
mem0 list --user-id alice -o json | jq -r '.[].memory' | while IFS= read -r mem; do
mem0 list --user-id alice -o json | jq -r '.data[].memory' | while IFS= read -r mem; do
mem0 add "$mem" --user-id bob
done
```
@@ -216,7 +218,7 @@ mem0 list --user-id alice -o json > alice_memories.json
page=1
while true; do
result=$(mem0 list --user-id alice -o json --page "$page" --page-size 100)
count=$(echo "$result" | jq 'length')
count=$(echo "$result" | jq '.count')
if [ "$count" -eq 0 ]; then
break
fi
@@ -271,7 +273,7 @@ mem0 add "CI run started" --user-id ci-bot
```bash
test_summary=$(cat test-results.txt | head -20)
mem0 add "$test_summary" --agent-id ci-bot --metadata '{"type":"test-results"}' --categories "ci,testing"
mem0 add "$test_summary" --agent-id ci-bot --metadata '{"type":"test-results"}'
```
---
@@ -282,11 +284,11 @@ The CLI reads from stdin only when ALL of these conditions are met:
1. No text argument was provided on the command line.
2. For `add`: no `--messages` and no `--file` flag.
3. For `update`: no `--metadata` flag.
4. stdin is piped (not a TTY).
3. stdin is a pipe or a redirected file (a plain non-TTY such as `/dev/null` does not count).
4. The CLI is not in `--json`/`--agent` mode (`add` in both CLIs, `search` and `update` in Python).
**This means:**
- `mem0 add --user-id alice` in an interactive terminal will NOT hang waiting for input. It will print a usage error.
- `mem0 add --user-id alice` in an interactive terminal will NOT hang waiting for input. It exits 1 with "No content provided".
- `echo "text" | mem0 add --user-id alice` will read "text" from stdin.
- `mem0 add "explicit text" --user-id alice` will use the explicit text, even if stdin is piped.
@@ -303,8 +305,7 @@ The CLI reads from stdin only when ALL of these conditions are met:
```bash
set -e # Exit on error
# This will exit the script if the API key is invalid
mem0 status > /dev/null 2>&1
mem0 status -o json | jq -e '.data.connected' > /dev/null
# Add with error check
if mem0 add "test memory" --user-id alice 2>/dev/null; then
@@ -317,10 +318,16 @@ fi
### Capture memory ID from add
Default adds are asynchronous and return an `event_id`. Use `--no-infer` for a synchronous add whose `.data[0].id` is the memory id.
```bash
# Use agent mode to get structured output
result=$(mem0 add "new fact" --user-id alice --agent 2>/dev/null)
memory_id=$(echo "$result" | jq -r '.data[0].id // empty')
event_id=$(mem0 --agent add "new fact" --user-id alice 2>/dev/null | jq -r '.data[0].event_id // empty')
for _ in $(seq 30); do
status=$(mem0 --agent event status "$event_id" | jq -r '.data.status')
[ "$status" = "SUCCEEDED" ] || [ "$status" = "FAILED" ] && break
sleep 2
done
memory_id=$(mem0 --agent event status "$event_id" | jq -r '.data.results[0].id // empty')
if [ -n "$memory_id" ]; then
echo "Created memory: $memory_id"
fi
@@ -330,7 +337,7 @@ fi
```bash
# Only add if search returns no results
count=$(mem0 search "dark mode" --user-id alice --agent 2>/dev/null | jq '.count // 0')
count=$(mem0 --agent search "dark mode" --user-id alice 2>/dev/null | jq '.count // 0')
if [ "$count" -eq 0 ]; then
mem0 add "User prefers dark mode" --user-id alice
fi
@@ -358,7 +365,7 @@ mem0 list
### Timeout handling
The CLI uses a 30-second timeout for all API requests. For long-running scripts, handle timeouts:
The CLI uses a 30-second timeout for normal API requests (the key-validation ping uses 5 seconds and `init` uses 5, 10 and 30 seconds). For long-running scripts, handle timeouts:
```bash
if ! mem0 search "query" --user-id alice -o json 2>/dev/null; then
@@ -382,13 +389,13 @@ Or use the event system to poll for completion:
```bash
# Add and capture event ID from agent output
result=$(mem0 add "new preference" --user-id alice --agent 2>/dev/null)
result=$(mem0 --agent add "new preference" --user-id alice 2>/dev/null)
event_id=$(echo "$result" | jq -r '.data[0].event_id // empty')
if [ -n "$event_id" ]; then
# Poll until processing completes
while true; do
status=$(mem0 event status "$event_id" --agent 2>/dev/null | jq -r '.data.status')
status=$(mem0 --agent event status "$event_id" 2>/dev/null | jq -r '.data.status')
if [ "$status" = "SUCCEEDED" ] || [ "$status" = "FAILED" ]; then
break
fi
@@ -413,16 +420,16 @@ shift 2
case "$ACTION" in
recall)
mem0 search "$*" --user-id "$USER_ID" --agent 2>/dev/null
mem0 --agent search "$*" --user-id "$USER_ID" 2>/dev/null
;;
remember)
mem0 add "$*" --user-id "$USER_ID" --agent 2>/dev/null
mem0 --agent add "$*" --user-id "$USER_ID" 2>/dev/null
;;
forget)
mem0 delete --all --user-id "$USER_ID" --force --agent 2>/dev/null
mem0 --agent delete --all --user-id "$USER_ID" --force 2>/dev/null
;;
history)
mem0 list --user-id "$USER_ID" --agent 2>/dev/null
mem0 --agent list --user-id "$USER_ID" 2>/dev/null
;;
*)
echo '{"status":"error","error":"Unknown action: '"$ACTION"'"}' >&2
+4 -4
View File
@@ -15,7 +15,7 @@ description: >
license: Apache-2.0
metadata:
author: mem0ai
version: "0.1.0"
version: "0.1.1"
category: ai-memory
tags: "memory, integration, tdd, platform, oss"
mem0_tested_versions: "mem0ai (PyPI) >=2.0.0,<3.0.0; mem0ai (npm) >=3.0.0,<4.0.0"
@@ -36,7 +36,7 @@ of the Mem0 API.
- Scope-tagged docs index: https://docs.mem0.ai/llms.txt
- Full docs (single file, deep dives): https://docs.mem0.ai/llms-full.txt
- OpenAPI spec (Platform REST, machine-readable): https://docs.mem0.ai/openapi.json
- Hosted MCP server: https://mcp.mem0.ai (requires Platform API key)
- Hosted MCP server: https://mcp.mem0.ai/mcp (requires Platform API key)
- Integrations index: https://docs.mem0.ai/integrations
### Published Mem0 skills — delegate; do not reimplement
@@ -109,7 +109,7 @@ the target stack. If yes, delegate — copy its call-site pattern into
|---|---|---|
| `@ai-sdk/*` + `ai` in `package.json` | `skills/mem0-vercel-ai-sdk` | Integration is via `createMem0` provider wrapper, not raw `MemoryClient`. |
| CLI-only repo (Typer, Commander, Click, Cobra) with no LLM call sites | `skills/mem0-cli` | Call sites are command handlers, not model wrappers. Consider whether mem0 actually fits first. |
| Target is an MCP client / editor config (Claude Code, Cursor, Codex settings) | `integrations/mem0-agent-plugin` | Wire via MCP server URL + hooks; no SDK code usually needed. |
| Target is an MCP client / editor config (Claude Code, Cursor, Codex settings) | `integrations/mem0-agent-plugin` | Local stdio MCP server (one search tool) plus skills, no hooks or automatic capture; for a hosted endpoint use `https://mcp.mem0.ai/mcp`. No SDK code usually needed. |
| Any other Python or TS repo with an LLM call site | `skills/mem0` | Default SDK integration path. |
Record the delegated skill's raw URL in `plan.md` under a
@@ -168,7 +168,7 @@ start executing a step; the summary below is only for routing.
| `trace.jsonl` | Every tool call, decision, and subagent exchange this run. | Overwritten per run. |
| `diff.patch` | The committed integration as a reviewable patch. | Overwritten per run. |
| `heal-trace.md` | Per-attempt record of the self-healing loop (step 10). | Overwritten per run. |
| `product.json` | `{"product": "platform"\|"oss", "language": "...", "mem0_version": "...", "write_site": "file:line", "read_site": "file:line", "feature_flag": "MEM0_ENABLED"}` — consumed by the verification skill. | Overwritten per run. |
| `product.json` | `{"product": "platform"\|"oss", "language": "...", "mem0_version": "...", "write_site": "file:line", "read_site": "file:line", "feature_flag": "MEM0_ENABLED", "preferred_site": "<surface index from step 2>"}`, consumed by the verification skill. | Overwritten per run. |
`.mem0-integration/` is added to `.gitignore` on first run. Nothing is
written outside this directory and the repo's source tree.
+12 -7
View File
@@ -116,8 +116,11 @@ Present in env, continue.
Mode**: run `mem0 init --agent --agent-caller <your-name> --json` (after
`pip install mem0-cli` or `npm install -g @mem0/cli`), substituting your agent
identity such as `claude-code`, `cursor`, `codex`. If you forgot
`--agent-caller`, run `mem0 identify <your-name>` after init. Cache the key to
`.env` with user consent and continue. Tell the user to claim it later with
`--agent-caller`, run `mem0 identify <your-name>` after init. Init does not
print the key: it saves it to `~/.mem0/config.json` (`platform.api_key`, file
mode 0600) and reuses a valid key already there instead of minting a new one,
so read the key from that file. Cache the key to `.env` with user consent and
continue. Tell the user to claim it later with
`mem0 init --email <their-email>`: same key, no agent disruption.
Missing and **CI mode** (`MEM0_INTEGRATE_CI=1`), exit code 2 with the name of
@@ -184,8 +187,8 @@ Then write `.mem0-integration/plan.md`:
call client.add([user_msg, assistant_msg], user_id=<source>).">
**Read pattern:** <one sentence, e.g. "Before building the LLM prompt,
call client.search(query=latest_user_msg, user_id=<source>, limit=5)
and inject results as a system message.">
call client.search(latest_user_msg, filters={"user_id": <source>},
top_k=5) and inject results["results"] as a system message.">
**User identifier source:** <code path, e.g. `req.auth.userId`,
`session.user.email`, `ctx.params.user_id`. If none, ask the user.>
@@ -276,7 +279,8 @@ test framework:
Test assertion shapes must match the **canonical signatures**:
- Platform method signatures: `https://docs.mem0.ai/openapi.json`, the request
body schemas for `/v1/memories/` and `/v1/memories/search/`.
body schemas for `/v3/memories/add/` and `/v3/memories/search/` (the
`/v1/memories/` POST and `/v1/memories/search/` paths are deprecated).
- OSS method signatures: the delegated skill named in `plan.md` (fetched from
its raw URL), or `skills/mem0/SKILL.md` as the default.
- Do not hand-roll request shapes. If the delegated skill has an example
@@ -341,7 +345,8 @@ Otherwise loop:
1. **Categorize the failing check** from `scorecard.json` and route:
- `install` / `static_checks`, dependency or import fix.
- `unit_tests`, wiring or assertion fix.
- `unit_tests_flag_on`, wiring or assertion fix. (`unit_tests_flag_off`
failing is the non-invasiveness case below.)
- `smoke_test`, API key or SDK call-shape fix.
- `e2e_test`, recipe, flag-wiring, or integration-point fix.
- **Pre-existing test failure** (test skill exit code 7,
@@ -351,7 +356,7 @@ Otherwise loop:
2. **Spawn a remediation subagent** with fresh context. Inputs: `plan.md`,
`goal.md`, `scorecard.md`, `scorecard.json`, the last committed diff, and
the relevant log for the failing category (`test-stdout.log` /
the relevant log for the failing category (`test-stdout-flag-on.log` /
`smoke-stdout.log` / `e2e-app.log` / `e2e-calls.log`). Use the remediation
prompt in [`subagent-prompts.md`](subagent-prompts.md) verbatim.
@@ -31,7 +31,8 @@ so paraphrasing them drops constraints the review step then has to catch.
6. Preserve everything listed under plan.md's "Preserved behavior"
and "Coexistence."
7. Lazy client construction. `MemoryClient()` validates the API
key in `__init__` (it makes a network call). Never instantiate
key in `__init__` (Python pings the API; TS throws on a missing
or blank key and pings in the background). Never instantiate
it at module-import time, construct on first use inside the
request / handler path. The same rule applies to OSS `Memory()`,
which can eagerly initialize embedding and LLM providers. Use
+1 -1
View File
@@ -71,7 +71,7 @@ curl -X POST https://api.anthropic.com/v1/skills \
- [Mem0 Platform Dashboard](https://app.mem0.ai)
- [Mem0 Documentation](https://docs.mem0.ai)
- [OSS → Platform migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3)
- [OSS → Platform migration guide](https://docs.mem0.ai/migration/oss-to-platform)
- [Platform vs OSS comparison](https://docs.mem0.ai/platform/platform-vs-oss)
## License
+36 -24
View File
@@ -1,18 +1,22 @@
---
name: mem0-oss-to-platform
description: >-
Plan and then execute a migration of a project from the mem0 open-source / self-hosted SDK
(the local `Memory` class) to the mem0 Platform / hosted / managed SDK (the `MemoryClient`
class). Use this whenever a developer wants to move, switch, or migrate their mem0 usage off
OSS/self-hosted to the hosted API — e.g. "migrate my mem0 setup to the platform", "switch from
self-hosted mem0 to MemoryClient", "use my mem0 API key instead of a local Qdrant", "move mem0
to the cloud/hosted/managed service", or "replace my local mem0 vector store + embedder config
with the platform". Applies to Python (`from mem0 import Memory` → `from mem0 import MemoryClient`)
and TypeScript/JavaScript (`import { Memory } from "mem0ai/oss"` → `import MemoryClient from "mem0ai"`).
Trigger even when the user doesn't say the word "migrate" but clearly wants their existing mem0
integration to run against the hosted platform. It first produces a reviewable migration plan,
then executes it after the developer approves. Strictly scoped to the mem0 integration — it does
not refactor, restructure, or "improve" any unrelated code.
Plan, then execute, a migration of a project from the mem0 open-source / self-hosted SDK
(local `Memory` class) to the mem0 Platform / hosted SDK (`MemoryClient`). TRIGGER when: the
developer wants to move, switch, or migrate mem0 off OSS/self-hosted to the hosted API, e.g.
"migrate my mem0 setup to the platform", "switch from self-hosted mem0 to MemoryClient", "use my
mem0 API key instead of a local Qdrant", "replace my local vector store + embedder config with
the platform", even without the word "migrate". Covers Python (`Memory` to `MemoryClient`) and
TypeScript (`mem0ai/oss` to `mem0ai`). Produces a reviewable plan, then executes it after
approval. Touches only the mem0 integration. DO NOT TRIGGER when: adding mem0 to a project that
has none (use `mem0-integrate`) or answering SDK usage questions (use `mem0`).
license: Apache-2.0
metadata:
author: mem0ai
version: "0.2.0"
category: ai-memory
tags: "migration, oss, self-hosted, platform, hosted"
mem0_tested_versions: "mem0ai (PyPI) >=2.0.0,<3.0.0; mem0ai (npm) >=3.0.0,<4.0.0"
---
# Migrate mem0 OSS → mem0 Platform (hosted)
@@ -35,7 +39,8 @@ few parameter conventions tighten up and the return values are server responses.
So the core of every migration is:
1. `Memory` / `Memory.from_config({...})` → `MemoryClient()` (reads the API key from the env).
2. Delete the local `vector_store` / `llm` / `embedder` / `graph_store` / `history_db_path` config.
2. Delete the local `vector_store` / `llm` / `embedder` / `reranker` / `history_db_path` config (and any
leftover `graph_store`).
3. Fix up each call site to the hosted call convention (entity IDs into `filters`, pagination, etc.).
4. Flag everything that *isn't* a clean 1:1 so the developer can decide (see `references/gotchas.md`).
@@ -55,10 +60,11 @@ must be set (in `.env` / secrets manager, never hardcoded) before execution and
### Phase 1 — Discover the mem0 footprint
Do not assume the layout. Find every place mem0 appears. Detect the language and the **installed**
version first, then sweep for usage. Concretely, search for:
- **Imports / instantiation:** `from mem0 import Memory`, `Memory.from_config`, `Memory(`,
`import ... from "mem0ai"`, `from "mem0ai/oss"`, `new Memory(`.
- **Config blocks:** keys like `vector_store`/`vectorStore`, `embedder`, `llm`, `graph_store`/
`graphStore`, `history_db_path`, `historyStore`, `custom_fact_extraction_prompt`,
- **Imports / instantiation:** `from mem0 import Memory`, `AsyncMemory`, `Memory.from_config`,
`Memory(`, `import ... from "mem0ai"`, `from "mem0ai/oss"`, `new Memory(`.
- **Config blocks:** keys like `vector_store`/`vectorStore`, `embedder`, `llm`, `reranker`,
`graph_store`/`graphStore`, `history_db_path`/`historyDbPath`, `historyStore`,
`custom_instructions`/`customInstructions`, `custom_fact_extraction_prompt`/`customPrompt`,
`custom_update_memory_prompt`, `enable_graph`.
- **Every call site:** `.add(`, `.search(`, `.get_all(`/`.getAll(`, `.delete_all(`/`.deleteAll(`,
`.get(`, `.update(`, `.delete(`, `.reset(`, `.history(`.
@@ -78,11 +84,14 @@ confirm the **real** signatures of the installed package rather than trusting me
`site-packages/mem0/client/main.py` if anything is ambiguous (e.g. whether a method *rejects*
top-level entity params). Also check the OSS side the project currently uses.
- **TypeScript:** read the installed types/dist under `node_modules/mem0ai/` to confirm option names
(`limit` vs `topK`, `userId` vs a nested `filters`) and the default vs `mem0ai/oss` export.
(`limit` vs `topK`, `userId` vs a nested `filters: { user_id }`) and the default vs `mem0ai/oss`
export.
This verification step is the single most important habit — it's what keeps the plan correct across
mem0 versions. Then consult `references/api-mapping.md` for the OSS→hosted translation of each
method (Python and TypeScript), and the official guide at https://docs.mem0.ai/migration/oss-v2-to-v3.
method (Python and TypeScript). The docs guide is https://docs.mem0.ai/migration/oss-to-platform
(verify its code samples against the installed SDK); OSS upgrade changes are at
https://docs.mem0.ai/migration/oss-v2-to-v3.
### Phase 3 — Map each site and flag the gaps
For every call site and config block from Phase 1, determine the hosted equivalent using the
@@ -90,7 +99,8 @@ mapping. Most calls map cleanly. Some don't — and those matter more than the m
Read `references/gotchas.md` and flag anything that needs a human decision: self-hosted/data-
residency setups, local model choices moving server-side, graph-memory usage, custom prompts, hot-
path calls that now make network round-trips, and **existing locally-stored memories not carrying
over** (data migration is out of scope unless the developer asks — note it, don't silently attempt it).
over** (data migration is out of scope unless the developer asks: note it, don't silently attempt it;
if they do ask, see `references/gotchas.md` #1, which covers the hosted-Qdrant migration script).
### Phase 4 — Write the plan and stop
Write the full plan to `MEM0_MIGRATION_PLAN.md` at the repo root, following the structure in
@@ -106,11 +116,13 @@ Once the developer approves (they may ask for changes first — incorporate them
- **Verify**, mirroring how you'd confirm any backend swap:
- It imports / type-checks / byte-compiles.
- A smoke test exercises `add` → `search`/`get_all` → `delete_all` against the hosted API with a
real `MEM0_API_KEY`, and the app's own entry point still runs.
- No local mem0 storage directory gets created anymore (e.g. a `.mem0/`, local Qdrant path) —
proof the memory really lives on the platform now.
real `MEM0_API_KEY`, and the app's own entry point still runs. Hosted `add` is asynchronous, so
allow a short wait before expecting the fact to show up in `search`/`get_all`.
- No local OSS storage gets created anymore (e.g. `~/.mem0/history.db`, a local Qdrant path). The
client itself still creates `~/.mem0/config.json`, so don't use the bare `.mem0/` directory as
the check.
- Report what changed, what was verified, and any flagged concerns the developer still needs to act
on (e.g. configuring custom instructions in the dashboard, migrating old data).
on (e.g. re-applying custom instructions with `project.update`, migrating old data).
## Reference files
- `references/api-mapping.md` — exact OSS→hosted method/param/return mapping for Python and
@@ -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.
+31 -13
View File
@@ -18,7 +18,7 @@ description: >
license: Apache-2.0
metadata:
author: mem0ai
version: "0.1.0"
version: "0.1.1"
category: ai-memory
tags: "memory, integration, testing, tdd, platform, oss"
coupling: loose
@@ -130,7 +130,8 @@ if dependencies don't resolve.
- **Eager-init check**: grep the `write_site` and `read_site` files (paths
from `product.json`) for `MemoryClient(` or `Memory(` at module scope —
i.e., not inside a function, method, or class body. `MemoryClient()`
validates the API key in `__init__` (network call) and OSS `Memory()`
validates the API key in `__init__` (Python pings the API, TS throws on a
blank key and pings in the background) and OSS `Memory()`
can eagerly initialize embedding/LLM providers — module-level
instantiation hits the wire on import and breaks Pass A's test
collection whenever the key is unset. Hit → fail with `file:line` and
@@ -140,7 +141,7 @@ if dependencies don't resolve.
| Language | Test command (in priority order) |
|---|---|
| Python | `pytest` with the test files from step 5 of the companion skill, else `python -m unittest discover`. |
| Python | `pytest` with the test files from step 7 of the companion skill, else `python -m unittest discover`. |
| TypeScript / JavaScript | `npm test` if defined in package.json; else auto-detect `vitest` or `jest`. |
**Pass A — `feature_flag` unset.** Run the *entire* pre-existing suite
@@ -171,26 +172,43 @@ shape for the detected stack.
**Platform (Python):**
import os
import time
from mem0 import MemoryClient
c = MemoryClient() # uses MEM0_API_KEY
uid = f"mem0-test-integration-{os.urandom(4).hex()}"
c.add([{"role": "user", "content": "I prefer aisle seats"}], user_id=uid)
hits = c.search("seat preference", user_id=uid)
for _ in range(10):
hits = c.search("seat preference", filters={"user_id": uid})["results"]
if hits:
break
time.sleep(2)
assert any("aisle" in h.get("memory", "") for h in hits), hits
c.delete_all(user_id=uid) # clean up
**Platform (TS):** same shape with `MemoryClient` from `"mem0ai"`.
`add` is asynchronous on Platform (it returns `status: "PENDING"` with an
`event_id`), so the search is retried for a bounded time. Entity IDs go in
`filters` and results come back under `["results"]`.
**Platform (TS):** same shape with `new MemoryClient({ apiKey:
process.env.MEM0_API_KEY })` from `"mem0ai"` (the TS client does not read the
env var itself), `client.search("seat preference", { filters: { user_id: uid
} })` with hits under `.results`, and `client.deleteAll({ userId: uid })`.
**OSS (Python / TS):** uses `Memory()` / `new Memory()` with default config
(OpenAI LLM via `OPENAI_API_KEY`, local Qdrant). If the repo ships a
`docker-compose.yml` with a Qdrant service, the skill starts it first and
tears it down after. If no backing store is reachable → fail with a
(OpenAI LLM via `OPENAI_API_KEY`; Python defaults to an embedded on-disk
Qdrant, TS to an in-memory vector store, so no service is needed unless the
repo's own config points at one). If the repo ships a `docker-compose.yml`
with the configured vector store service, the skill starts it first and tears
it down after. If the configured backing store is not reachable → fail with a
clear message naming the fix.
The smoke test always uses a **disposable random user_id** prefixed with
`mem0-test-integration-` so a failed cleanup doesn't pollute the user's
real data. A background tidy step deletes any prefix-matching entries
older than 24 hours on the next run.
real data. A background tidy step lists Platform entities with `client.users()`,
picks the `type: "user"` entries whose `name` starts with that prefix and whose
`created_at` is older than 24 hours, and calls `delete_all(user_id=...)` for
each on the next run (there is no server-side prefix delete).
Capture output to `.mem0-integration/smoke-stdout.log`.
@@ -201,7 +219,7 @@ real signal: **does memory actually appear in the app's user-visible
output when the integration runs end-to-end?**
Requires `plan.md` to contain an `E2E recipe:` section (authored by
`/mem0-integrate` step 5). If absent → status `skipped` (not `fail`),
`/mem0-integrate` step 6). If absent → status `skipped` (not `fail`),
note in scorecard that the repo has no runnable entry point.
Recipe fields the skill reads:
@@ -238,8 +256,8 @@ Execution order:
8. Run `read_call`.
9. Evaluate `read_assert` against `read_call`'s stdout. Miss → fail.
10. Cleanup (always, even on failure): SIGTERM the app, SIGKILL after
5s, `docker compose down` if services were started, `delete_all`
memories matching `mem0-test-integration-*` on Platform scenarios.
5s, `docker compose down` if services were started, `delete_all` for
the disposable `MEM0_USER_ID` on Platform scenarios.
On any failure, the scorecard includes:
+3 -3
View File
@@ -37,7 +37,7 @@ curl -X POST https://api.anthropic.com/v1/skills \
### Prerequisites
- **Node.js 18+**
- **Vercel AI SDK v5** (`ai` package version 5.x)
- **Vercel AI SDK v6** (`ai` package version 6.x)
- A Mem0 Platform API key ([Get one here](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=skill-mem0-vercel-ai-sdk-readme))
- An LLM provider API key (OpenAI, Anthropic, Google, Groq, or Cohere)
- Set environment variables:
@@ -54,7 +54,7 @@ After installing, just ask Claude:
- "Add memory to my Vercel AI SDK app"
- "Set up mem0 with streamText in my Next.js API route"
- "Use retrieveMemories with Anthropic instead of the wrapped model"
- "Show me how to use graph memories with the Vercel AI provider"
- "Show me how to read the Mem0 source from generateText with the Vercel AI provider"
- "Help me store conversation history with addMemories"
## What's Inside
@@ -67,7 +67,7 @@ skills/mem0-vercel-ai-sdk/
└── references/ # Documentation (loaded on demand)
├── provider-api.md # createMem0, Mem0Provider, types, config
├── memory-utilities.md # addMemories, retrieveMemories, getMemories, searchMemories
└── usage-patterns.md # Working examples: streaming, Next.js, multi-provider, graph
└── usage-patterns.md # Working examples: streaming, Next.js, multi-provider
```
## Links
+38 -20
View File
@@ -12,10 +12,11 @@ description: >
license: Apache-2.0
metadata:
author: mem0ai
version: "1.1.0"
version: "2.0.0"
category: ai-memory
tags: "vercel, ai-sdk, memory, nextjs, typescript, provider"
compatibility: Node.js 18+, npm install @mem0/vercel-ai-provider, Vercel AI SDK v5 (ai package), MEM0_API_KEY + LLM provider API key
mem0_tested_versions: "@mem0/vercel-ai-provider (npm) >=3.0.0,<4.0.0; ai (npm) >=6.0.0,<7.0.0; mem0ai (npm) >=3.0.0,<4.0.0"
compatibility: Node.js 18+, npm install @mem0/vercel-ai-provider, Vercel AI SDK v6 (ai package ^6), MEM0_API_KEY + LLM provider API key
---
# Mem0 Vercel AI SDK Provider
@@ -25,9 +26,11 @@ Memory-enhanced AI provider for Vercel AI SDK. Automatically retrieves and store
## Step 1: Install
```bash
npm install @mem0/vercel-ai-provider ai
npm install @mem0/vercel-ai-provider ai@^6
```
`ai` and the `@ai-sdk/*` provider packages ship as regular dependencies of `@mem0/vercel-ai-provider`, so nothing else needs installing. The only peer dependency is `zod` (optional, `^3.0.0`).
## Step 2: Set up environment variables
```bash
@@ -53,10 +56,23 @@ const { text } = await generateText({
```
What happens under the hood:
1. The prompt is sent to Mem0 search (`POST /v3/memories/search/`) to retrieve relevant memories
2. Retrieved memories are injected as a system message at the start of the prompt
3. The underlying LLM (e.g., OpenAI gpt-5-mini) generates a response using the enriched prompt
4. The conversation is stored back to Mem0 (`POST /v3/memories/add/`) as a fire-and-forget async call (no await)
1. The prompt is stored to Mem0 (`POST /v3/memories/add/`), awaited before the LLM call (a failed write is logged and ignored)
2. The prompt is sent to Mem0 search (`POST /v3/memories/search/`) to retrieve relevant memories
3. If any memories are found, they are injected as a system message at the start of the prompt
4. The underlying LLM (e.g., OpenAI gpt-5-mini) generates a response using the enriched prompt
For `generateText`, the retrieved memories are also attached to the result as a source:
```typescript
const { text, sources } = await generateText({
model: mem0("gpt-5-mini", { user_id: "alice" }),
prompt: "Recommend a restaurant",
});
console.log(sources.find((s) => s.title === "Mem0 Memories")?.providerMetadata?.mem0);
```
The source has `title: "Mem0 Memories"` and `providerMetadata.mem0` holds `memories` (array of memory objects) and `memoriesText`. It is only present when at least one memory was retrieved.
## Pattern 2: Standalone Utilities
@@ -111,7 +127,7 @@ for await (const chunk of result.textStream) {
}
```
The wrapped model handles memory retrieval before streaming begins and stores the conversation after.
The wrapped model stores the conversation and retrieves memories before streaming begins.
## Supported Providers
@@ -119,7 +135,7 @@ The wrapped model handles memory retrieval before streaming begins and stores th
|----------|-------------|------------------|
| OpenAI (default) | `"openai"` | `OPENAI_API_KEY` |
| Anthropic | `"anthropic"` | `ANTHROPIC_API_KEY` |
| Google | `"google"` | `GOOGLE_GENERATIVE_AI_API_KEY` |
| Google | `"google"` (alias `"gemini"`) | `GOOGLE_GENERATIVE_AI_API_KEY` |
| Groq | `"groq"` | `GROQ_API_KEY` |
| Cohere | `"cohere"` | `COHERE_API_KEY` |
@@ -128,7 +144,7 @@ Select a provider when creating the Mem0 instance:
```typescript
const mem0 = createMem0({ provider: "anthropic" });
const { text } = await generateText({
model: mem0("gpt-5-mini", { user_id: "alice" }),
model: mem0("claude-sonnet-4-20250514", { user_id: "alice" }),
prompt: "Hello!",
});
```
@@ -139,11 +155,11 @@ const { text } = await generateText({
```
User prompt
--> searchInternalMemories (POST /v3/memories/search/)
--> memories injected as system message at start of prompt
--> processMemories: addMemories (POST /v3/memories/add/, awaited)
--> processMemories: getMemories (POST /v3/memories/search/)
--> memories (if any) injected as system message at start of prompt
--> underlying LLM generates response (doGenerate or doStream)
--> processMemories fires addMemories as fire-and-forget (no await)
--> response returned to caller
--> response returned to caller (doGenerate also attaches a "Mem0 Memories" source)
```
### Standalone flow
@@ -162,18 +178,20 @@ User controls each step:
|----------|---------|----------|
| `retrieveMemories` | Formatted system prompt **string** | Injecting directly into `system` parameter |
| `getMemories` | Raw memory **array** | Processing memories programmatically |
| `searchMemories` | Full search **response** (results + relations) | Need relations, scores, metadata |
| `searchMemories` | Raw search **response** (as returned by the API) | Need scores and full metadata |
| `addMemories` | API response | Storing new messages to Mem0 |
All four accept `LanguageModelV2Prompt | string` as the first argument and optional `Mem0ConfigSettings` as the second.
`retrieveMemories`, `getMemories`, and `searchMemories` accept `LanguageModelV3Prompt | string` as the first argument; `addMemories` is typed as `LanguageModelV3Prompt` (a string also works at runtime). All four take optional `Mem0ConfigSettings` as the second argument.
## Common Edge Cases and Tips
- **Always provide `user_id`** (or `agent_id`/`app_id`/`run_id`) for consistent memory retrieval. Without an entity identifier, memories cannot be scoped.
- **Always provide `user_id`** (or `agent_id`/`app_id`/`run_id`) for consistent memory retrieval. The search endpoint requires at least one entity ID in `filters`; the provider places these IDs there for you.
- **Standalone utilities require explicit API key**: pass `mem0ApiKey` in the config object, or set the `MEM0_API_KEY` environment variable.
- **This uses Vercel AI SDK v5** (LanguageModelV2 / ProviderV2 interfaces). It is not compatible with AI SDK v3 or v4.
- **`processMemories` fires `addMemories` as fire-and-forget** (`.then()` without `await`). Memory storage happens asynchronously and does not block the LLM response.
- **The `"gemini"` alias** exists in the provider switch but is NOT in the `supportedProviders` list. Use `"google"` instead.
- **This uses Vercel AI SDK v6** (LanguageModelV3 / ProviderV3 interfaces, `@mem0/vercel-ai-provider` 3.x). Provider 2.x targeted AI SDK v5. It is not compatible with AI SDK v4 or earlier.
- **`processMemories` awaits `addMemories`** before searching and calling the LLM, so each wrapped call includes one memory write and one memory search. If either request fails, the error is logged and the LLM call proceeds without memories.
- **`"google"` and `"gemini"`** are both accepted and map to `@ai-sdk/google`.
- **Removed in 3.0.0**: `org_id`, `project_id`, `org_name`, `project_name`, `output_format`, `filter_memories`, `async_mode`, `enable_graph`, `version`, `api_version`. Graph memory is now a Mem0 Platform project setting, not a provider option.
- **Default `top_k` is 10.** `threshold` and `rerank` are only sent when set (the API default for `rerank` is `false`; `threshold` is a server-side cutoff, not a floor on the returned score).
- **Custom host**: set `host` in the config to point to a different Mem0 API endpoint (default: `https://api.mem0.ai`).
## References
@@ -24,7 +24,7 @@ await addMemories(
```typescript
async function addMemories(
messages: LanguageModelV2Prompt | string,
messages: LanguageModelV3Prompt,
config?: Mem0ConfigSettings
): Promise<any>;
```
@@ -33,15 +33,16 @@ async function addMemories(
| Parameter | Type | Description |
|-----------|------|-------------|
| `messages` | `LanguageModelV2Prompt \| string` | Messages to store. If a string, wrapped as `[{ role: "user", content: string }]` |
| `messages` | `LanguageModelV3Prompt` | Messages to store. A plain string is also handled at runtime (wrapped as `[{ role: "user", content: string }]`) but is not part of the declared type |
| `config` | `Mem0ConfigSettings` | Optional. Must include entity scope (`user_id`, etc.) and API key |
**Behavior:**
1. If `messages` is a string, wraps it as a single user message
2. Otherwise, converts `LanguageModelV2Prompt` to Mem0 format via `convertToMem0Format` (handles multimodal content)
3. Calls `POST /v1/memories/` with the converted messages and config
2. Otherwise, converts `LanguageModelV3Prompt` to Mem0 format via `convertToMem0Format` (maps multimodal parts, but the add endpoint rejects them with a 400)
3. Calls `POST {host}/v3/memories/add/` with body `{ messages, user_id?, app_id?, agent_id?, run_id?, metadata?, infer? }` (entity IDs are top-level on add)
4. Throws `HTTP error! status: <code>` on a non-2xx response
**Returns:** The API response from Mem0 (memory operation result).
**Returns:** The parsed JSON response from Mem0. The v3 add endpoint queues extraction and responds with `{ status: "PENDING", event_id }` (the provider returns the raw REST JSON, so keys stay snake_case, unlike the `mem0ai` client). With `infer: false` messages are stored verbatim synchronously and the response also includes `results`.
---
@@ -63,7 +64,7 @@ const systemPrompt = await retrieveMemories("What restaurants do I like?", {
```typescript
async function retrieveMemories(
prompt: LanguageModelV2Prompt | string,
prompt: LanguageModelV3Prompt | string,
config?: Mem0ConfigSettings
): Promise<string>;
```
@@ -72,13 +73,13 @@ async function retrieveMemories(
| Parameter | Type | Description |
|-----------|------|-------------|
| `prompt` | `LanguageModelV2Prompt \| string` | The query to search memories for |
| `prompt` | `LanguageModelV3Prompt \| string` | The query to search memories for |
| `config` | `Mem0ConfigSettings` | Optional. Entity scope and API key |
**Behavior:**
1. Flattens the prompt to a plain string (extracts text from `LanguageModelV2Prompt` parts)
2. Calls `searchInternalMemories` (`POST /v2/memories/search/`)
3. Formats each memory as `"Memory: {memory.memory}\n\n"`
1. Flattens the prompt to a plain string (extracts text from `LanguageModelV3Prompt` parts)
2. Calls `searchInternalMemories` (`POST /v3/memories/search/`)
3. Accepts either a flat array or a `{ results: [...] }` response and formats each memory as `"Memory: {memory.memory}\n\n"`
4. Wraps everything in a system prompt preamble
**Returns:** A **string** containing the formatted system prompt with embedded memories. Returns `""` (empty string) if no memories found.
@@ -112,7 +113,7 @@ const memories = await getMemories("What are my preferences?", {
```typescript
async function getMemories(
prompt: LanguageModelV2Prompt | string,
prompt: LanguageModelV3Prompt | string,
config?: Mem0ConfigSettings
): Promise<any>;
```
@@ -121,21 +122,21 @@ async function getMemories(
| Parameter | Type | Description |
|-----------|------|-------------|
| `prompt` | `LanguageModelV2Prompt \| string` | The query to search memories for |
| `prompt` | `LanguageModelV3Prompt \| string` | The query to search memories for |
| `config` | `Mem0ConfigSettings` | Optional. Entity scope and API key |
**Behavior:**
1. Flattens the prompt to a plain string
2. Calls `searchInternalMemories` (`POST /v2/memories/search/`)
3. Returns `memories.results` (the array of memory objects)
2. Calls `searchInternalMemories` (`POST /v3/memories/search/`)
3. Normalizes the response: returns the response itself if it is an array, otherwise `response.results` (or `[]`)
**Returns:** Memory object array.
**Returns:** Flat memory object array.
---
## `searchMemories(prompt, config?)`
Retrieves the **full search API response** including results, relations, scores, and metadata.
Retrieves the **raw search API response** including results, scores, and metadata.
```typescript
import { searchMemories } from "@mem0/vercel-ai-provider";
@@ -144,14 +145,14 @@ const response = await searchMemories("cooking preferences", {
user_id: "alice",
mem0ApiKey: "m0-xxx",
});
// Returns: { results: [{ memory: "...", score: 0.95, ... }], relations: [...] }
// Returns: { results: [{ memory: "...", score: 0.95, ... }] }
```
**Signature:**
```typescript
async function searchMemories(
prompt: LanguageModelV2Prompt | string,
prompt: LanguageModelV3Prompt | string,
config?: Mem0ConfigSettings
): Promise<any>;
```
@@ -160,17 +161,17 @@ async function searchMemories(
| Parameter | Type | Description |
|-----------|------|-------------|
| `prompt` | `LanguageModelV2Prompt \| string` | The query to search memories for |
| `prompt` | `LanguageModelV3Prompt \| string` | The query to search memories for |
| `config` | `Mem0ConfigSettings` | Optional. Entity scope and API key |
**Behavior:**
1. Flattens the prompt to a plain string
2. Calls `searchInternalMemories` (`POST /v2/memories/search/`)
3. Returns the full response without any filtering
2. Calls `searchInternalMemories` (`POST /v3/memories/search/`)
3. Returns the response without any normalization
**Returns:** The complete API response object. On error, returns `[]`.
**Returns:** The API response as-is (an object with `results`, or a flat array if the API returns one). On error, logs and returns `[]` instead of throwing (unlike the other utilities, which rethrow).
**Note:** Unlike `getMemories`, this always returns the full response.
**Note:** Unlike `getMemories`, this does not unwrap `results`.
---
@@ -180,7 +181,7 @@ async function searchMemories(
|----------|---------|----------|
| `retrieveMemories` | Formatted system prompt **string** | Injecting directly into a `system` parameter for `generateText`/`streamText` |
| `getMemories` | Memory **array** | Processing memories programmatically (filtering, transforming, counting) |
| `searchMemories` | Full API **response** (results + relations) | Need relations, similarity scores, or complete metadata |
| `searchMemories` | Raw API **response** | Need similarity scores or complete metadata |
| `addMemories` | API response | Storing new conversation messages as memories |
## Internal: `searchInternalMemories(query, config?, top_k?)`
@@ -191,25 +192,27 @@ Not exported. Used by all retrieval functions.
async function searchInternalMemories(
query: string,
config?: Mem0ConfigSettings,
top_k: number = 5
top_k: number = 10
): Promise<any>;
```
**Behavior:**
1. Builds a `filters` object from entity identifiers (`user_id`, `app_id`, `agent_id`, `run_id`)
2. Resolves entity identifiers
3. Loads the API key from `config.mem0ApiKey` or `MEM0_API_KEY` env var
4. Calls `POST {host}/v2/memories/search/` with:
1. Builds a `filters` object from `config.filters`, then sets `user_id`, `app_id`, `agent_id`, `run_id` on it when present (entity IDs go inside `filters`, never top-level)
2. Loads the API key from `config.mem0ApiKey` or `MEM0_API_KEY` env var
3. Calls `POST {host}/v3/memories/search/` with:
- `query`: the search string
- `filters`: the filter object with entity identifiers
- `top_k`: from config or default 5
- All other config fields spread into the request body
- `top_k`: `config.top_k` or default 10 (an explicit `0` is respected)
- `threshold`, `rerank`, `metadata`: only when set in config
- No other config fields (`infer`, `page`, `page_size`, `host`, `mem0ApiKey`) are sent
4. Sends `Authorization: Token <key>` plus `X-Mem0-Source: VERCEL_AI_SDK` and `X-Mem0-Client: mem0-vercel-ai-provider/<version>` headers
5. Throws `HTTP error! status: <code>` on a non-2xx response
**Default host:** `https://api.mem0.ai`
## Internal: `convertToMem0Format(messages)`
Not exported. Used by `addMemories` to convert `LanguageModelV2Prompt` messages to Mem0's format.
Not exported. Used by `addMemories` to convert `LanguageModelV3Prompt` messages to Mem0's format.
**Multimodal content mapping:**
@@ -223,6 +226,8 @@ Not exported. Used by `addMemories` to convert `LanguageModelV2Prompt` messages
| MDX content | `{ type: "mdx_url", mdx_url: { url } }` or `{ type: "mdx", ... }` | MDX URL | `{ role, content: { type: "mdx_url", mdx_url: { url } } }` |
| PDF content | `{ type: "pdf_url", pdf_url: { url } }` or `{ type: "pdf", ... }` | PDF URL | `{ role, content: { type: "pdf_url", pdf_url: { url } } }` |
`/v3/memories/add/` rejects structured (non-string) `content` with a 400 `Not a valid string.` (live-tested on the Python SDK with `infer` true and false), so a prompt containing these parts is expected to fail with `HTTP error! status: 400` (checked against the endpoint, not through the provider). Only text parts are storable.
The function handles three message content shapes:
1. **String content**: passed through directly
2. **Array content**: each element mapped individually, nulls filtered out
@@ -230,7 +235,7 @@ The function handles three message content shapes:
## Internal: `flattenPrompt(prompt)`
Not exported. Extracts plain text from `LanguageModelV2Prompt` for use as a search query.
Not exported. Extracts plain text from `LanguageModelV3Prompt` for use as a search query.
- Iterates over prompt parts, extracting text from `user` role messages
- For `text` type content: extracts `.text`
@@ -249,12 +254,12 @@ All fields are optional. Used across all utility functions.
| `agent_id` | `string` | -- | Scope memories to an agent |
| `run_id` | `string` | -- | Scope memories to a session/run |
| `metadata` | `Record<string, any>` | -- | Custom metadata |
| `filters` | `Record<string, any>` | -- | Custom search filters |
| `infer` | `boolean` | -- | Enable inference |
| `page` | `number` | -- | Pagination page number |
| `page_size` | `number` | -- | Results per page |
| `filters` | `Record<string, any>` | -- | Custom search filters (entity IDs above are merged in and win on conflicts) |
| `infer` | `boolean` | -- | Sent on add only. `false` stores messages verbatim without extraction |
| `page` | `number` | -- | Declared in the type, not sent by the provider |
| `page_size` | `number` | -- | Declared in the type, not sent by the provider |
| `mem0ApiKey` | `string` | `MEM0_API_KEY` env | Mem0 API key |
| `top_k` | `number` | `5` | Number of memories to retrieve |
| `threshold` | `number` | -- | Minimum similarity score |
| `rerank` | `boolean` | -- | Enable re-ranking |
| `top_k` | `number` | `10` | Number of memories to retrieve |
| `threshold` | `number` | -- | Server-side relevance cutoff, not a floor on the returned score; sent only when set |
| `rerank` | `boolean` | -- (API default `false`) | Enable re-ranking, sent only when set |
| `host` | `string` | `https://api.mem0.ai` | Custom API host |
@@ -25,26 +25,26 @@ When called with no arguments, defaults to `{ provider: "openai" }`.
## `Mem0Provider` Interface
Implements `ProviderV2` from `@ai-sdk/provider`.
Implements `ProviderV3` from `@ai-sdk/provider` (`specificationVersion: "v3"`, AI SDK v6).
```typescript
interface Mem0Provider extends ProviderV2 {
interface Mem0Provider extends ProviderV3 {
// Call directly as a function
(modelId: Mem0ChatModelId, settings?: Mem0ChatSettings): LanguageModelV2;
(modelId: Mem0ChatModelId, settings?: Mem0ChatSettings): LanguageModelV3;
// Or use named methods
chat(modelId: Mem0ChatModelId, settings?: Mem0ChatSettings): LanguageModelV2;
completion(modelId: Mem0ChatModelId, settings?: Mem0ChatSettings): LanguageModelV2;
languageModel(modelId: Mem0ChatModelId, settings?: Mem0ChatSettings): LanguageModelV2;
chat(modelId: Mem0ChatModelId, settings?: Mem0ChatSettings): LanguageModelV3;
completion(modelId: Mem0ChatModelId, settings?: Mem0ChatSettings): LanguageModelV3;
languageModel(modelId: Mem0ChatModelId, settings?: Mem0ChatSettings): LanguageModelV3;
}
```
- **Direct call** (`mem0("gpt-5-mini", {...})`): creates a generic language model (neither chat nor completion mode forced).
- **`chat()`**: creates a model with `modelType: "chat"` (note: in the current source, the chat constructor sets `modelType: "completion"` -- this appears to be a bug; functionally equivalent to `completion()` at present).
- **`chat()`**: creates a model with `modelType: "chat"`.
- **`completion()`**: creates a model with `modelType: "completion"`.
- **`languageModel()`**: alias for the generic model (same as direct call).
All three return a `Mem0GenericLanguageModel` instance implementing `LanguageModelV2`.
All three return a `Mem0GenericLanguageModel` instance implementing `LanguageModelV3`.
## `Mem0ProviderSettings` Interface
@@ -52,14 +52,14 @@ Configuration passed to `createMem0()`.
```typescript
interface Mem0ProviderSettings {
baseURL?: string; // Base URL for the LLM provider (default: "http://api.openai.com")
headers?: Record<string, string>; // Custom headers for LLM requests
baseURL?: string; // Stored on the model config (default: "https://api.openai.com"), not applied to upstream LLM requests
headers?: Record<string, string | undefined>; // Stored on the model config, not applied to upstream LLM requests
provider?: string; // LLM provider name (default: "openai")
mem0ApiKey?: string; // Mem0 Platform API key (or use MEM0_API_KEY env var)
apiKey?: string; // LLM provider API key (e.g., OpenAI key)
mem0Config?: Mem0Config; // Default Mem0 config (user_id, etc.) applied to all calls
config?: LLMProviderSettings; // Provider-specific settings (OpenAI, Anthropic, etc.)
fetch?: typeof fetch; // Custom fetch implementation (for testing/middleware)
fetch?: typeof fetch; // Stored on the model config, not used by the upstream LLM client or Mem0 API calls
generateId?: () => string; // Custom ID generator (internal use)
name?: string; // Provider instance name
modelType?: "completion" | "chat"; // Force model type
@@ -75,7 +75,7 @@ interface Mem0ProviderSettings {
| `apiKey` | LLM provider API key | `"sk-xxx"` (OpenAI), `"sk-ant-xxx"` (Anthropic) |
| `mem0Config` | Default Mem0 settings for all calls | `{ user_id: "alice" }` |
| `config` | Provider-specific SDK settings | `{ organization: "org-xxx" }` for OpenAI |
| `baseURL` | Override LLM provider base URL | `"https://my-proxy.example.com"` |
| `config.baseURL` | Override LLM provider base URL (put it inside `config`; the top-level `baseURL` does not reach the upstream client) | `{ baseURL: "https://my-proxy.example.com/v1" }` |
## `mem0` Singleton
@@ -94,7 +94,7 @@ Equivalent to `createMem0()` with no arguments.
## `Mem0ConfigSettings` Interface
Configuration for memory operations. Used as `Mem0ChatSettings` (per-call) or `Mem0Config` (provider-level default). All fields are optional.
Configuration for memory operations. Used as `Mem0ChatSettings` (per-call) or `Mem0Config` (provider-level default; same shape, not exported from the package entry point). All fields are optional.
```typescript
interface Mem0ConfigSettings {
@@ -104,13 +104,13 @@ interface Mem0ConfigSettings {
run_id?: string; // Scope memories to a specific run/session
metadata?: Record<string, any>; // Custom metadata attached to memories
filters?: Record<string, any>; // Custom filters for memory search
infer?: boolean; // Enable inference during memory operations
page?: number; // Pagination: page number
page_size?: number; // Pagination: results per page
infer?: boolean; // Sent on add only: false stores messages verbatim without extraction
page?: number; // Declared in the type, not sent by the provider
page_size?: number; // Declared in the type, not sent by the provider
mem0ApiKey?: string; // Mem0 API key (overrides provider-level key)
top_k?: number; // Number of memories to retrieve (default: 5)
threshold?: number; // Minimum similarity score for retrieval (default: 0.1)
rerank?: boolean; // Enable re-ranking of search results (default: false)
top_k?: number; // Number of memories to retrieve (default: 10)
threshold?: number; // Server-side relevance cutoff; sent only when set
rerank?: boolean; // Enable re-ranking of search results; sent only when set (API default: false)
host?: string; // Custom Mem0 API host (default: "https://api.mem0.ai")
}
```
@@ -137,17 +137,18 @@ mem0("gpt-5-mini", { user_id: "alice" })
## `LLMProviderSettings` Type
Union of provider-specific settings. Extends all supported provider setting interfaces:
Union of provider-specific settings for all supported provider SDKs:
```typescript
interface LLMProviderSettings extends
OpenAIProviderSettings,
AnthropicProviderSettings,
CohereProviderSettings,
GroqProviderSettings {}
type LLMProviderSettings =
| OpenAIProviderSettings
| AnthropicProviderSettings
| CohereProviderSettings
| GroqProviderSettings
| GoogleGenerativeAIProviderSettings;
```
Pass via the `config` field of `Mem0ProviderSettings` to forward settings to the underlying LLM provider SDK.
Pass via the `config` field of `Mem0ProviderSettings` to forward settings (e.g., `baseURL`, `headers`, `apiKey`) to the underlying LLM provider SDK. `config` is spread after `apiKey`, so a `config.apiKey` takes precedence.
## Provider Selection: `Mem0ClassSelector`
@@ -155,12 +156,12 @@ Internal class that maps the `provider` string to the correct AI SDK provider.
```typescript
class Mem0ClassSelector {
static supportedProviders = ["openai", "anthropic", "cohere", "groq", "google"];
static supportedProviders = ["openai", "anthropic", "cohere", "groq", "google", "gemini"];
// ...
}
```
**Important:** The `"gemini"` alias exists in the provider switch statement (maps to `createGoogleGenerativeAI`) but is **NOT** in the `supportedProviders` list. The constructor validates against `supportedProviders`, so using `"gemini"` will throw `"Model not supported: gemini"`. Use `"google"` instead.
`"gemini"` is an alias of `"google"` (both map to `createGoogleGenerativeAI`). Any other value throws `"Model not supported: <value>"`.
### Provider mapping
@@ -170,7 +171,7 @@ class Mem0ClassSelector {
| `"anthropic"` | `@ai-sdk/anthropic` | `createAnthropic` |
| `"cohere"` | `@ai-sdk/cohere` | `createCohere` |
| `"groq"` | `@ai-sdk/groq` | `createGroq` |
| `"google"` | `@ai-sdk/google` | `createGoogleGenerativeAI` |
| `"google"` or `"gemini"` | `@ai-sdk/google` | `createGoogleGenerativeAI` |
## `Mem0` Facade Class
@@ -184,7 +185,7 @@ const chatModel = mem0.chat("gpt-5-mini", { user_id: "alice" });
const completionModel = mem0.completion("gpt-5-mini");
```
The facade defaults its base URL to `"http://127.0.0.1:11434/api"` (Ollama-style) rather than `"http://api.openai.com"`. It always uses `"openai"` as the provider for created models.
The facade defaults its base URL to `"https://api.openai.com"`. It always uses `"openai"` as the provider for created models, and only stores `baseURL` and `headers` from its options: the `provider`, `mem0ApiKey`, and `apiKey` options are ignored, so `MEM0_API_KEY` and `OPENAI_API_KEY` must come from the environment. Prefer `createMem0` for anything beyond a quick start.
**Methods:**
- `chat(modelId, settings?)` -- creates a model with `modelType: "chat"`
@@ -192,13 +193,11 @@ The facade defaults its base URL to `"http://127.0.0.1:11434/api"` (Ollama-style
## `Mem0GenericLanguageModel` Class
The core class implementing `LanguageModelV2`. Created by `createMem0` or the `Mem0` facade.
The core class implementing `LanguageModelV3`. Created by `createMem0` or the `Mem0` facade.
```typescript
class Mem0GenericLanguageModel implements LanguageModelV2 {
readonly specificationVersion = "v2";
readonly defaultObjectGenerationMode = "json";
readonly supportsImageUrls = false;
class Mem0GenericLanguageModel implements LanguageModelV3 {
readonly specificationVersion = "v3";
readonly supportedUrls: Record<string, RegExp[]> = { '*': [/.*/] };
provider: string; // e.g., "openai"
@@ -206,22 +205,26 @@ class Mem0GenericLanguageModel implements LanguageModelV2 {
settings: Mem0ChatSettings;
config: Mem0ChatConfig;
async doGenerate(options: LanguageModelV2CallOptions): Promise<...>;
async doStream(options: LanguageModelV2CallOptions): Promise<...>;
async doGenerate(options: LanguageModelV3CallOptions): Promise<...>;
async doStream(options: LanguageModelV3CallOptions): Promise<...>;
}
```
`defaultObjectGenerationMode` and `supportsImageUrls` (V2 properties) no longer exist.
Both `doGenerate` and `doStream` follow the same internal flow:
1. Build `Mem0ConfigSettings` from `config.mem0Config` merged with `settings`
1. Build `Mem0ConfigSettings` from `config.mem0ApiKey`, then `config.mem0Config`, then `settings` (later entries win)
2. Call `processMemories`:
- Fire `addMemories` as fire-and-forget (no await, `.then().catch()`)
- Await `getMemories` to retrieve relevant memories
- Format memories as a system message and prepend to the prompt
- Await `addMemories` (errors are logged and ignored)
- Await `getMemories` to retrieve relevant memories (on failure, continue with no memories)
- If any memories were found, format them as a system message and prepend it to a copy of the prompt
3. Create the underlying LLM model via `Mem0ClassSelector`
4. Delegate to the underlying model's `doGenerate` or `doStream`
5. Return the result
`doGenerate` additionally appends a `source` content part (`title: "Mem0 Memories"`) with `providerMetadata.mem0.memories` and `providerMetadata.mem0.memoriesText` when memories were retrieved. `doStream` returns the underlying stream result unchanged (no Mem0 source) and throws `"Streaming failed or method not implemented."` if streaming setup fails.
**Note:** Entity identifier fields use snake_case (`user_id`, `app_id`, `agent_id`, `run_id`) to match the Mem0 API.
## Type: `Mem0ChatModelId`
@@ -230,4 +233,4 @@ Both `doGenerate` and `doStream` follow the same internal flow:
type Mem0ChatModelId = string & NonNullable<unknown>;
```
Any non-null string. The model ID is passed through to the underlying provider (e.g., `"gpt-5-mini"`, `"gemini-pro"`).
Any non-null string. The model ID is passed through to the underlying provider (e.g., `"gpt-5-mini"`, `"gemini-2.5-flash"`).
@@ -20,7 +20,7 @@ const { text } = await generateText({
console.log(text);
```
Memories are automatically retrieved before the call and stored after.
The prompt is stored to Mem0 and relevant memories are retrieved before the LLM call (see section 8).
## 2. Wrapped Model with streamText (Streaming)
@@ -42,7 +42,7 @@ for await (const chunk of result.textStream) {
}
```
Memory retrieval happens before streaming begins. The conversation is stored to Mem0 as a fire-and-forget call (non-blocking).
The add request and the memory search both finish before streaming begins (extraction itself is async). The Mem0 source (`sources`) is only attached by `generateText`, not by streaming.
## 3. Standalone Utilities with OpenAI
@@ -136,7 +136,7 @@ console.log(object);
// { breakfast: "Avocado toast (you mentioned loving it)", lunch: "...", ... }
```
The `defaultObjectGenerationMode` is `"json"`, so structured output works out of the box.
The wrapped model forwards all call options (including the response format) to the underlying provider, so structured output works out of the box. `generateObject` is deprecated in AI SDK v6 in favor of `generateText` with `output: Output.object({ schema })`; both go through the same wrapped model.
## 6. Multi-Provider Setup
@@ -195,22 +195,20 @@ A POST handler that uses the wrapped model in a Next.js App Router API route.
```typescript
// app/api/chat/route.ts
import { streamText } from "ai";
import { convertToModelMessages, streamText, UIMessage } from "ai";
import { createMem0 } from "@mem0/vercel-ai-provider";
const mem0 = createMem0();
export async function POST(req: Request) {
const { messages, user_id } = await req.json();
const lastMessage = messages[messages.length - 1];
const { messages, user_id }: { messages: UIMessage[]; user_id: string } = await req.json();
const result = streamText({
model: mem0("gpt-5-mini", { user_id }),
prompt: lastMessage.content,
messages: await convertToModelMessages(messages),
});
return result.toDataStreamResponse();
return result.toUIMessageStreamResponse();
}
```
@@ -219,22 +217,25 @@ export async function POST(req: Request) {
```typescript
// app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";
import { convertToModelMessages, streamText, UIMessage } from "ai";
import { retrieveMemories, addMemories } from "@mem0/vercel-ai-provider";
export async function POST(req: Request) {
const { messages, user_id } = await req.json();
const { messages, user_id }: { messages: UIMessage[]; user_id: string } = await req.json();
const lastMessage = messages[messages.length - 1];
const lastText = lastMessage.parts
.flatMap((part) => (part.type === "text" ? [part.text] : []))
.join(" ");
// Retrieve relevant memories
const memories = await retrieveMemories(lastMessage.content, {
const memories = await retrieveMemories(lastText, {
user_id,
});
// Stream the response
const result = streamText({
model: openai("gpt-5-mini"),
prompt: lastMessage.content,
messages: await convertToModelMessages(messages),
system: memories,
});
@@ -242,14 +243,14 @@ export async function POST(req: Request) {
result.text.then(async (text) => {
await addMemories(
[
{ role: "user", content: [{ type: "text", text: lastMessage.content }] },
{ role: "user", content: [{ type: "text", text: lastText }] },
{ role: "assistant", content: [{ type: "text", text }] },
],
{ user_id }
);
});
return result.toDataStreamResponse();
return result.toUIMessageStreamResponse();
}
```
@@ -260,25 +261,26 @@ export async function POST(req: Request) {
```
1. doGenerate(options) or doStream(options) is called
2. processMemories(messagesPrompts, mem0Config):
a. addMemories(messagesPrompts, mem0Config)
--> fire-and-forget: .then().catch(), NO await
a. await addMemories(messagesPrompts, mem0Config)
--> POST /v3/memories/add/ with converted messages
--> errors are caught and logged
b. await getMemories(messagesPrompts, mem0Config)
--> POST /v3/memories/search/ with flattened prompt
--> returns memory array
c. Format memories into system message string
d. Prepend system message to messagesPrompts array
d. If memories were found, prepend the system message to a copy of messagesPrompts
e. Return { memories, messagesPrompts }
3. Create underlying LLM via Mem0ClassSelector.createProvider()
4. Call model.doGenerate(updatedOptions) or model.doStream(updatedOptions)
5. Return result
5. doGenerate appends the "Mem0 Memories" source when memories were found
6. Return result
```
**Critical detail:** The `addMemories` call in step 2a is **NON-BLOCKING**. It uses `.then().catch()` without `await`, meaning:
- Memory storage happens asynchronously in the background
- The LLM response is not delayed by the memory write
- If the memory write fails, it logs an error but does not affect the response
- There is a brief window where the latest conversation is not yet stored
**Critical detail:** The `addMemories` call in step 2a is **awaited**, meaning:
- The add request is sent (and awaited) before the search and the LLM call, but extraction is async (`/v3/memories/add/` only queues the work and returns `PENDING`), so facts from the current prompt are usually not searchable until a later call
- Each wrapped call adds one Mem0 write and one Mem0 search of latency before the LLM starts
- If the memory write fails, it logs an error and the call continues
- `messagesPrompts` is the full prompt (all roles, including earlier turns), so every call sends the entire conversation to the add endpoint
### Memory injection format
@@ -318,8 +320,8 @@ const memories = await retrieveMemories(prompt, {
const mem0 = createMem0();
const model = mem0("gpt-5-mini", {
user_id: "alice",
top_k: 10, // retrieve up to 10 memories (default: 5)
threshold: 0.8, // only memories with score >= 0.8
top_k: 20, // retrieve up to 20 memories (default: 10)
threshold: 0.8, // server-side cutoff applied before score blending
rerank: true, // enable re-ranking of results
});
```
+11 -10
View File
@@ -15,10 +15,11 @@ description: >
license: Apache-2.0
metadata:
author: mem0ai
version: "3.0.0"
version: "3.1.0"
category: ai-memory
tags: "memory, personalization, ai, python, typescript, vector-search"
compatibility: Requires Python 3.10+ or Node.js 18+, pip install mem0ai or npm install mem0ai, MEM0_API_KEY env var (Platform), and internet access to api.mem0.ai. SDK v3 with v2 compatibility mode available.
mem0_tested_versions: "mem0ai (PyPI) >=2.0.0,<3.0.0; mem0ai (npm) >=3.0.0,<4.0.0"
compatibility: Requires Python 3.10+ or Node.js 18+, pip install mem0ai or npm install mem0ai, MEM0_API_KEY env var (Platform), and internet access to api.mem0.ai. Targets the v3 API (Python mem0ai 2.x, TypeScript mem0ai 3.x).
---
# Mem0 Platform Integration
@@ -134,20 +135,20 @@ def chat(user_input: str, user_id: str) -> str:
## Common edge cases
- **Search returns empty:** Memories process asynchronously. Wait 2-3s after `add()` before searching. Also verify `user_id` matches exactly (case-sensitive) and use `filters={"user_id": "..."}` syntax.
- **Search returns empty:** `add()` is asynchronous and returns `{"event_id": "...", "status": "PENDING"}` (`eventId` on the TS client). Memories are searchable once the event is `SUCCEEDED` (poll `GET /v1/event/{event_id}/`, or wait a few seconds). `infer=False` is synchronous. Also verify `user_id` matches exactly (case-sensitive) and use `filters={"user_id": "..."}` syntax.
- **AND filter with user_id + agent_id returns empty:** Entities are stored separately. Use `OR` instead, or query separately.
- **Duplicate memories:** Don't mix `infer=True` (default) and `infer=False` for the same data. Stick to one mode.
- **Wrong import:** Always use `from mem0 import MemoryClient` (or `AsyncMemoryClient` for async). Do not use `from mem0 import Memory`.
- **v3 defaults:** `top_k=20`, `threshold=0.1`, `rerank=False`. Adjust as needed for your use case.
- **Wrong import:** For the hosted Platform use `from mem0 import MemoryClient` (or `AsyncMemoryClient` for async). `from mem0 import Memory` is the self-hosted OSS class and does not use `MEM0_API_KEY`.
- **v3 defaults (Platform):** `top_k=10`, `rerank=False`. `threshold` is a server-side cutoff applied before score blending, not a floor on the returned `score` (the default and `0.0` return the same or nearly the same results). The client sends none of these unless you pass them. The OSS `Memory.search()` default is `top_k=20`. Adjust as needed for your use case.
## v2 Compatibility
If you're using SDK v2.x, note these differences:
- **Entity IDs:** Pass `user_id` as top-level kwarg to `search()` instead of inside `filters`
- **Defaults:** `top_k=100`, no threshold, `rerank=True`
- **Graph memory:** Available via `enable_graph=True`
The "v2" line is Python SDK 1.x and TypeScript SDK 2.x. If you are still on it, note these differences from the current SDKs (Python 2.x, TypeScript 3.x):
- **Entity IDs:** `user_id` / `agent_id` / `run_id` could be top-level kwargs on `search()` and `get_all()`. They now go inside `filters` (top-level raises an error)
- **Defaults (Platform):** `threshold=0.3`, `rerank=False`. OSS: `top_k=100`, no threshold, `rerank=True`
- **Graph memory:** `enable_graph=True` and `relations` are gone. Entity linking is built in (see [client/python.md](client/python.md) for OSS)
See the [migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for details.
See the [Platform migration guide](https://docs.mem0.ai/migration/platform-v2-to-v3) and the [OSS migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for details.
## Live documentation search
+10 -5
View File
@@ -9,9 +9,9 @@ Quick-reference cheatsheet for developers working across both Mem0 SDKs.
| Import (Platform) | `from mem0 import MemoryClient` | `import MemoryClient from 'mem0ai'` |
| Import (OSS) | `from mem0 import Memory` | `import { Memory } from 'mem0ai/oss'` |
| Constructor | `MemoryClient(api_key="m0-xxx")` | `new MemoryClient({ apiKey: 'm0-xxx' })` |
| Required param | `api_key` (positional or kwarg) | `apiKey` (in options object) |
| Required param | `api_key` (optional, falls back to `MEM0_API_KEY`) | `apiKey` (required, in options object) |
Both read from `MEM0_API_KEY` env var if no key provided.
Python reads `MEM0_API_KEY` if no key is passed. TypeScript does not read the environment: pass `apiKey: process.env.MEM0_API_KEY` yourself.
## Method Naming
@@ -38,6 +38,11 @@ Both read from `MEM0_API_KEY` env var if no key provided.
| Create export | `create_memory_export()` | `createMemoryExport()` |
| Get export | `get_memory_export()` | `getMemoryExport()` |
| Feedback | `feedback()` | `feedback()` |
| Get profile | `get_profile(entity_id)` | `getProfile({ entityId })` |
| Generate profile | `generate_profile(entity_id)` | `generateProfile({ entityId })` |
| Profile settings | `get_profile_settings()` / `update_profile_settings()` | `getProfileSettings()` / `updateProfileSettings()` |
| Sample profiles | `sample_profiles()` | `sampleProfiles()` |
| Profile job | `get_profile_job()` | `getProfileJob()` |
**Rule:** Python uses `snake_case`, TypeScript uses `camelCase` for method names.
@@ -61,8 +66,8 @@ await client.search('query', { filters: { user_id: 'alice' }, topK: 5, rerank: t
| Aspect | Python | TypeScript |
|--------|--------|------------|
| HTTP library | httpx | axios |
| Default timeout | 300s | 60s |
| HTTP library | httpx | native `fetch` |
| Default timeout | 300s | None set by the SDK |
| Sync support | Yes (`MemoryClient`) | No (all async) |
| Async support | Yes (`AsyncMemoryClient`) | All methods are async |
| Project management | `client.project.*` (separate class) | `client.getProject()` / `client.updateProject()` |
@@ -87,7 +92,7 @@ These methods exist in Python but not TypeScript:
| Method | Description |
|--------|-------------|
| `deleteUser(data)` | Convenience method for single entity deletion |
| `deleteUser(data)` | Deprecated single entity deletion (use `deleteUsers`) |
| `ping()` | Health check endpoint |
## OSS Config Naming
+137 -48
View File
@@ -21,12 +21,15 @@ import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: 'm0-xxx' });
```
**Constructor:** `new MemoryClient({ apiKey })`. If `apiKey` is not provided, reads from `MEM0_API_KEY` environment variable.
**Constructor:** `new MemoryClient({ apiKey, host?, identityCacheMax? })`. `apiKey` is required: the constructor throws `Mem0 API key is required` when it is missing or empty. There is no `MEM0_API_KEY` environment fallback, so pass `apiKey: process.env.MEM0_API_KEY` yourself.
- HTTP library: `axios`
- Timeout: 60 seconds
- Base URL: `https://api.mem0.ai`
- Also a named export: `import { MemoryClient, Feedback, WebhookEvent } from 'mem0ai'`
- HTTP library: native `fetch` (the axios instance in `mem0.ts` is unused)
- Timeout: none set by the SDK
- Base URL: `https://api.mem0.ai` (override with `host`)
- All methods are async (return `Promise`)
- Top-level option names are camelCase and responses come back camelCased (`event_id` becomes `eventId`). Keys inside `filters` are sent as written, so keep them snake_case (`user_id`).
- `MEM0_SOURCE`, `MEM0_APPLICATION` and `MEM0_CLIENT_STACK` set the surface-identity headers for wrappers that cannot pass options.
---
@@ -52,16 +55,22 @@ await client.add(messages, { userId: 'alice' });
| `options.appId` | string | Application identifier |
| `options.runId` | string | Session identifier |
| `options.metadata` | object | Custom key-value pairs |
| `options.infer` | boolean | If false, store raw text (default: true) |
| `options.infer` | boolean | If false, store messages as-is without extraction (default: true) |
| `options.customCategories` | `{[name]: description}[]` | Per-call category list |
| `options.customInstructions` | string | Per-call extraction instructions |
| `options.agentCustomInstructions` | string | Per-call extraction instructions for agent-scoped memories |
| `options.timestamp` | number | Unix timestamp (seconds) to record as the memory time |
| `options.expirationDate` | string | Date after which the memory is no longer returned |
| `options.structuredDataSchema` | object | Schema for structured extraction |
**Returns:** `Promise<any>` -- list of events
**Returns:** typed `Promise<Array<Memory>>`, but the v3 API queues extraction and responds with `{ status: 'PENDING', eventId }`. With `infer: false` the call is synchronous and the response carries `message`, `status` and `results`. The client has no event-polling method (REST `GET /v1/event/{event_id}/`, see `../references/api-reference.md`).
#### search(query, options?)
Search memories by semantic similarity.
```typescript
const results = await client.search('dietary preferences', { filters: { user_id: 'alice' }, topK: 20 });
const results = await client.search('dietary preferences', { filters: { user_id: 'alice' }, topK: 10 });
for (const mem of results.results) {
console.log(mem.memory, mem.score);
}
@@ -71,11 +80,20 @@ for (const mem of results.results) {
|-----------|------|-------------|
| `query` | string | Natural language search query |
| `options.filters` | object | Filter object with entity IDs (`user_id`, `agent_id`, etc.) and/or `AND`/`OR`/`NOT` conditions |
| `options.topK` | number | Number of results (default: 20) |
| `options.topK` | number | Number of results (default: 10) |
| `options.rerank` | boolean | Enable semantic reranking (default: false) |
| `options.threshold` | number | Minimum similarity (default: 0.1) |
| `options.threshold` | number | Server-side relevance cutoff (0 to 1), applied before score blending, so it is not a floor on the returned `score`. Omitting it and passing `0` returned the same results in live tests. Filter on `score` client-side for a precise cutoff |
| `options.latestOnly` | boolean | Return only current (non-superseded) memories |
| `options.fields` | string[] | Not applied in v3 |
| `options.categories` | string[] | Not applied in v3. Use `filters: { AND: [{ categories: { in: [...] } }] }` |
| `options.metadata` | object | Not applied in v3. Use `filters: { AND: [{ metadata: {...} }] }` |
| `options.showExpired` | boolean | Include memories past their expiration date |
| `options.referenceDate` | string \| number | Treat this as "now" for relative time queries |
| `options.keywordSearch` | boolean | Not applied in v3 (removed from the v3 search schema; keyword matching is part of v3 hybrid scoring) |
**Returns:** `Promise<SearchResult>` -- `{results: [{id, memory, score, ...}]}`
Entity IDs go inside `filters`. A top-level `userId`, `agentId`, `appId` or `runId` throws.
**Returns:** `Promise<{ results: Array<Memory> }>` -- `{results: [{id, memory, score, ...}]}`
#### get(memoryId)
@@ -85,7 +103,7 @@ const memory = await client.get('ea925981-...');
#### getAll(options?)
Retrieve all memories. Requires at least one entity identifier in filters.
Retrieve all memories. Requires non-empty `filters`; scope them with at least one entity identifier.
```typescript
const memories = await client.getAll({ filters: { user_id: 'alice' } });
@@ -99,7 +117,12 @@ const filtered = await client.getAll({
|-----------|------|-------------|
| `options.filters` | object | Filter object with entity IDs (`user_id`, `agent_id`, etc.) and/or `AND`/`OR`/`NOT` conditions |
| `options.page` | number | Page number |
| `options.pageSize` | number | Results per page |
| `options.pageSize` | number | Results per page (default: 100, max: 200) |
| `options.startDate` / `options.endDate` / `options.categories` | - | Not applied in v3. Use `filters` with `created_at` or `categories` |
| `options.latestOnly` | boolean | Return only current (non-superseded) memories |
| `options.showExpired` | boolean | Include memories past their expiration date |
**Returns:** `Promise<{ count, next, previous, results: Array<Memory> }>`
#### update(memoryId, data)
@@ -113,25 +136,33 @@ await client.update('ea925981-...', { text: 'Updated', metadata: { verified: tru
| `memoryId` | string | Memory ID |
| `data.text` | string | New content |
| `data.metadata` | object | New metadata |
| `data.timestamp` | string | New timestamp |
| `data.timestamp` | number \| string | New timestamp |
| `data.expirationDate` | string \| null | New expiration date, `null` to clear |
#### delete(memoryId)
At least one of `text`, `metadata`, `timestamp` or `expirationDate` is required, otherwise the call throws.
#### delete(memoryId, options?)
```typescript
await client.delete('ea925981-...');
await client.delete('ea925981-...', { deleteLinked: true });
```
`deleteLinked: true` also deletes the older memories this one superseded (default: false).
#### deleteAll(options?)
```typescript
await client.deleteAll({ userId: 'alice' });
```
Takes top-level `userId`, `agentId`, `appId`, `runId` (not `filters`).
#### history(memoryId)
```typescript
const history = await client.history('ea925981-...');
// Returns: [{previousValue, newValue, action, timestamps}]
// Returns: [{id, memoryId, input, oldMemory, newMemory, event, userId, categories, metadata, createdAt, updatedAt}]
```
---
@@ -147,6 +178,8 @@ await client.batchUpdate([
]);
```
Each item must include `text`. `metadata` on a batch item is ignored and a metadata-only item returns a 400. Use `update(memoryId, { metadata })` to change metadata.
#### batchDelete(memories)
```typescript
@@ -160,17 +193,19 @@ await client.batchDelete(['uuid-1', 'uuid-2', 'uuid-3']);
#### users()
```typescript
const users = await client.users();
// Returns: {results: [{type: "user", name: "alice"}, ...]}
const users = await client.users({ page: 1, pageSize: 50 });
// Returns: {count, next, previous, totalUsers, totalAgents, totalApps, totalRuns, results: [{id, name, type, createdAt, updatedAt, owner, metadata, isPlayground}, ...]}
```
#### deleteUser(data) / deleteUsers(data)
#### deleteUsers(params)
```typescript
await client.deleteUser({ userId: 'alice' }); // Single entity
await client.deleteUsers({ agentId: 'bot-1' }); // Flexible
await client.deleteUsers({ userId: 'alice' });
await client.deleteUsers({ agentId: 'bot-1' });
```
Takes one of `userId`, `agentId`, `appId`, `runId`. Calling it with no arguments deletes ALL users, agents, apps and runs, but only those on the first page returned by `users()`: with many entities, re-run it until it throws `No entities to delete`, or page with `users({ page, pageSize })` and delete per entity. `deleteUser({ entity_id, entity_type })` still exists but is deprecated.
---
### Project Management
@@ -182,24 +217,29 @@ const config = await client.getProject({ fields: ['customCategories'] });
// Update project settings
await client.updateProject({
customInstructions: 'Extract dietary preferences and health info',
agentCustomInstructions: 'Extract operational lessons for the agent',
customCategories: [{ health: 'Medical and dietary info' }],
decay: true,
});
```
`getProject` requires its options argument (pass `{}` for no field filter). Other `updateProject` keys: `memoryDepth`, `usecaseSetting`, `multilingual`, `version`. Both methods wait for the org and project identity the client resolves at startup and throw if it cannot be resolved.
---
### Webhooks
```typescript
// List
import { WebhookEvent } from 'mem0ai';
// List (projectId is optional, defaults to the project of your API key)
const webhooks = await client.getWebhooks({ projectId: 'proj_123' });
// Create
// Create (always uses the project resolved from your API key)
const webhook = await client.createWebhook({
url: 'https://your-app.com/webhook',
name: 'Memory Logger',
projectId: 'proj_123',
eventTypes: ['memory_add', 'memory_update'],
eventTypes: [WebhookEvent.MEMORY_ADDED, WebhookEvent.MEMORY_UPDATED],
});
// Update
@@ -218,26 +258,50 @@ await client.deleteWebhook({ webhookId: 'wh_123' });
### Feedback
```typescript
import { Feedback } from 'mem0ai';
await client.feedback({
memoryId: 'mem-123',
feedback: 'POSITIVE',
feedback: Feedback.POSITIVE,
feedbackReason: 'Accurately captured preference',
});
```
`Feedback` values: `POSITIVE`, `NEGATIVE`, `VERY_NEGATIVE`. `feedback` and `feedbackReason` are optional, and `null` clears existing feedback.
---
### Export
```typescript
const exportReq = await client.createMemoryExport({
schema: JSON.stringify({ type: 'object', properties: { name: { type: 'string' } } }),
filters: { user_id: 'alice' },
schema: { type: 'object', properties: { name: { type: 'string' } } },
filters: { AND: [{ user_id: 'alice' }] },
exportInstructions: 'Build a profile from all memories',
});
const result = await client.getMemoryExport({ memoryExportId: exportReq.id });
```
`schema` is an object (not a JSON string) and `schema` and `filters` are both required. `getMemoryExport` needs `memoryExportId` or `filters`.
---
### User Profiles (beta)
```typescript
await client.updateProfileSettings({
enabled: true,
schema: { type: 'object', properties: { communication_style: { type: 'string', description: 'How the user prefers to be addressed' } } },
});
const job = await client.generateProfile({ entityId: 'alice' });
const result = await client.getProfile({ entityId: 'alice' });
if (result.status === 'succeeded') console.log(result.profile);
```
Other methods: `getProfileSettings()`, `sampleProfiles({ limit?, idempotencyKey? })`, `getProfileJob(jobIdOrStatusUrl)`. Generation is asynchronous, so branch on `status` (`succeeded`, `pending`, `failed`, `not_enabled`, `insufficient_data`) rather than on an empty `profile`. Every schema property needs a `description`.
---
### TypeScript Types
@@ -245,15 +309,20 @@ const result = await client.getMemoryExport({ memoryExportId: exportReq.id });
Key interfaces from `mem0.types.ts`:
```typescript
interface Message { role: string; content: string; }
interface Memory { id: string; memory: string; userId: string; categories: string[]; score?: number; /* ... */ }
interface MemoryOptions { userId?: string; agentId?: string; appId?: string; runId?: string; metadata?: object; /* ... */ }
interface SearchOptions { filters?: object; topK?: number; rerank?: boolean; threshold?: number; /* ... */ }
interface MemoryHistory { id: string; memoryId: string; previousValue: string; newValue: string; action: string; /* ... */ }
interface FeedbackPayload { memoryId: string; feedback: string; feedbackReason?: string; }
interface WebhookCreatePayload { url: string; name: string; projectId: string; eventTypes: string[]; }
interface Message { role: 'user' | 'assistant'; content: string | { type: 'image_url'; image_url: { url: string } }; }
interface Memory { id: string; memory?: string; userId?: string; categories?: string[]; score?: number; expirationDate?: string | null; /* ... */ }
interface AddMemoryOptions { userId?: string; agentId?: string; appId?: string; runId?: string; metadata?: object; infer?: boolean; /* ... */ }
interface SearchMemoryOptions { filters?: object; topK?: number; rerank?: boolean; threshold?: number; /* ... */ }
interface GetAllMemoryOptions { filters?: object; page?: number; pageSize?: number; /* ... */ }
interface MemoryHistory { id: string; memoryId: string; oldMemory: string | null; newMemory: string | null; event: string; /* ... */ }
interface FeedbackPayload { memoryId: string; feedback?: Feedback | null; feedbackReason?: string | null; }
interface WebhookCreatePayload { name: string; url: string; eventTypes: WebhookEvent[]; }
```
`Message.content` is typed to allow an `image_url` object, but `/v3/memories/add/` rejects structured content with a 400 (`Not a valid string.`), so pass a plain string (see Multimodal Support in [features.md](../references/features.md)).
Also exported: `DeleteAllMemoryOptions`, `MemoryUpdateBody`, `PromptUpdatePayload`, `Webhook`, `WebhookUpdatePayload`, `User`, `AllUsers`, the profile types, and the error classes `MemoryError`, `AuthenticationError`, `RateLimitError`, `ValidationError`, `MemoryNotFoundError`, `NetworkError`, `ConfigurationError`, `MemoryQuotaExceededError`.
---
## Open Source / Self-Hosted
@@ -279,21 +348,21 @@ const m = new Memory(); // Uses default config
```typescript
const config = {
llm: {
provider: 'openai', // openai, groq, anthropic, google, ollama, lmstudio, mistral, azure
provider: 'openai', // openai, openai_structured, anthropic, groq, ollama, lmstudio, google (gemini), azure_openai, mistral, langchain, deepseek, xai, sarvam, aws_bedrock, litellm, minimax, together, vllm
config: {
model: 'gpt-5-mini',
apiKey: 'sk-xxx',
},
},
embedder: {
provider: 'openai', // openai, ollama, lmstudio, google, azure, langchain, anthropic
provider: 'openai', // openai, aws_bedrock, ollama, lmstudio, together, google (gemini), azure_openai, fastembed, langchain, vertexai, huggingface
config: {
model: 'text-embedding-3-small',
apiKey: 'sk-xxx',
},
},
vectorStore: {
provider: 'qdrant', // memory, qdrant, redis, supabase, langchain, azure_ai_search, pgvector
provider: 'qdrant', // memory (default), qdrant, chroma, redis, valkey, supabase, langchain, vectorize, azure-ai-search, vertex_ai_vector_search, pgvector, databricks, neptune-analytics, elasticsearch, opensearch, upstash_vector, azure_mysql, cassandra, pinecone, s3-vectors, turbopuffer, milvus, mongodb, weaviate, oracledb, baidu
config: {
collectionName: 'my_memories',
host: 'localhost',
@@ -310,6 +379,8 @@ const m = new Memory(config);
const m2 = Memory.fromConfig(config);
```
Defaults when omitted: LLM `openai` `gpt-5-mini`, embedder `openai` `text-embedding-3-small`, vector store `memory` (in-process), history `sqlite` at `memory.db`. `reranker` is also accepted (providers `cohere`, `zero_entropy`, `sentence_transformer`, `huggingface`, `llm_reranker`) and applies when `search` is called with `rerank: true`. There is no graph store in the TS OSS SDK.
### Methods
All methods are async (return `Promise`):
@@ -327,14 +398,17 @@ await m.add([
| Parameter | Type | Description |
|-----------|------|-------------|
| `messages` | `string \| Message[]` | Content to store |
| `config.userId` | string | User identifier (at least one scope required) |
| `config.userId` | string | User identifier (at least one of `userId`, `agentId`, `runId` is required) |
| `config.agentId` | string | Agent identifier |
| `config.runId` | string | Session identifier |
| `config.metadata` | object | Custom key-value pairs |
| `config.filters` | object | Additional filters |
| `config.infer` | boolean | LLM inference (default: true) |
| `config.expirationDate` | string | `YYYY-MM-DD`, expired memories are hidden from `search` and `getAll` |
**Returns:** `Promise<{results: [...], relations?: [...]}>`
`config` is a required argument. `config.timestamp` is not supported in OSS (it throws).
**Returns:** `Promise<{results: [...]}>`, each item `{ id, memory, metadata: { event: 'ADD' } }` (the event is under `metadata`, not top-level).
#### search(query, config)
@@ -347,13 +421,23 @@ const results = await m.search('dietary preferences', { filters: { user_id: 'ali
| `query` | string | Search query |
| `config.filters` | object | Filter object with entity IDs (`user_id`, `agent_id`, `run_id`, etc.) |
| `config.topK` | number | Max results (default: 20) |
| `config.threshold` | number | Minimum similarity (default: 0.1) |
| `config.rerank` | boolean | Rerank with the configured `reranker` (no-op without one) |
| `config.showExpired` | boolean | Include expired memories (default: false) |
Top-level entity IDs throw. `config.referenceDate` is not supported in OSS (it throws).
#### get(memoryId) / getAll(config) / update(memoryId, data) / delete(memoryId) / deleteAll(config) / history(memoryId)
Same interface patterns. Note: OSS `update` takes a string for data, not an object.
Same interface patterns, with these differences:
- `getAll({ filters, topK?, showExpired? })` needs an entity ID in `filters` and has no `page`/`pageSize` (`topK` defaults to 20).
- `deleteAll({ userId?, agentId?, runId? })` takes top-level IDs and requires at least one. Use `reset()` to wipe everything.
- `update` takes a string or `{ text?, metadata?, expirationDate? }` and returns `{ message }`.
- `history` returns raw rows `{ id, memory_id, previous_value, new_value, action, created_at, updated_at, is_deleted }` (snake_case, newest first), not the hosted client's `oldMemory` / `newMemory` / `event`.
```typescript
await m.update('mem-id', 'new content');
await m.update('mem-id', { text: 'new content', metadata: { verified: true } });
```
#### reset()
@@ -371,7 +455,7 @@ await m.reset();
| Aspect | Platform (`MemoryClient`) | OSS (`Memory`) |
|--------|--------------------------|----------------|
| **Import** | `import MemoryClient from 'mem0ai'` | `import { Memory } from 'mem0ai/oss'` |
| **Auth** | API key required (`MEM0_API_KEY`) | No API key -- config-based |
| **Auth** | API key required (`apiKey` option) | No Mem0 API key -- config-based |
| **Execution** | API calls to `api.mem0.ai` | Local execution |
| **Infrastructure** | Fully managed | Self-managed vector DB, embedder, LLM |
| **Param style** | Top-level: `camelCase` (`userId`, `topK`), filter keys: `snake_case` (`user_id`) | Top-level: `camelCase` (`userId`, `topK`), filter keys: `snake_case` (`user_id`) |
@@ -380,14 +464,15 @@ await m.reset();
| **Export** | `createMemoryExport` | Not available |
| **Feedback** | `feedback()` | Not available |
| **Project mgmt** | `getProject`, `updateProject` | Not available |
| **User listing** | `users()`, `deleteUser()` | Not available |
| **User listing** | `users()`, `deleteUsers()` | Not available |
| **Profiles** | `getProfile`, `generateProfile`, profile settings | Not available |
| **History** | Platform-managed | SQLite (configurable) |
---
## v2 Compatibility
If you're using SDK v2.x:
If you're migrating from TS SDK 2.x (the pre-V3 line):
**Naming Changes:**
- Top-level params now use camelCase: `topK`, `rerank` (not `top_k`)
@@ -406,13 +491,17 @@ await client.search("query", { filters: { user_id: "alice" }, topK: 20 });
**Default Changes:**
| Param | v2 | v3 |
|-------|----|----|
| `topK` | 100 | 20 |
| `threshold` | none | 0.1 |
| `rerank` | true | false |
| `topK` (OSS) | 100 | 20 |
| `threshold` | 0.3 (Platform), none (OSS) | server-side cutoff (Platform), 0.1 (OSS) |
| `rerank` | false (Platform), true (OSS) | false |
Platform `topK` defaults to 10 (max 1000).
**Removed:**
- `OutputFormat` and `API_VERSION` enums
- `organizationId`, `projectId` from constructor
- `enableGraph`, `asyncMode`, `outputFormat`, `immutable`, `expirationDate`, `filterMemories`, `batchSize`, `forceAddOnly`, `includes`, `excludes`, `keywordSearch`
- `organizationId`, `projectId`, `organizationName`, `projectName` from the constructor
- `add()`: `enableGraph`, `asyncMode`, `outputFormat`, `immutable`, `filterMemories`, `batchSize`, `forceAddOnly`, `includes`, `excludes`, `keywordSearch`
- `search()` and `getAll()`: `enableGraph`
- OSS config: `customPrompt` (now `customInstructions`), `enableGraph` and `graphStore`
See the [v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for details.
+87 -52
View File
@@ -21,7 +21,7 @@ from mem0 import MemoryClient
client = MemoryClient(api_key="m0-xxx")
```
**Constructor:** `MemoryClient(api_key=None)`. If `api_key` is not provided, reads from `MEM0_API_KEY` environment variable. Raises `ValueError` if no key found.
**Constructor:** `MemoryClient(api_key=None, host=None, client=None)`. If `api_key` is not provided, reads from `MEM0_API_KEY` environment variable. Raises `ValueError` if no key found. `host` overrides the base URL and `client` accepts a custom `httpx.Client`. `MEM0_SOURCE`, `MEM0_APPLICATION` and `MEM0_CLIENT_STACK` set the request identity headers for wrappers.
- HTTP library: `httpx`
- Timeout: 300 seconds
@@ -45,7 +45,7 @@ Same methods as `MemoryClient`, all `async`/`await`. Supports async context mana
### Memory Methods
#### add(messages, **kwargs)
#### add(messages, options=None, **kwargs)
Store new memories from messages.
@@ -68,11 +68,13 @@ client.add(messages, user_id="alice")
| `infer` | bool | True | If False, store raw text without LLM inference |
| `custom_categories` | list | None | Override project categories |
| `custom_instructions` | str | None | Override extraction instructions |
| `timestamp` | int \| float \| str | None | Custom timestamp (Unix epoch or ISO 8601) |
| `agent_custom_instructions` | str | None | Extraction instructions for agent-scoped memories |
| `expiration_date` | str | None | `YYYY-MM-DD`, memory is hidden after this date |
| `timestamp` | int | None | Custom timestamp (Unix epoch seconds) |
**Returns:** `dict` -- list of events: `[{"id": "...", "event": "ADD", "data": {"memory": "..."}}]`
**Returns:** `dict` -- asynchronous by default: `{"event_id": "...", "status": "PENDING"}`. Poll `GET /v1/event/{event_id}/` until `SUCCEEDED`. With `infer=False` it is synchronous and returns the stored `results`.
#### search(query, **kwargs)
#### search(query, options=None, **kwargs)
Search memories by semantic similarity.
@@ -88,9 +90,13 @@ for mem in results.get("results", []):
| `filters` | dict | None | Filter object with entity IDs and/or `AND`/`OR`/`NOT` conditions (e.g., `{"user_id": "alice"}`) |
| `top_k` | int | 10 | Number of results |
| `rerank` | bool | False | Enable deep semantic reranking (+150-200ms) |
| `threshold` | float | 0.1 | Minimum similarity score |
| `fields` | list | None | Specific fields to return |
| `categories` | list | None | Filter by category |
| `threshold` | float | server-side | Relevance cutoff (0.0 to 1.0), applied before score blending, so it is not a floor on the returned `score`. The default and `0.0` returned the same or nearly the same results in live tests |
| `fields` | - | - | Not applied in v3 |
| `categories` | - | - | Not applied in v3. Use `filters={"AND": [{"categories": {"in": [...]}}]}` |
| `metadata` | - | - | Not applied in v3. Use `filters={"AND": [{"metadata": {...}}]}` |
| `reference_date` | str \| int | None | Anchor for relative time queries (`YYYY-MM-DD`, ISO datetime, or Unix epoch) |
| `show_expired` | bool | False | Include memories past their `expiration_date` |
| `latest_only` | bool | None | Only return the latest version of a memory |
**Returns:** `dict` -- `{"results": [{id, memory, user_id, categories, score, created_at, ...}]}`
@@ -104,9 +110,9 @@ memory = client.get(memory_id="ea925981-...")
**Returns:** `dict` -- full memory object
#### get_all(**kwargs)
#### get_all(options=None, **kwargs)
Retrieve all memories with optional filtering. Requires at least one entity identifier.
Retrieve all memories with optional filtering. Requires non-empty `filters`; scope them with at least one entity identifier.
```python
memories = client.get_all(filters={"user_id": "alice"})
@@ -117,15 +123,17 @@ memories = client.get_all(filters={"AND": [{"user_id": "alice"}, {"categories":
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `filters` | dict | None | Filter object with entity IDs and/or `AND`/`OR`/`NOT` conditions |
| `top_k` | int | None | Limit results |
| `page` | int | None | Page number |
| `page_size` | int | None | Results per page |
| `page` | int | 1 | Page number |
| `page_size` | int | 100 | Results per page (max 200) |
| `start_date` / `end_date` / `categories` | - | - | Not applied in v3. Use `filters` with `created_at` or `categories` |
| `show_expired` | bool | False | Include expired memories |
| `latest_only` | bool | None | Only return the latest version of a memory |
**Returns:** `dict` -- `{"results": [...]}`
**Returns:** `dict` -- `{"count": int, "next": str | None, "previous": str | None, "results": [...]}`
#### update(memory_id, text=None, metadata=None, timestamp=None)
#### update(memory_id, options=None, **kwargs)
Update a memory's content, metadata, or timestamp. At least one parameter required.
Update a memory's `text`, `metadata`, `timestamp`, or `expiration_date` (pass `expiration_date=None` to clear it). At least one required.
```python
client.update("ea925981-...", text="Updated: vegan since 2024")
@@ -134,17 +142,18 @@ client.update("ea925981-...", metadata={"verified": True})
**Returns:** `dict` -- updated memory
#### delete(memory_id)
#### delete(memory_id, delete_linked=False)
Permanently delete a single memory.
Permanently delete a single memory. With `delete_linked=True`, also deletes the older memories it superseded.
```python
client.delete("ea925981-...")
client.delete("ea925981-...", delete_linked=True)
```
#### delete_all(**kwargs)
#### delete_all(options=None, **kwargs)
Delete all memories matching filters. Irreversible.
Delete all memories matching the entity IDs (`user_id`, `agent_id`, `app_id`, `run_id`, passed as top-level kwargs). Irreversible.
```python
client.delete_all(user_id="alice")
@@ -156,7 +165,7 @@ Get the change history of a memory.
```python
history = client.history("ea925981-...")
# Returns: [{previous_value, new_value, action, timestamps}]
# Returns: [{id, memory_id, input, old_memory, new_memory, event, user_id, categories, metadata, created_at, updated_at}]
```
---
@@ -170,10 +179,12 @@ Update up to 1000 memories in a single request.
```python
client.batch_update([
{"memory_id": "uuid-1", "text": "Updated text"},
{"memory_id": "uuid-2", "text": "Another update", "metadata": {"verified": True}},
{"memory_id": "uuid-2", "text": "Another update"},
])
```
Each item must include `text`. `metadata` on a batch item is ignored and a metadata-only item returns a 400. Use `update(memory_id, metadata=...)` to change metadata.
#### batch_delete(memories)
Delete up to 1000 memories in a single request.
@@ -208,7 +219,7 @@ client.delete_users(user_id="alice")
#### reset()
Delete ALL users, agents, sessions, and memories. Complete data reset.
Delete ALL users, agents, sessions, and memories. Complete data reset. It only deletes the entities on the first page returned by `users()`, so with many entities re-run it until it raises `No entities to delete`, or delete per entity with `delete_users(user_id=...)`.
```python
client.reset()
@@ -223,16 +234,14 @@ client.reset()
Create a structured export of memories.
```python
import json
schema = json.dumps({
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"preferences": {"type": "array", "items": {"type": "string"}},
}
})
export = client.create_memory_export(schema=schema, user_id="alice")
}
export = client.create_memory_export(schema=schema, filters={"AND": [{"user_id": "alice"}]})
```
#### get_memory_export(**kwargs)
@@ -269,6 +278,19 @@ client.feedback(
---
### User Profiles
```python
profile = client.get_profile("alice")
client.generate_profile("alice")
client.get_profile_settings()
client.update_profile_settings(enabled=True)
```
`get_profile` returns `status` (`succeeded`, `pending`, `failed`, `not_enabled`, `insufficient_data`) plus `profile`; generation is asynchronous, so branch on `status`. Other methods: `sample_profiles(limit=None)` and `get_profile_job(job_id_or_status_url)`.
---
### Webhooks
```python
@@ -284,10 +306,10 @@ webhook = client.create_webhook(
)
# Update
client.update_webhook(webhook_id=123, name="Updated", url="https://new-url.com")
client.update_webhook(webhook_id="wh_123", name="Updated", url="https://new-url.com")
# Delete
client.delete_webhook(webhook_id=123)
client.delete_webhook(webhook_id="wh_123")
```
---
@@ -304,7 +326,9 @@ config = client.project.get(fields=["custom_categories", "custom_instructions"])
client.project.update(
custom_instructions="Extract dietary preferences and health info",
custom_categories=[{"health": "Medical and dietary info"}],
agent_custom_instructions="Extract only what the agent learned about the user",
multilingual=True,
decay=True,
)
# Create/delete project
@@ -326,6 +350,8 @@ client.project.remove_member(email="user@example.com")
```bash
pip install mem0ai
pip install "mem0ai[nlp]" # optional: spaCy entity linking (Python 3.10-3.12)
pip install "mem0ai[extras]" # optional: fastembed for Qdrant BM25 keyword search
```
### Memory Class
@@ -333,9 +359,11 @@ pip install mem0ai
```python
from mem0 import Memory
m = Memory() # Uses default config (OpenAI embedder + in-memory vector store)
m = Memory() # Defaults: OpenAI gpt-5-mini + text-embedding-3-small, local Qdrant at /tmp/qdrant
```
`Memory(config)` takes a `MemoryConfig` object. For a plain dict use `Memory.from_config(config)`. Requires `OPENAI_API_KEY` for the default LLM and embedder.
**Import:** `from mem0 import Memory` (NOT `MemoryClient` -- that is the Platform client)
### Configuration
@@ -343,28 +371,28 @@ m = Memory() # Uses default config (OpenAI embedder + in-memory vector store)
```python
config = {
"llm": {
"provider": "openai", # openai, groq, azure, ollama, lmstudio, google, anthropic, mistral
"provider": "openai", # openai, anthropic, gemini, groq, ollama, lmstudio, azure_openai, aws_bedrock, together, deepseek, xai, vllm, litellm, ...
"config": {
"model": "gpt-5-mini",
"api_key": "sk-xxx",
}
},
"embedder": {
"provider": "openai", # openai, ollama, azure, lmstudio, google, huggingface
"provider": "openai", # openai, ollama, azure_openai, lmstudio, gemini, vertexai, huggingface, fastembed, aws_bedrock, together
"config": {
"model": "text-embedding-3-small",
"api_key": "sk-xxx",
}
},
"vector_store": {
"provider": "qdrant", # faiss, qdrant, pgvector, redis, supabase, azure_ai_search, memory
"provider": "qdrant", # qdrant (default), chroma, pgvector, pinecone, milvus, redis, supabase, faiss, azure_ai_search, ...
"config": {
"collection_name": "my_memories",
"host": "localhost",
"port": 6333,
}
},
"history_db_path": "history.db", # SQLite path for change history
"history_db_path": "history.db", # SQLite path for change history (default ~/.mem0/history.db)
"custom_instructions": "...", # Custom LLM prompt for extraction
}
@@ -374,7 +402,7 @@ m = Memory.from_config(config)
### Context Manager
```python
with Memory(config) as m:
with Memory.from_config(config) as m:
m.add("I prefer dark mode", user_id="alice")
results = m.search("preferences", filters={"user_id": "alice"})
# SQLite connections released automatically
@@ -384,7 +412,7 @@ with Memory(config) as m:
All methods mirror the Platform client but run locally:
#### add(messages, *, user_id, agent_id, run_id, metadata, infer=True)
#### add(messages, *, user_id, agent_id, run_id, metadata, expiration_date, infer=True, memory_type, prompt)
```python
m.add("I'm a vegetarian", user_id="alice")
@@ -396,9 +424,11 @@ m.add([
At least one of `user_id`, `agent_id`, `run_id` required.
**Returns:** `{"results": [...], "relations": [...]}`
`timestamp` raises `ValueError` in OSS (Platform only).
#### search(query, *, filters=None, top_k=20, threshold=0.1, rerank=False)
**Returns:** `{"results": [{"id": "...", "memory": "...", "event": "ADD"}]}`
#### search(query, *, top_k=20, filters=None, threshold=0.1, rerank=False, explain=False, show_expired=False)
```python
results = m.search("dietary preferences", filters={"user_id": "alice"}, top_k=5)
@@ -406,11 +436,11 @@ results = m.search("dietary preferences", filters={"user_id": "alice"}, top_k=5)
Entity IDs (`user_id`, `agent_id`, `run_id`) must be passed inside the `filters` dict.
Supports filter operators: `eq`, `ne`, `in`, `nin`, `gt`, `gte`, `lt`, `lte`, `contains`, `not_contains`.
Supports filter operators: `eq`, `ne`, `in`, `nin`, `gt`, `gte`, `lt`, `lte`, `contains`, `icontains`, plus `AND` / `OR` / `NOT`. `reference_date` raises `ValueError` in OSS (Platform only).
#### get(memory_id) / get_all(**kwargs) / update(memory_id, data, metadata=None) / delete(memory_id) / delete_all(**kwargs) / history(memory_id)
#### get(memory_id) / get_all(*, filters, top_k=20, show_expired=False) / update(memory_id, text=None, metadata=None, expiration_date) / delete(memory_id) / delete_all(user_id=None, agent_id=None, run_id=None) / history(memory_id)
Same interface as Platform client.
`get_all()` requires entity IDs inside `filters` and returns `{"results": [...]}`. `update()` takes `text` (`data` is a deprecated alias) and needs at least one of `text`, `metadata`, `expiration_date`. `delete_all()` needs at least one entity ID and takes them top-level. `m.project.update()` raises `ValueError` in OSS.
#### reset()
@@ -429,7 +459,7 @@ Release SQLite connections. Called automatically when using context manager.
```python
from mem0 import AsyncMemory
m = AsyncMemory(config)
m = AsyncMemory.from_config(config)
await m.add("text", user_id="alice")
results = await m.search("query", filters={"user_id": "alice"})
```
@@ -459,10 +489,10 @@ results = await m.search("query", filters={"user_id": "alice"})
## v2 Compatibility
If you're using SDK v2.x or the v2 API:
The "v2" line is Python SDK 1.x (TypeScript SDK 2.x). If you are still on it, these are the differences from Python 2.x:
**API Changes:**
- **Entity IDs in search/get_all:** Pass `user_id`, `agent_id` as top-level kwargs instead of inside `filters`
- **Entity IDs in search/get_all:** `user_id`, `agent_id` were top-level kwargs, now they go inside `filters` (top-level raises `ValueError`)
```python
# v2
results = client.search("query", user_id="alice")
@@ -470,18 +500,23 @@ If you're using SDK v2.x or the v2 API:
results = client.search("query", filters={"user_id": "alice"})
```
- **add() returns:** v2 returns ADD, UPDATE, DELETE events; v3 returns ADD only
- **Platform add() is async:** returns `{"event_id": "...", "status": "PENDING"}`
**Default Changes:**
| Param | v2 | v3 |
|-------|----|----|
| `top_k` | 100 | 20 |
| `threshold` | None | 0.1 |
| `rerank` | True | False |
| Param | v2 Platform | v3 Platform | v2 OSS | v3 OSS |
|-------|-------------|-------------|--------|--------|
| `top_k` | 10 | 10 | 100 | 20 |
| `threshold` | 0.3 | server-side cutoff | None | 0.1 |
| `rerank` | False | False | True | False |
**Removed Parameters:**
- Constructor: `org_id`, `project_id`
- add(): `async_mode`, `output_format`, `enable_graph`, `immutable`, `expiration_date`, `filter_memories`, `batch_size`, `force_add_only`, `includes`, `excludes`, `keyword_search`
- add(): `async_mode`, `output_format`, `enable_graph`, `immutable`, `filter_memories`, `batch_size`, `force_add_only`, `includes`, `excludes`, `keyword_search`
- search()/get_all(): `enable_graph`
- Config: `enable_graph`, `graph_store`, `custom_fact_extraction_prompt` (renamed to `custom_instructions`)
- Config: `enable_graph`, `graph_store`, `custom_fact_extraction_prompt` (renamed to `custom_instructions`), `custom_update_memory_prompt` (deprecated)
`expiration_date` is still supported on `add()` and `update()`.
**Graph memory:** the external graph store (Neo4j, Memgraph, Kuzu, AGE) was removed from OSS. Entity linking is built in (spaCy via `mem0ai[nlp]`, stored in a `{collection}_entities` vector collection) and falls back to semantic-only search without it.
See the [v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for full details.
+34 -21
View File
@@ -14,8 +14,16 @@ All endpoints require: `Authorization: Token <MEM0_API_KEY>`
| Get Single Memory | `GET` | `/v1/memories/{memory_id}/` |
| Update Memory | `PUT` | `/v1/memories/{memory_id}/` |
| Delete Memory | `DELETE` | `/v1/memories/{memory_id}/` |
| Memory History | `GET` | `/v1/memories/{memory_id}/history/` |
| Delete by Filter | `DELETE` | `/v1/memories/` (query params `user_id`, `agent_id`, `app_id`, `run_id`, `metadata`; `*` matches all) |
| Batch Update / Delete | `PUT` / `DELETE` | `/v1/batch/` (max 1000 memories per request) |
| Feedback | `POST` | `/v1/feedback/` |
| Create / Get Export | `POST` | `/v1/exports/`, `/v1/exports/get/` |
| List Entities | `GET` | `/v1/entities/` |
| Delete Entity | `DELETE` | `/v2/entities/{entity_type}/{entity_id}/` |
| Event Status | `GET` | `/v1/event/{event_id}/` |
Note: v1/v2 endpoints still work (backward compatible).
The `GET`/`POST` `/v1/memories/`, `POST` `/v2/memories/`, and `POST` `/v1/memories/search/` and `/v2/memories/search/` endpoints are marked deprecated in the OpenAPI spec. Use the `/v3/` endpoints above.
## Memory Object Structure
@@ -26,14 +34,16 @@ Note: v1/v2 endpoints still work (backward compatible).
| `user_id` | string | Associated user |
| `agent_id` | string (nullable) | Agent identifier |
| `app_id` | string (nullable) | Application identifier |
| `run_id` | string (nullable) | Run/session identifier |
| `run_id` | string (nullable) | Run/session identifier (see note below) |
| `metadata` | object | Custom key-value pairs |
| `categories` | array of strings | Auto-assigned category tags |
| `hash` | string | Content hash |
| `expiration_date` | string (nullable) | Date after which the memory is hidden unless `show_expired` is true |
| `created_at` | datetime | Creation timestamp |
| `updated_at` | datetime | Last modification timestamp |
Search results additionally include `score` (relevance metric).
Search results additionally include `score` (relevance metric) and `score_breakdown` (per-signal scores). Get and get-all results additionally include `structured_attributes` (temporal breakdown of the creation time), `replaced_by`, and `synthesized`, and get also returns `lifecycle_state`. Get and get-all omit `app_id` and `run_id` when they are not set.
The run/session identifier is `run_id` in search results but `session_id` in get, get-all, and history results.
## Scoping Identifiers
@@ -46,13 +56,16 @@ Memories can be scoped to different levels:
| Application | `app_id` | Cross-agent app-level memory |
| Run/Session | `run_id` | Session-scoped temporary memory |
**Critical:** Combining `user_id` and `agent_id` in a single AND filter yields empty results. Entities are stored separately. Use `OR` logic or separate queries.
**Critical:** Combining `user_id` and `agent_id` in a single AND filter yields empty results for memories created with `infer=true`. Each extracted fact is attributed to its speaker, so a record carries `user_id` (user messages) or `agent_id` (assistant messages), not both. Use `OR` logic or separate queries. Only Direct Import (`infer=false`) writes both fields on one record. `app_id` and `run_id` are stored on every record.
Filters only constrain the entities you mention. `{"user_id": "alice"}` does not require `agent_id`, `app_id`, or `run_id` to be null.
## Processing Model
- Memories are processed **asynchronously** (v3 default)
- Add responses return queued `ADD` events only (v3 is ADD-only, no UPDATE/DELETE)
- Poll status via `GET /v1/event/{event_id}/`
- Add responses return a `PENDING` event (v3 is ADD-only, no UPDATE/DELETE)
- Poll status via `GET /v1/event/{event_id}/` (`PENDING`, `RUNNING`, `FAILED`, `SUCCEEDED`)
- `infer=false` is synchronous: memories are stored verbatim and the response carries `message` and `results`
## Filter System
@@ -68,7 +81,7 @@ Filters use nested JSON with a logical operator at the root:
}
```
Root must be `AND`, `OR`, or `NOT`. Simple shorthand `{"user_id": "alice"}` also works.
The root can also be a bare condition such as `{"user_id": "alice"}`. Sibling top-level keys in a flat object are implicitly ANDed. Use `AND`, `OR`, or `NOT` for OR/NOT semantics or nesting. An unrecognized top-level key returns a 400.
### Supported Operators
@@ -88,20 +101,21 @@ Root must be `AND`, `OR`, or `NOT`. Simple shorthand `{"user_id": "alice"}` also
| Field | Valid Operators |
|-------|-----------------|
| `user_id`, `agent_id`, `app_id`, `run_id` | `eq`, `ne`, `in`, `*` |
| `created_at`, `updated_at`, `timestamp` | `gt`, `gte`, `lt`, `lte`, `eq`, `ne` |
| `categories` | `eq`, `ne`, `in`, `contains` |
| `metadata` | `eq`, `ne`, `contains` (top-level keys only) |
| `created_at`, `updated_at`, `timestamp`, `expiration_date` | `gt`, `gte`, `lt`, `lte`, `ne` (explicit `eq` is rejected, pass a bare value; `in` fails with a 503 in search and a 500 in get-all) |
| `categories` | `in`, `contains` (`eq` and `ne` are rejected) |
| `metadata` | `eq`, `ne`, `contains` (top-level keys only). `contains` is case-sensitive and matches the whole stored value or one member of a list value, not a substring. `icontains` is rejected with a 400 |
| `keywords` | `contains`, `icontains` |
| `memory_ids` | `in` |
| `memory_ids` | plain list of UUIDs (no `in`) |
### Filter Constraints
1. **Entity scope partitioning:** `user_id` AND `agent_id` in one `AND` block yields empty results.
1. **Entity scope partitioning:** `user_id` AND `agent_id` in one `AND` block yields empty results (except for Direct Import records).
2. **Metadata limitations:** Only top-level keys. Only `eq`, `contains`, `ne`. No `in` or `gt`.
3. **Operator syntax:** Use `gte`, `lt`, `ne`. SQL-style (`>=`, `!=`) rejected.
4. **Entity filter required for get-all:** At least one of `user_id`, `agent_id`, `app_id`, or `run_id`.
3. **Operator syntax:** Use `gte`, `lt`, `ne`. SQL-style (`>=`, `!=`) rejected. There is no `nin` on Platform: use `{"NOT": [{"categories": {"in": [...]}}]}`.
4. **Filters required for get-all:** Empty or missing `filters` return a 400. Scope the listing with at least one of `user_id`, `agent_id`, `app_id`, or `run_id` (search enforces this, get-all does not).
5. **Wildcard excludes null:** `*` matches only non-null values.
6. **Date format:** ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). Timezone-naive defaults to UTC.
7. **Keyword filter:** `keywords` works in `get_all` filters but returns a 503 inside `search()` filters (MEM-5746). Pass the text as `query` for search.
## Response Formats
@@ -109,13 +123,12 @@ Root must be `AND`, `OR`, or `NOT`. Simple shorthand `{"user_id": "alice"}` also
```json
{
"message": "Memory processing has been queued for background execution",
"status": "PENDING",
"event_id": "evt-uuid"
"event_id": "evt-uuid",
"status": "PENDING"
}
```
v3 is ADD-only. No UPDATE or DELETE events.
v3 is ADD-only. No UPDATE or DELETE events. With `infer=false` the call is synchronous and the response also carries `message` and `results` (`[{"id": ..., "data": {"memory": ...}, "event": "ADD"}]`).
### Search Response
@@ -134,7 +147,7 @@ v3 is ADD-only. No UPDATE or DELETE events.
}
```
In v3, `score` is a combined multi-signal relevance score.
In v3, `score` is a combined multi-signal relevance score in [0, 1]. Request defaults: `top_k` 10 (1 to 1000), `rerank` false. `threshold` is a server-side cutoff applied before score blending, not a floor on the returned `score`: the default and `0.0` returned the same or nearly the same results in live tests, including scores below 0.1.
### Get All Response (v3)
@@ -147,4 +160,4 @@ In v3, `score` is a combined multi-signal relevance score.
}
```
v3 returns paginated envelope. Use `page` and `page_size` query params.
v3 returns paginated envelope. Use `page` (default 1) and `page_size` (default 100, max 200) query params.
+25 -20
View File
@@ -23,9 +23,10 @@ Mem0 is a managed memory layer that sits between your AI application and users.
User Input → Retrieve relevant memories → Enrich LLM prompt → Generate response → Store new memories
```
Mem0 handles the complexity of extraction, deduplication, conflict resolution, and semantic retrieval so your application only needs to call `search()` and `add()`.
Mem0 handles the complexity of extraction, deduplication, and hybrid retrieval so your application only needs to call `search()` and `add()`.
**Storage architecture:**
- **SQL database**: Facts and metadata (the source of truth for each memory)
- **Vector store**: Embeddings for semantic similarity search
- **Entity store**: Automatic entity linking for relationship-aware retrieval
@@ -64,20 +65,22 @@ Messages In
v3 processes memories asynchronously by default:
- API returns immediately: `{"status": "PENDING", "event_id": "evt-..."}`
- Poll status via `GET /v1/event/{event_id}/`
- Poll status via `GET /v1/event/{event_id}/` (the TS client camelCases response keys, so it sees `eventId`)
- Use webhooks for completion notifications
- `infer=False` is the exception: it runs synchronously and returns `message` and `results`
### Extraction modes
**Inferred (`infer=True`, default):**
- LLM extracts structured facts from conversation
- Conflict resolution deduplicates and resolves contradictions
- LLM extracts structured facts from conversation, including facts stated by the assistant
- Redundant facts are removed, but nothing is overwritten: when a fact changes, both versions are kept with temporal context
- Best for: natural conversation → memory
**Raw (`infer=False`):**
- Stores text exactly as provided, no LLM processing
- Skips conflict resolution — same fact can be stored twice
- Only `user` role messages are stored; `assistant` messages ignored
- Skips semantic duplicate detection (same fact in different words can be stored twice); exact repeats are deduplicated by hash
- `user` and `assistant` messages are stored, one memory per message; `system` messages are dropped
- Structured multimodal content (`image_url`, `pdf_url`, `txt_url`, `mdx_url`) is rejected with a 400, not skipped
- Best for: bulk imports, pre-structured data, migrations
**Warning:** Don't mix `infer=True` and `infer=False` for the same data — the same fact will be stored twice.
@@ -101,6 +104,7 @@ Query In
│ 2. PARALLEL SCORING │ Semantic search (vector similarity)
│ │ BM25 keyword search (term matching)
│ │ Entity matching (entity graph boost)
│ │ Temporal scoring (time metadata vs query intent)
└─────────┬───────────┘
│
▼
@@ -117,18 +121,17 @@ Query In
| Parameter | Default | Notes |
|-----------|---------|-------|
| `top_k` | 20 | Was 100 in v2 |
| `threshold` | 0.1 | Was None in v2 |
| `rerank` | False | Was True in v2 |
| `top_k` | 10 | Range 1-1000 (OSS default is 20) |
| `threshold` | server-side cutoff | Not a floor on the returned `score`. On Platform v3 the default and `0.0` return the same or nearly the same results. Was 0.3 on Platform v2 (None on OSS v2) |
| `rerank` | False | Was False on Platform v2 (True on OSS v2) |
### Implicit null scoping
### Unmentioned entities are not constrained
When you search with `filters={"user_id": "alice"}` only, Mem0 returns memories where `agent_id`, `app_id`, and `run_id` are all null. This prevents cross-scope leakage by default.
When you search with `filters={"user_id": "alice"}` only, Mem0 matches on `user_id` alone. It does not require `agent_id`, `app_id`, or `run_id` to be null, so records that also carry those fields are returned.
To include memories with non-null fields, use explicit filters:
To narrow to a scope, name the entity explicitly:
```python
# Gets memories for alice regardless of agent/app/run
filters={"OR": [{"user_id": "alice"}]}
filters={"AND": [{"user_id": "alice"}, {"run_id": "session_123"}]}
```
---
@@ -149,7 +152,7 @@ v3 uses ADD-only extraction. Memories accumulate over time rather than being con
### Deletion
- Single: `client.delete(memory_id)`
- Batch: `client.batch_delete([...])`
- Bulk: `client.delete_all(filters={"user_id": "alice"})`
- Bulk: `client.delete_all(user_id="alice")`
---
@@ -185,13 +188,15 @@ v3 uses ADD-only extraction. Memories accumulate over time rather than being con
| `user_id` | string | Primary entity scope |
| `agent_id` | string | Agent scope |
| `app_id` | string | Application scope |
| `run_id` | string | Session/run scope |
| `run_id` | string | Session/run scope (named `session_id` in get, get-all, and history responses) |
| `metadata` | object | Custom key-value pairs for filtering |
| `categories` | array | Auto-assigned or custom category tags |
| `expiration_date` | string | Date after which the memory is hidden unless `show_expired` is true |
| `created_at` | datetime | Creation timestamp |
| `updated_at` | datetime | Last modification timestamp |
| `structured_attributes` | object | Temporal breakdown for time-based queries |
| `score` | float | Semantic similarity (search results only, 0-1) |
| `lifecycle_state` | string | Lifecycle state of the memory (returned by get) |
| `score` | float | Combined multi-signal relevance (search results only, 0-1) |
---
@@ -208,12 +213,12 @@ Mem0 separates memories across four dimensions to prevent data mixing:
### Storage model
Each entity combination creates separate records. A memory with `user_id="alice"` is stored separately from one with `user_id="alice"` + `agent_id="bot"`.
`app_id` and `run_id` are stored on every record the call produces. `user_id` and `agent_id` behave differently on the default extraction path: each extracted fact is attributed to whoever stated it, so a record carries `user_id` (from `user` messages) or `agent_id` (from `assistant` messages), not both. Only Direct Import (`infer=False`) writes both on one record.
### Critical: cross-entity queries
```python
# This returns NOTHING — user and agent memories are stored separately
# This returns NOTHING for inferred memories: a record has user_id OR agent_id, not both
filters={"AND": [{"user_id": "alice"}, {"agent_id": "bot"}]}
# Use OR to query multiple scopes
@@ -327,4 +332,4 @@ def chat(user_input: str, user_id: str, session_id: str) -> str:
| **RAG over documents** | Good for static knowledge | No personalization, no memory updates |
| **Mem0 Platform** | Managed extraction + dedup + graph + scoping | External dependency, async processing delay |
Mem0 combines the best of vector search (semantic retrieval) with automatic extraction (LLM-powered), conflict resolution (deduplication), and structured scoping (multi-tenancy) — in a single managed API.
Mem0 combines the best of vector search (semantic retrieval) with automatic extraction (LLM-powered), deduplication, and structured scoping (multi-tenancy), in a single managed API.
+80 -80
View File
@@ -23,6 +23,7 @@ v3 uses multi-signal hybrid search combining:
- **Semantic search** (vector similarity)
- **BM25 keyword search** (normalized term matching)
- **Entity matching** (entity graph boost)
- **Temporal reasoning** (Platform only): memories whose event dates match time expressions in the query ("last week", "as of March 2025") get a boost
This is automatic — no configuration needed.
@@ -56,10 +57,10 @@ v3 replaces graph memory with built-in entity linking. Entities (proper nouns, q
### How It Works
1. **Extraction**: During `add()`, entities are automatically extracted from memory text
2. **Storage**: Entities are stored in a parallel collection (`{collection}_entities`)
2. **Storage**: Entities are stored in a parallel collection (OSS: `{collection}_entities`; on Platform the entity store is managed for you)
3. **Retrieval**: During `search()`, query entities are matched and used to boost relevant memories
Entity linking is automatic — no configuration required. The boost is folded into the combined `score` on each result.
Entity linking is automatic, no configuration required. The boost is folded into the combined `score` on each result. On Platform this is Graph Memory: built in on all plans, no external graph store to provision, and the dashboard Graph view is Pro and Enterprise only.
### v2 Migration Note
@@ -141,6 +142,32 @@ client.project.update(custom_instructions="Your guidelines here...")
await client.updateProject({ customInstructions: "Your guidelines here..." });
```
### Agent Custom Instructions
`agent_custom_instructions` (Python SDK 2.0.17+, TypeScript SDK 3.1.5+) is a second set of extraction rules that applies only to agent-scoped memories. It is unset by default, and while unset `custom_instructions` applies to every memory.
```python
client.project.update(
custom_instructions="Extract the user's preferences, goals, and constraints.",
agent_custom_instructions="Extract tools that failed, and retry strategies that worked.",
)
```
```javascript
await client.updateProject({
customInstructions: "Extract the user's preferences, goals, and constraints.",
agentCustomInstructions: "Extract tools that failed, and retry strategies that worked.",
});
```
| The `add` call passes | Instructions applied |
|-----------------------|----------------------|
| `user_id` only | `custom_instructions` |
| `agent_id` only | `agent_custom_instructions` |
| `user_id` and `agent_id` | `agent_custom_instructions` for memories attributed to the assistant, `custom_instructions` for the rest |
Both fields can also be passed per `add` call to override the project setting for that call. Clear the project value with an empty string.
### Template Structure
1. **Task Description** -- brief extraction overview
@@ -194,8 +221,11 @@ for item in feedback_data:
**TypeScript:**
```typescript
await client.feedback('mem-123', {
feedback: 'POSITIVE',
import { Feedback } from 'mem0ai';
await client.feedback({
memoryId: 'mem-123',
feedback: Feedback.POSITIVE,
feedbackReason: 'Accurately captured dietary preference',
});
```
@@ -209,8 +239,6 @@ Create structured exports of memories using customizable schemas with filters.
### Usage
```python
import json
# Define export schema
schema = {
"type": "object",
@@ -223,8 +251,8 @@ schema = {
# Create export
response = client.create_memory_export(
schema=json.dumps(schema),
filters={"user_id": "alice"},
schema=schema,
filters={"AND": [{"user_id": "alice"}]},
export_instructions="Create comprehensive profile based on all memories"
)
@@ -238,33 +266,39 @@ result = client.get_memory_export(memory_export_id=response["id"])
## Group Chat
Process multi-participant conversations and automatically attribute memories to individual speakers.
Process multi-participant conversations and keep a separate memory profile per speaker. Scope comes only from the `user_id`, `agent_id`, and `run_id` you pass to `add()`: Mem0 does not infer it from the conversation.
### Usage
```python
messages = [
{"role": "user", "name": "Alice", "content": "I think we should use React for the frontend"},
{"role": "user", "name": "Bob", "content": "I prefer Vue.js, it's simpler for our use case"},
{"role": "assistant", "content": "Both are great choices. Let me note your preferences."},
]
Call `add()` once per participant with their own `user_id`, and share a `run_id` for the session:
# Mem0 automatically attributes memories to each speaker
response = client.add(messages, run_id="team_meeting_1")
```python
client.add(
[{"role": "user", "content": "I think we should use React for the frontend"}],
user_id="alice", run_id="team_meeting_1",
)
client.add(
[{"role": "user", "content": "I prefer Vue.js, it's simpler for our use case"}],
user_id="bob", run_id="team_meeting_1",
)
# Retrieve Alice's memories from that session
alice_mems = client.get_all(
filters={"AND": [{"user_id": "alice"}, {"run_id": "team_meeting_1"}]}
)
session_mems = client.get_all(
filters={"AND": [{"user_id": "*"}, {"run_id": "team_meeting_1"}]}
)
```
Use the `name` field in messages to identify speakers. Mem0 maps names to entity scopes automatically.
A `name` field on a message is stored as extraction context only. Passing messages from two different `name`s in one `add()` call does not split the memories: they all land under the `user_id` you passed.
---
## MCP Integration
Model Context Protocol integration enables AI clients (Claude, Claude Code, Cursor, Windsurf, VS Code, OpenCode) to manage Mem0 memory autonomously.
Model Context Protocol integration enables AI clients (Claude, Claude Code, Codex, Cursor, Windsurf, VS Code, OpenCode) to manage Mem0 memory autonomously.
### Setup
@@ -275,15 +309,17 @@ npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "claude,claude code,cursor,windsurf,vscode,opencode"
--clients "claude code,cursor,windsurf,vscode,opencode"
```
Claude Desktop rejects `mcp-add`: add it under Settings > Connectors instead. Codex reads `~/.codex/config.toml` (TOML, server name `mem0`). The first tool call opens a browser sign-in, or send your API key as a bearer token for headless environments.
### Available MCP Tools
The MCP server exposes 9 memory tools that AI agents can use autonomously:
- Add, search, get, update, delete memories
- Get history, list users, delete users
- Search Mem0 documentation
The MCP server exposes 11 memory tools that AI agents can use autonomously:
- `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`
- `delete_memory`, `delete_all_memories`, `delete_entities`
- `list_entities`, `list_events`, `get_event_status`
### How It Works
@@ -307,6 +343,12 @@ Real-time event notifications for memory operations.
| `memory_update` | Memory modified |
| `memory_delete` | Memory removed |
| `memory_categorize` | Memory tagged |
| `ingest_job_completed` | Ingest job finished successfully |
| `ingest_job_partially_completed` | Ingest job finished with some items failed |
| `ingest_job_failed` | Ingest job failed entirely |
| `ingest_job_cancelled` | Ingest job cancelled |
The TypeScript `WebhookEvent` enum covers only the four `memory_*` events (`MEMORY_ADDED`, `MEMORY_UPDATED`, `MEMORY_DELETED`, `MEMORY_CATEGORIZED`).
### Create Webhook
@@ -321,6 +363,18 @@ webhook = client.create_webhook(
)
```
```typescript
import { WebhookEvent } from 'mem0ai';
const webhook = await client.createWebhook({
url: 'https://your-app.com/webhook',
name: 'Memory Logger',
eventTypes: [WebhookEvent.MEMORY_ADDED, WebhookEvent.MEMORY_CATEGORIZED],
});
```
TypeScript `createWebhook` takes no `projectId`: it uses the project resolved by the client. `getWebhooks({ projectId })` accepts one optionally.
### Manage Webhooks
```python
@@ -341,6 +395,7 @@ client.delete_webhook(webhook_id="wh_123")
### Payload Structure
The POST body wraps everything in `event_details`.
Memory events contain: ID, data object with memory content, event type (`ADD`/`UPDATE`/`DELETE`).
Categorization events contain: memory ID, event type (`CATEGORIZE`), assigned category labels.
@@ -348,59 +403,4 @@ Categorization events contain: memory ID, event type (`CATEGORIZE`), assigned ca
## Multimodal Support
Mem0 can process images and documents alongside text.
### Supported Media Types
- Images: JPG, PNG
- Documents: MDX, TXT, PDF
### Image via URL
```python
image_message = {
"role": "user",
"content": {
"type": "image_url",
"image_url": {"url": "https://example.com/image.jpg"}
}
}
client.add([image_message], user_id="alice")
```
### Image via Base64
```python
import base64
with open("photo.jpg", "rb") as f:
base64_image = base64.b64encode(f.read()).decode("utf-8")
image_message = {
"role": "user",
"content": {
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}
}
}
client.add([image_message], user_id="alice")
```
### Document (MDX/TXT)
```python
doc_message = {
"role": "user",
"content": {"type": "mdx_url", "mdx_url": {"url": document_url}}
}
client.add([doc_message], user_id="alice")
```
### PDF Document
```python
pdf_message = {
"role": "user",
"content": {"type": "pdf_url", "pdf_url": {"url": pdf_url}}
}
client.add([pdf_message], user_id="alice")
```
`POST /v3/memories/add/` (what `client.add()` calls) accepts only string `content`. Structured multimodal content (`image_url`, `pdf_url`, `txt_url`, `mdx_url`) is rejected with a 400 `Not a valid string.`, with `infer=True` and with `infer=False`. To remember what an image or document says, extract the text yourself and pass it as a plain string.
+45 -37
View File
@@ -38,7 +38,7 @@ prompt = ChatPromptTemplate.from_messages([
def retrieve_context(query: str, user_id: str):
"""Retrieve relevant memories from Mem0"""
memories = mem0.search(query, user_id=user_id)
memories = mem0.search(query, filters={"user_id": user_id})
memory_list = memories['results']
serialized = ' '.join([m["memory"] for m in memory_list])
return [
@@ -66,7 +66,9 @@ def chat_turn(user_input: str, user_id: str) -> str:
Source: [docs.mem0.ai/integrations/crewai](https://docs.mem0.ai/integrations/crewai)
CrewAI has native Mem0 integration via `memory_config`:
Install: `pip install crewai crewai-tools mem0ai`
Newer CrewAI versions removed the `memory_config={"provider": "mem0"}` shortcut on `Crew(...)`. Wire Mem0 in explicitly through `MemoryClient` (retrieve, inject into the task, store). CrewAI's own `ExternalMemory` API is the native alternative; see the [CrewAI memory docs](https://docs.crewai.com/en/concepts/memory) for the shape your version expects.
```python
from crewai import Agent, Task, Crew, Process
@@ -74,7 +76,6 @@ from mem0 import MemoryClient
client = MemoryClient()
# Store user preferences first
messages = [
{"role": "user", "content": "I am more of a beach person than a mountain person."},
{"role": "assistant", "content": "Noted! I'll recommend beach destinations."},
@@ -82,41 +83,41 @@ messages = [
]
client.add(messages, user_id="crew_user_1")
# Create agent
travel_agent = Agent(
role="Personalized Travel Planner",
goal="Plan personalized travel itineraries",
backstory="You are a seasoned travel planner.",
memory=True,
)
def get_user_context(user_id: str, query: str) -> str:
results = client.search(query, filters={"user_id": user_id}).get("results", [])
return "\n".join(f"- {m['memory']}" for m in results)
# Create task
task = Task(
description="Find places to live, eat, and visit in San Francisco.",
expected_output="A detailed list of places to live, eat, and visit.",
agent=travel_agent,
)
def plan_trip(destination: str, user_id: str):
travel_agent = Agent(
role="Personalized Travel Planner",
goal="Plan personalized travel itineraries",
backstory="You are a seasoned travel planner.",
)
user_context = get_user_context(user_id, f"travel preferences for {destination}")
task = Task(
description=f"""Find places to live, eat, and visit in {destination}.
# Setup crew with Mem0 memory
crew = Crew(
agents=[travel_agent],
tasks=[task],
process=Process.sequential,
memory=True,
memory_config={
"provider": "mem0",
"config": {"user_id": "crew_user_1"},
}
)
Known preferences for this user:
{user_context or "No stored preferences yet."}
""",
expected_output=f"A detailed list of places to live, eat, and visit in {destination}.",
agent=travel_agent,
)
crew = Crew(agents=[travel_agent], tasks=[task], process=Process.sequential)
result = crew.kickoff()
client.add([{"role": "user", "content": f"Planned a trip to {destination}."}], user_id=user_id)
return result
result = crew.kickoff()
result = plan_trip("San Francisco", "crew_user_1")
```
`add()` is asynchronous on the Platform, so the seed memories above are not searchable the instant it returns and a `plan_trip` call straight after it can see no preferences. Run the seeding in an earlier session, or wait until the `add` event is `SUCCEEDED` (poll `GET /v1/event/{event_id}/`) before the first `plan_trip`. The `add` at the end of `plan_trip` only writes, so it needs no wait.
---
## Vercel AI SDK
> **Dedicated skill available.** For comprehensive Vercel AI SDK documentation, see the [mem0-vercel-ai-sdk skill](../mem0-vercel-ai-sdk/SKILL.md) ([GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk)).
> **Dedicated skill available.** For comprehensive Vercel AI SDK documentation, see the [mem0-vercel-ai-sdk skill](../../mem0-vercel-ai-sdk/SKILL.md) ([GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk)).
Install: `npm install @mem0/vercel-ai-provider`
@@ -150,7 +151,7 @@ mem0 = MemoryClient()
@function_tool
def search_memory(query: str, user_id: str) -> str:
"""Search through past conversations and memories"""
memories = mem0.search(query, user_id=user_id, top_k=3)
memories = mem0.search(query, filters={"user_id": user_id}, top_k=3)
if memories and memories.get('results'):
return "\n".join([f"- {mem['memory']}" for mem in memories['results']])
return "No relevant memories found."
@@ -209,7 +210,11 @@ result = Runner.run_sync(triage_agent, "Plan a healthy meal for my Italy trip")
Source: [docs.mem0.ai/integrations/pipecat](https://docs.mem0.ai/integrations/pipecat)
Install: `pip install "pipecat-ai[mem0]"`
```python
import os
from pipecat.services.mem0 import Mem0MemoryService
memory = Mem0MemoryService(
@@ -236,8 +241,6 @@ pipeline = Pipeline([
])
```
---
## LangGraph
@@ -266,7 +269,7 @@ def chatbot(state: State):
user_id = state["mem0_user_id"]
# Retrieve relevant memories
memories = mem0.search(messages[-1].content, user_id=user_id)
memories = mem0.search(messages[-1].content, filters={"user_id": user_id})
context = "Relevant context:\n"
for memory in memories["results"]:
context += f"- {memory['memory']}\n"
@@ -344,6 +347,8 @@ Install: `pip install autogen mem0ai`
Multi-agent conversational systems with memory persistence.
```python
import os
from autogen import ConversableAgent
from mem0 import MemoryClient
@@ -359,7 +364,7 @@ agent = ConversableAgent(
def get_context_aware_response(question: str) -> str:
# Retrieve memories for context
relevant_memories = memory_client.search(question, user_id=USER_ID)
relevant_memories = memory_client.search(question, filters={"user_id": USER_ID})
context = "\n".join([m["memory"] for m in relevant_memories.get("results", [])])
prompt = f"""Answer considering previous interactions:
@@ -384,12 +389,15 @@ Beyond the examples above, Mem0 integrates with:
| Framework | Type | Install |
|-----------|------|---------|
| [Mastra](https://docs.mem0.ai/integrations/mastra) | TS agent framework | `npm install @mastra/mem0` |
| [Mastra](https://docs.mem0.ai/integrations/mastra) | TS agent framework | `npm install @mastra/core @mastra/mem0 @ai-sdk/openai zod` |
| [ElevenLabs](https://docs.mem0.ai/integrations/elevenlabs) | Voice AI | `pip install elevenlabs mem0ai` |
| [LiveKit](https://docs.mem0.ai/integrations/livekit) | Real-time voice/video | `pip install livekit-agents mem0ai` |
| [Camel AI](https://docs.mem0.ai/integrations/camel-ai) | Multi-agent framework | `pip install camel-ai[all] mem0ai` |
| [AWS Bedrock](https://docs.mem0.ai/integrations/aws-bedrock) | Cloud LLM provider | `pip install boto3 mem0ai` |
| [Camel AI](https://docs.mem0.ai/integrations/camel-ai) | Multi-agent framework | `pip install "camel-ai>=0.2.0" mem0ai` |
| [AWS Bedrock](https://docs.mem0.ai/integrations/aws-bedrock) | Cloud LLM provider | `pip install mem0ai boto3 opensearch-py` |
| [Dify](https://docs.mem0.ai/integrations/dify) | Low-code AI platform | Plugin-based |
| [Google AI ADK](https://docs.mem0.ai/integrations/google-ai-adk) | Google agent framework | `pip install google-adk mem0ai` |
| [Agno](https://docs.mem0.ai/integrations/agno) | Multimodal agent framework | `pip install agno mem0ai` |
| [Strands Agents](https://docs.mem0.ai/integrations/strands) | AWS agent SDK (native `MemoryStore`) | `pip install mem0-strands` |
| [LangChain Tools](https://docs.mem0.ai/integrations/langchain-tools) | Mem0 tools for any LangChain agent | `pip install langchain_core mem0ai` |
For the general Python pattern (no framework), see the "Common integration pattern" in [SKILL.md](../SKILL.md).
+8 -4
View File
@@ -27,7 +27,7 @@ messages = [
client.add(messages, user_id="user123")
# Search memories
results = client.search("What are my dietary restrictions?", user_id="user123")
results = client.search("What are my dietary restrictions?", filters={"user_id": "user123"})
print(results)
```
@@ -39,7 +39,7 @@ from mem0 import AsyncMemoryClient
client = AsyncMemoryClient(api_key="your-api-key")
await client.add(messages, user_id="user123")
results = await client.search("query", user_id="user123")
results = await client.search("query", filters={"user_id": "user123"})
```
## TypeScript / JavaScript Setup
@@ -74,7 +74,7 @@ console.log(results);
export MEM0_API_KEY="m0-your-api-key"
# Add memory
curl -X POST https://api.mem0.ai/v1/memories/ \
curl -X POST https://api.mem0.ai/v3/memories/add/ \
-H "Authorization: Token $MEM0_API_KEY" \
-H "Content-Type: application/json" \
-d '{
@@ -86,7 +86,7 @@ curl -X POST https://api.mem0.ai/v1/memories/ \
}'
# Search memories
curl -X POST https://api.mem0.ai/v2/memories/search/ \
curl -X POST https://api.mem0.ai/v3/memories/search/ \
-H "Authorization: Token $MEM0_API_KEY" \
-H "Content-Type: application/json" \
-d '{
@@ -97,6 +97,8 @@ curl -X POST https://api.mem0.ai/v2/memories/search/ \
## Sample Response
`add()` is asynchronous and returns `{"event_id": "...", "status": "PENDING"}` (`eventId` on the TS client); poll `GET /v1/event/{event_id}/` until it is `SUCCEEDED`. A search returns:
```json
{
"results": [
@@ -112,6 +114,8 @@ curl -X POST https://api.mem0.ai/v2/memories/search/ \
}
```
The TS client returns the same fields camelCased (`userId`, `createdAt`).
## Next Steps
- [SDK Guide](sdk-guide.md) -- all methods for Python and TypeScript
+36 -13
View File
@@ -24,7 +24,7 @@ import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: 'm0-your-api-key' });
```
Constructor accepts `apiKey` (required) and `host` (optional, default: `https://api.mem0.ai`).
Constructor accepts `apiKey` (required in TypeScript; Python falls back to the `MEM0_API_KEY` env var) and `host` (optional, default: `https://api.mem0.ai`).
---
@@ -58,6 +58,7 @@ await client.add(messages, { userId: "alice", metadata: { source: "onboarding" }
| `run_id` | string | Session identifier |
| `metadata` | object | Custom key-value pairs |
| `infer` | boolean | If `false`, store raw text without inference (default: `true`) |
| `expiration_date` | string | `YYYY-MM-DD`, memory is hidden from search and get_all after this date |
### Advanced Add Options
@@ -109,7 +110,10 @@ const results = await client.search("work experience", {
| `filters` | object | Filter object (AND/OR operators). Use `{"user_id": "..."}` to filter by user |
| `top_k` | number | Number of results (default: 10 for Platform) |
| `rerank` | boolean | Enable reranking for better relevance (default: `false`) |
| `threshold` | number | Minimum similarity score (default: 0.1) |
| `threshold` | number | Server-side relevance cutoff (0 to 1), applied before score blending, so it is not a floor on the returned `score`. The default and `0.0` returned the same or nearly the same results in live tests |
| `reference_date` | string / number | Anchor for relative time queries such as "last week" (epoch, `YYYY-MM-DD`, or ISO datetime) |
| `show_expired` | boolean | Include memories past their `expiration_date` (default: `false`) |
| `latest_only` | boolean | Only return the latest version of a memory |
### Common Filter Patterns
@@ -138,12 +142,11 @@ filters={"AND": [
]}
# Exclude categories with NOT
filters={"AND": [{"user_id": "user_123"}, {"NOT": {"categories": {"in": ["spam", "test"]}}}]}
filters={"AND": [{"user_id": "user_123"}, {"NOT": [{"categories": {"in": ["spam", "test"]}}]}]}
# Multi-dimensional query
filters={"AND": [
{"user_id": "user_123"},
{"keywords": {"icontains": "invoice"}},
{"categories": {"in": ["finance"]}},
{"created_at": {"gte": "2024-01-01T00:00:00Z"}}
]}
@@ -191,7 +194,7 @@ const memory = await client.get("ea925981-...");
const memories = await client.getAll({ filters: { user_id: "alice" } });
```
**Note:** `get_all` requires at least one of `user_id`, `agent_id`, `app_id`, or `run_id` in filters.
**Note:** `get_all` requires non-empty `filters`. Scope them with at least one of `user_id`, `agent_id`, `app_id`, or `run_id`.
---
@@ -215,12 +218,14 @@ await client.update("ea925981-...", { text: "Updated: vegan since 2024" });
**Python:**
```python
client.delete(memory_id="ea925981-...")
client.delete(memory_id="ea925981-...", delete_linked=True)
client.delete_all(user_id="alice") # Irreversible bulk delete
```
**TypeScript:**
```typescript
await client.delete("ea925981-...");
await client.delete("ea925981-...", { deleteLinked: true });
await client.deleteAll({ userId: "alice" });
```
@@ -231,7 +236,7 @@ await client.deleteAll({ userId: "alice" });
**Python:**
```python
history = client.history(memory_id="ea925981-...")
# Returns: [{previous_value, new_value, action, timestamps}]
# Returns: [{id, memory_id, input, old_memory, new_memory, event, user_id, categories, metadata, created_at, updated_at}]
```
**TypeScript:**
@@ -241,8 +246,19 @@ const history = await client.history("ea925981-...");
---
## Batch Operations (TypeScript)
## Batch Operations
**Python:**
```python
client.batch_update([
{"memory_id": "uuid-1", "text": "Updated text"},
{"memory_id": "uuid-2", "text": "Another updated text"},
])
client.batch_delete([{"memory_id": "uuid-1"}, {"memory_id": "uuid-2"}])
```
**TypeScript:**
```typescript
// Batch update
await client.batchUpdate([
@@ -254,6 +270,8 @@ await client.batchUpdate([
await client.batchDelete(["uuid-1", "uuid-2", "uuid-3"]);
```
Each batch update item must include `text`. `metadata` on an item is ignored and a metadata-only item returns a 400.
---
## Additional Methods
@@ -269,8 +287,14 @@ client.delete_users(user_id="alice")
client.feedback(memory_id="...", feedback="POSITIVE", feedback_reason="Accurate extraction")
# Export memories
export = client.create_memory_export(filters={"AND": [{"user_id": "alice"}]})
export = client.create_memory_export(
schema={"type": "object", "properties": {"diet": {"type": "string"}}},
filters={"AND": [{"user_id": "alice"}]},
)
data = client.get_memory_export(memory_export_id=export["id"])
profile = client.get_profile("alice")
client.generate_profile("alice")
```
---
@@ -281,8 +305,8 @@ data = client.get_memory_export(memory_export_id=export["id"])
2. **SQL operators rejected** -- use `gte`, `lt`, etc. Not `>=`, `<`.
3. **Metadata filtering is limited** -- only top-level keys with `eq`, `contains`, `ne`.
4. **Wildcard `*` excludes null** -- only matches non-null values.
5. **Default threshold is 0.1** -- increase for stricter matching.
6. **Async processing** -- memories process asynchronously. Wait 2-3s after `add()` before searching.
5. **Threshold is a server-side cutoff** -- raise it to drop weak matches, but it is applied before score blending, so it is not a floor on the returned `score`. Filter on `score` client-side for a precise cutoff.
6. **Async processing** -- `add()` returns `{"event_id": ..., "status": "PENDING"}` (`eventId` on the TS client). Memories are searchable after the event is `SUCCEEDED` (poll `GET /v1/event/{event_id}/`, or wait a few seconds). `infer=False` is synchronous.
## Naming Conventions
@@ -334,8 +358,8 @@ v3 TypeScript uses camelCase for all parameters:
| Parameter | v2 Default | v3 Default |
|-----------|------------|------------|
| `threshold` | 0.3 | 0.1 |
| `rerank` | (not specified) | `false` |
| `threshold` | 0.3 | server-side cutoff |
| `rerank` | `false` | `false` |
**4. Removed Parameters**
@@ -347,7 +371,6 @@ The following parameters are no longer supported:
| `keyword_search` | Removed from search |
| `filter_memories` | Removed |
| `immutable` | Removed from add |
| `expiration_date` | Removed from add |
| `includes` | Removed from add |
| `excludes` | Removed from add |
| `async_mode` | Removed from add |
+16 -15
View File
@@ -30,7 +30,7 @@ openai_client = OpenAI()
def chat(user_input: str, user_id: str) -> str:
# 1. Retrieve relevant memories
memories = mem0.search(user_input, user_id=user_id)
memories = mem0.search(user_input, filters={"user_id": user_id})
context = "\n".join([f"- {m['memory']}" for m in memories.get("results", [])])
# 2. Generate response with memory context
@@ -99,7 +99,7 @@ async function chat(userInput: string, userId: string): Promise<string> {
### Key Benefits
- Context persists across app restarts — no session management needed
- Memories are automatically deduplicated and updated
- Facts are extracted automatically and accumulate (ADD-only: nothing is overwritten or deleted)
- Works with any LLM provider (OpenAI, Anthropic, etc.)
**Best for:** Fitness coaches, tutors, therapists — any assistant that needs to remember goals across sessions.
@@ -172,7 +172,7 @@ const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });
// Setup categories (one-time)
await client.updateProject({
custom_categories: [
customCategories: [
{ support_tickets: 'Customer issues and resolutions' },
{ billing: 'Payment history and billing questions' },
{ product_feedback: 'Feature requests and feedback' },
@@ -226,7 +226,7 @@ def save_patient_info(user_id: str, information: str):
def consult(user_id: str, question: str) -> str:
# High threshold for medical accuracy
memories = mem0.search(question, user_id=user_id, top_k=5, threshold=0.7)
memories = mem0.search(question, filters={"user_id": user_id}, top_k=5, threshold=0.7)
context = "\n".join([f"- {m['memory']}" for m in memories.get("results", [])])
response = openai_client.chat.completions.create(
@@ -295,7 +295,7 @@ async function consult(userId: string, question: string): Promise<string> {
### Key Benefits
- High threshold (0.7) ensures only confident matches for safety-critical retrieval
- High threshold (0.7) drops weak matches for safety-critical retrieval (it is a server-side cutoff applied before score blending, so check `score` too)
- Session scoping via `run_id` groups related health interactions
- Metadata tagging separates patient info from conversation history
@@ -384,7 +384,7 @@ async function draftContent(userId: string, topic: string): Promise<string> {
- Voice consistency across all content without repeating guidelines
- Scoped sessions let you maintain different style profiles
- Preferences update automatically as you refine them
- Refined preferences are added alongside earlier ones (ADD-only), and retrieval ranks relevance
**Best for:** Marketing teams, technical writers, agencies — consistent voice across all content.
@@ -426,7 +426,7 @@ def search_user_session(query: str, user_id: str, app_id: str, run_id: str):
)
def search_agent_knowledge(query: str, agent_id: str, app_id: str):
"""Search all memories an agent has across all users."""
"""Search facts attributed to an agent (from its assistant messages) in an app."""
return client.search(
query,
filters={
@@ -449,7 +449,7 @@ store_scoped_memory(
# User-scoped query: "What does Cam prefer?"
user_mems = search_user_session("dietary restrictions?", "traveler_cam", "concierge_app", "tokyo-2025")
# Agent-scoped query: "What do all travelers prefer?" (across users)
# Agent-scoped query: facts attributed to the travel_planner agent, not user preferences
agent_mems = search_agent_knowledge("common dietary restrictions?", "travel_planner", "concierge_app")
```
@@ -490,6 +490,7 @@ async function searchAgentKnowledge(query: string, agentId: string, appId: strin
- Full isolation between users, agents, sessions, and apps
- Query at any scope level — user, agent, session, or app-wide
- No memory leakage between tenants
- When `add` gets both `user_id` and `agent_id`, each fact is attributed to its speaker (user messages carry `user_id`, assistant messages carry `agent_id`), so an `AND` of both ids returns nothing; use `OR` (see [entity-scoped memory](https://docs.mem0.ai/platform/features/entity-scoped-memory))
**Best for:** Multi-agent workflows, multi-tenant SaaS — proper isolation at every level.
@@ -516,7 +517,7 @@ Extract dietary preferences, location, interests, and purchase history."""
def personalized_search(user_id: str, query: str, search_results: list) -> str:
# Get user context from memory
memories = mem0.search(query, user_id=user_id, top_k=5)
memories = mem0.search(query, filters={"user_id": user_id}, top_k=5)
user_context = "\n".join([f"- {m['memory']}" for m in memories.get("results", [])])
response = openai_client.chat.completions.create(
@@ -599,7 +600,7 @@ def store_email(user_id: str, sender: str, subject: str, body: str, date: str):
def search_emails(user_id: str, query: str):
return client.search(
query,
filters={"AND": [{"user_id": user_id}, {"categories": {"contains": "email"}}]},
filters={"AND": [{"user_id": user_id}, {"metadata": {"email_type": "incoming"}}]},
top_k=10
)
@@ -608,7 +609,7 @@ def get_emails_from_sender(user_id: str, sender: str):
filters={
"AND": [
{"user_id": user_id},
{"metadata": {"contains": sender}}
{"metadata": {"sender": sender}}
]
}
)
@@ -637,7 +638,7 @@ async function storeEmail(userId: string, sender: string, subject: string, body:
async function searchEmails(userId: string, query: string) {
return client.search(query, {
filters: { AND: [{ user_id: userId }, { categories: { contains: 'email' } }] },
filters: { AND: [{ user_id: userId }, { metadata: { email_type: 'incoming' } }] },
topK: 10,
});
}
@@ -646,7 +647,7 @@ async function searchEmails(userId: string, query: string) {
### Key Benefits
- Rich metadata enables multi-dimensional queries (sender, date, subject)
- Category filtering separates emails from other memory types
- Metadata filtering (`email_type`) separates emails from other memory types
- Semantic search across all email content
**Best for:** Inbox management, email automation — searchable email memories with metadata filtering.
@@ -661,7 +662,7 @@ Every use case follows the same 3-step loop:
```python
# 1. Retrieve relevant context
memories = mem0.search(user_input, user_id=user_id)
memories = mem0.search(user_input, filters={"user_id": user_id})
context = "\n".join([m["memory"] for m in memories.get("results", [])])
# 2. Generate with context
@@ -717,4 +718,4 @@ client.project.update(
## More Examples
For 30+ cookbooks with complete working code: [docs.mem0.ai/cookbooks](https://docs.mem0.ai/cookbooks)
For 30+ cookbooks with complete working code: [docs.mem0.ai/cookbooks/overview](https://docs.mem0.ai/cookbooks/overview)
+86 -99
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python3
"""
Mem0 Documentation Search Agent (Mintlify-based)
Mem0 Documentation Search Agent
On-demand search tool for querying Mem0 documentation without storing content locally.
This tool leverages Mintlify's documentation structure to perform just-in-time
retrieval of technical information from docs.mem0.ai.
This tool searches the docs.mem0.ai llms.txt index and fetches pages as markdown
to perform just-in-time retrieval of technical information.
Usage:
python mem0_doc_search.py --query "how to add graph memory"
@@ -20,137 +20,124 @@ Purpose:
- Search across the full Mem0 documentation site
"""
from __future__ import annotations
import argparse
import json
import re
import sys
import urllib.error
import urllib.parse
import urllib.request
DOCS_BASE = "https://docs.mem0.ai"
SEARCH_ENDPOINT = f"{DOCS_BASE}/api/search"
DOCS_HOST = urllib.parse.urlsplit(DOCS_BASE).netloc
LLMS_INDEX = f"{DOCS_BASE}/llms.txt"
ENTRY_URL = re.compile(r"\((https?://[^)\s]+)\)")
MAX_RESULTS = 20
MAX_PAGE_CHARS = 10000
MAX_INDEX_BYTES = 2_000_000
# Known documentation sections for targeted retrieval
SECTION_MAP = {
"platform": [
"/platform/overview",
"/platform/quickstart",
"/platform/features",
"/platform/features/graph-memory",
"/platform/features/selective-memory",
"/platform/features/custom-categories",
"/platform/features/v2-memory-filters",
"/platform/features/async-client",
"/platform/features/webhooks",
"/platform/features/multimodal-support",
],
"api": [
"/api-reference/memory/add-memories",
"/api-reference/memory/v2-search-memories",
"/api-reference/memory/v2-get-memories",
"/api-reference/memory/get-memory",
"/api-reference/memory/update-memory",
"/api-reference/memory/delete-memory",
],
"open-source": [
"/open-source/overview",
"/open-source/python-quickstart",
"/open-source/node-quickstart",
"/open-source/features",
"/open-source/features/graph-memory",
"/open-source/features/rest-api",
"/open-source/configure-components",
],
"sdks": [
"/sdks/python",
"/sdks/js",
],
"integrations": [
"/integrations",
],
SECTION_PREFIXES = {
"platform": ("/platform/",),
"api": ("/api-reference/",),
"open-source": ("/open-source/", "/components/"),
"integrations": ("/integrations",),
}
def fetch_url(url: str) -> str:
"""Fetch content from a URL."""
def require_docs_url(url: str) -> None:
"""Exit unless the URL is an https URL on the Mem0 docs host."""
parts = urllib.parse.urlsplit(url)
if parts.scheme != "https" or parts.netloc != DOCS_HOST:
sys.exit(f"Refusing to fetch {url}: only {DOCS_BASE} is allowed")
class DocsRedirectHandler(urllib.request.HTTPRedirectHandler):
"""Follow redirects only when they stay on the Mem0 docs host."""
def redirect_request(self, req, fp, code, msg, headers, newurl):
require_docs_url(newurl)
return super().redirect_request(req, fp, code, msg, headers, newurl)
def fetch_url(url: str, max_bytes: int) -> tuple[str, bool]:
"""Fetch at most max_bytes from a docs URL, returning the text and whether it was cut short."""
require_docs_url(url)
opener = urllib.request.build_opener(DocsRedirectHandler)
req = urllib.request.Request(url, headers={"User-Agent": "Mem0DocSearchAgent/1.0"})
try:
with urllib.request.urlopen(req, timeout=15) as resp:
return resp.read().decode("utf-8")
with opener.open(req, timeout=15) as resp:
data = resp.read(max_bytes + 1)
except urllib.error.HTTPError as e:
return f"HTTP Error {e.code}: {e.reason}"
except urllib.error.URLError as e:
return f"URL Error: {e.reason}"
sys.exit(f"Error fetching {url}: HTTP {e.code} {e.reason}")
except OSError as e:
sys.exit(f"Error fetching {url}: {e}")
return data[:max_bytes].decode("utf-8", errors="replace"), len(data) > max_bytes
def index_entries() -> list:
"""Return the page entries listed in the llms.txt index."""
content, truncated = fetch_url(LLMS_INDEX, MAX_INDEX_BYTES)
if truncated:
sys.exit(f"Error: {LLMS_INDEX} exceeds {MAX_INDEX_BYTES} bytes")
return [line.strip()[2:] for line in content.splitlines() if line.strip().startswith("- [")]
def entry_path(entry: str) -> str:
"""Extract the URL path from an llms.txt index entry."""
match = ENTRY_URL.search(entry)
return urllib.parse.urlparse(match.group(1)).path if match else ""
def search_docs(query: str, section: str | None = None) -> dict:
"""
Search Mem0 documentation using Mintlify's search API.
Falls back to the llms.txt index for keyword matching if the API is unavailable.
"""
# Try Mintlify search API first
params = urllib.parse.urlencode({"query": query})
search_url = f"{SEARCH_ENDPOINT}?{params}"
"""Search the llms.txt index for entries matching the query terms, best matches first."""
terms = re.findall(r"[a-z0-9_]+", query.lower())
entries = index_entries()
try:
result = fetch_url(search_url)
data = json.loads(result)
if isinstance(data, dict) and data.get("results"):
results = data["results"]
if section and section in SECTION_MAP:
section_paths = SECTION_MAP[section]
results = [r for r in results if any(r.get("url", "").startswith(p) for p in section_paths)]
return {"source": "mintlify_search", "results": results}
except (json.JSONDecodeError, Exception):
pass
if section in SECTION_PREFIXES:
entries = [e for e in entries if entry_path(e).startswith(SECTION_PREFIXES[section])]
# Fallback: search llms.txt index for matching URLs
index_content = fetch_url(LLMS_INDEX)
query_lower = query.lower()
matching_urls = []
for line in index_content.splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
if query_lower in line.lower():
matching_urls.append(line)
if section and section in SECTION_MAP:
section_paths = SECTION_MAP[section]
matching_urls = [u for u in matching_urls if any(p in u for p in section_paths)]
scored = []
for entry in entries:
haystack = entry.lower()
title = haystack.split("]")[0]
score = sum((term in haystack) + (term in title) for term in terms)
if score:
scored.append((score, entry))
scored.sort(key=lambda item: -item[0])
return {
"source": "llms_txt_index",
"query": query,
"matching_urls": matching_urls[:20],
"suggestion": "Fetch specific URLs for detailed content",
"matching_urls": [entry for _, entry in scored[:MAX_RESULTS]],
"suggestion": "Fetch specific pages with --page <path> for detailed content",
}
def fetch_page(page_path: str) -> dict:
"""Fetch a specific documentation page."""
url = f"{DOCS_BASE}{page_path}" if page_path.startswith("/") else page_path
content = fetch_url(url)
return {"url": url, "content": content[:10000], "truncated": len(content) > 10000}
"""Fetch a specific documentation page as markdown."""
parts = urllib.parse.urlsplit(urllib.parse.urljoin(DOCS_BASE, page_path))
path = parts.path.rstrip("/")
if not path.endswith(".md"):
path = f"{path}.md"
url = urllib.parse.urlunsplit((parts.scheme, parts.netloc, path, "", ""))
content, cut_short = fetch_url(url, MAX_PAGE_CHARS * 4)
return {"url": url, "content": content[:MAX_PAGE_CHARS], "truncated": cut_short or len(content) > MAX_PAGE_CHARS}
def get_index() -> dict:
"""Fetch the full documentation index from llms.txt."""
content = fetch_url(LLMS_INDEX)
urls = [line.strip() for line in content.splitlines() if line.strip() and not line.startswith("#")]
return {"total_pages": len(urls), "urls": urls, "sections": list(SECTION_MAP.keys())}
urls = index_entries()
return {"total_pages": len(urls), "urls": urls, "sections": list(SECTION_PREFIXES)}
def list_section(section: str) -> dict:
"""List all known pages in a documentation section."""
if section not in SECTION_MAP:
return {"error": f"Unknown section: {section}", "available": list(SECTION_MAP.keys())}
return {
"section": section,
"pages": [f"{DOCS_BASE}{p}" for p in SECTION_MAP[section]],
}
"""List the llms.txt index entries in a documentation section."""
if section not in SECTION_PREFIXES:
return {"error": f"Unknown section: {section}", "available": list(SECTION_PREFIXES)}
pages = [e for e in index_entries() if entry_path(e).startswith(SECTION_PREFIXES[section])]
return {"section": section, "pages": pages}
def main():
@@ -206,7 +193,7 @@ def main():
elif "content" in result:
print(f"URL: {result['url']}")
if result.get("truncated"):
print("[Content truncated to 10000 chars]")
print(f"[Content truncated to {MAX_PAGE_CHARS} chars]")
print(result["content"])
elif "error" in result:
print(f"Error: {result['error']}")
@@ -1,3 +1,4 @@
import logging
from unittest.mock import Mock, patch
import pytest
@@ -165,3 +166,35 @@ def test_error_handling(vector_store, mock_vertex_ai):
assert isinstance(exc_info.value, exceptions.InvalidArgument)
assert "Invalid request" in str(exc_info.value)
def test_debug_logs_never_carry_service_account_key(caplog):
"""Regression for #7503: constructor DEBUG lines must not log the inline credential.
The constructor emits the whole kwargs dict and the validated config dump at
DEBUG; when ``service_account_json`` is passed inline that writes the PEM
private key to the log sink. Constructor diagnostics should name the fields,
never their values.
"""
private_key = "-----BEGIN PRIVATE KEY-----\nFAKE-KEY-MATERIAL-FOR-TEST\n-----END PRIVATE KEY-----\n"
kwargs = {
"project_id": "test-project",
"project_number": "123456789",
"region": "us-central1",
"endpoint_id": "test-endpoint",
"index_id": "test-index",
"deployment_index_id": "test-deployment",
"service_account_json": {"type": "service_account", "private_key": private_key},
}
with caplog.at_level(logging.DEBUG, logger="mem0.vector_stores.vertex_ai_vector_search"):
# Fails later at credential load (no network, no valid key); the two
# DEBUG statements under test run before that.
with pytest.raises(Exception):
GoogleMatchingEngine(**kwargs)
logged = caplog.text
assert private_key not in logged
assert "FAKE-KEY-MATERIAL-FOR-TEST" not in logged
# The diagnostics must stay useful: field names are still logged.
assert "service_account_json" in logged