Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1112be3e5e | |||
| 6052252250 | |||
| deca4bd3a5 | |||
| 4cfcd0241a | |||
| b54710a3c3 | |||
| 4cfa98f626 | |||
| b830b99abe | |||
| 45208feebb | |||
| 3ac9ba4b50 | |||
| 965140eb19 | |||
| 6e6f5b8d59 | |||
| dd54e387de | |||
| fd32b980b4 | |||
| 21aae599be | |||
| ea6fd3b457 | |||
| 8ca9a0f2c0 | |||
| 5f77d86caf | |||
| 9ef409222f | |||
| c90bdbdce0 | |||
| 50bdaaea0c | |||
| 38e47ac261 | |||
| d06ea1875c | |||
| c2c3a12838 | |||
| 07e58c54ae | |||
| cf3355e2ca | |||
| 54328ffd97 | |||
| 29fa41558c | |||
| 8d45fb3c9a | |||
| 760dca6f39 | |||
| 74f6dc6f0d | |||
| bcca72e3f0 | |||
| 9c2d6222ce | |||
| 790e190486 | |||
| d4869d24ec | |||
| 1ac3aa7256 | |||
| e168d48e04 | |||
| ea2ee07586 | |||
| 540d23d610 | |||
| 3274390f82 | |||
| b357a5a1b0 | |||
| d653b63fac | |||
| cc4671579f | |||
| 01afdde3e7 | |||
| d6d89c987b | |||
| c2150e8f1a | |||
| 19c7bb84a2 | |||
| e6281ab724 | |||
| a71d7bdbe3 | |||
| 56ec7d20f1 | |||
| ca2abca2b8 | |||
| 0e582adc6c | |||
| 9caffeaa7b | |||
| a58e0586ad | |||
| a9cb4bb644 | |||
| 7bf84b8d38 | |||
| 5e7adc4d12 | |||
| 14393b5962 | |||
| f590c9596a | |||
| dd5f7e39a8 | |||
| 70ab76a053 | |||
| 2af3a72f73 | |||
| 8005e18aec | |||
| 39551145b8 | |||
| c2bc28e589 | |||
| fec2fe6a2c | |||
| d0c23a5950 | |||
| b05cce581b | |||
| 8a57967c8a | |||
| 756b0b1b6d | |||
| 726bcc80b2 | |||
| 9383e9a255 | |||
| ddaa655edf | |||
| 739534c0a3 | |||
| 633b035342 | |||
| ccbe5861a1 | |||
| 50c3cf44f1 | |||
| d6d2588ef5 | |||
| 6c1741e3a4 |
@@ -12,7 +12,7 @@
|
||||
"name": "mem0",
|
||||
"source": "./integrations/mem0-plugin",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.",
|
||||
"version": "0.2.12"
|
||||
"version": "0.2.14"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"name": "mem0",
|
||||
"source": "./integrations/mem0-plugin",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.",
|
||||
"version": "0.2.12"
|
||||
"version": "0.2.14"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,12 +9,10 @@ body:
|
||||
label: Component
|
||||
description: Which part of mem0 is affected?
|
||||
options:
|
||||
- Core / Python SDK
|
||||
- Python SDK
|
||||
- TypeScript SDK
|
||||
- Vector Store (Qdrant, PGVector, Redis, Chroma, etc.)
|
||||
- Graph Memory (Neo4j, Memgraph, etc.)
|
||||
- Ollama / Local Models
|
||||
- OpenClaw
|
||||
- Vector Store
|
||||
- Plugin
|
||||
- REST API
|
||||
- Other
|
||||
validations:
|
||||
|
||||
@@ -9,14 +9,11 @@ body:
|
||||
label: Component
|
||||
description: Which part of mem0 does this relate to?
|
||||
options:
|
||||
- Core / Python SDK
|
||||
- Python SDK
|
||||
- TypeScript SDK
|
||||
- Vector Store (Qdrant, PGVector, Redis, Chroma, etc.)
|
||||
- Graph Memory (Neo4j, Memgraph, etc.)
|
||||
- Ollama / Local Models
|
||||
- OpenClaw
|
||||
- Vector Store
|
||||
- Plugin
|
||||
- REST API
|
||||
- Benchmarks / Evals
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
# Maps dropdown selections to GitHub labels
|
||||
# Used by the advanced-issue-labeler GitHub Action
|
||||
|
||||
component:
|
||||
- label: "sdk-python"
|
||||
matcher: "Core / Python SDK"
|
||||
- label: "sdk-typescript"
|
||||
matcher: "TypeScript SDK"
|
||||
- label: "vector-store"
|
||||
matcher: "Vector Store"
|
||||
- label: "graph-memory"
|
||||
matcher: "Graph Memory"
|
||||
- label: "ollama"
|
||||
matcher: "Ollama"
|
||||
- label: "openclaw"
|
||||
matcher: "OpenClaw"
|
||||
- label: "rest-api"
|
||||
matcher: "REST API"
|
||||
policy:
|
||||
- section:
|
||||
- id: ['component']
|
||||
block-list: ['Other']
|
||||
label:
|
||||
- name: 'sdk-python'
|
||||
keys: ['Python SDK']
|
||||
- name: 'sdk-typescript'
|
||||
keys: ['TypeScript SDK']
|
||||
- name: 'vector-store'
|
||||
keys: ['Vector Store']
|
||||
- name: 'plugin'
|
||||
keys: ['Plugin']
|
||||
- name: 'rest-api'
|
||||
keys: ['REST API']
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"language": {
|
||||
"sdk-python": [
|
||||
"python", "pip install", "pypi", "pyproject", "requirements.txt",
|
||||
"from mem0", "import mem0", "traceback", "pydantic", "asyncmemory",
|
||||
"poetry", "virtualenv", "venv", "conda", "pytest", "async def"
|
||||
],
|
||||
"sdk-typescript": [
|
||||
"typescript", "javascript", "pnpm", "yarn", "node.js", "nodejs",
|
||||
"mem0-ts", "mem0ai/oss", "tsconfig", "await import",
|
||||
"=> {", "undefined is not"
|
||||
]
|
||||
},
|
||||
"area": {
|
||||
"plugin": [
|
||||
"openclaw", "openclaw-mem0", "openclaw.json", "openclaw plugin",
|
||||
"claude code", "opencode", "pi agent", "mem0-plugin",
|
||||
"cursor plugin", "codex plugin", "editor plugin"
|
||||
],
|
||||
"cli": ["mem0-cli", "@mem0/cli", "npx mem0", "command line"],
|
||||
"vector-store": [
|
||||
"pgvector", "pinecone", "chroma", "chromadb", "weaviate",
|
||||
"milvus", "faiss", "vector store", "vectorstore",
|
||||
"elasticsearch", "supabase", "azure ai search",
|
||||
"s3 vectors", "mongodb"
|
||||
],
|
||||
"integrations": [
|
||||
"vercel ai", "vercel-ai-sdk", "@mem0/vercel-ai-provider",
|
||||
"llamaindex", "crewai", "autogen", "langgraph"
|
||||
],
|
||||
"rest-api": [
|
||||
"rest api", "fastapi", "docker-compose", "/v1/memories",
|
||||
"localhost:8000", "localhost:8888", "curl -x", "http endpoint"
|
||||
],
|
||||
"documentation": [
|
||||
"docs.mem0.ai", "documentation", "typo", "readme", "docstring", "broken link",
|
||||
"issue on docs", "docs:", "link to the docs page", "issue with current documentation"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
sdk-python:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'mem0/**'
|
||||
- 'tests/**'
|
||||
- 'cli/python/**'
|
||||
- 'pyproject.toml'
|
||||
- 'poetry.lock'
|
||||
|
||||
sdk-typescript:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'mem0-ts/**'
|
||||
- 'cli/node/**'
|
||||
|
||||
vector-store:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'mem0/vector_stores/**'
|
||||
- 'mem0-ts/src/oss/src/vector_stores/**'
|
||||
|
||||
rest-api:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'server/**'
|
||||
|
||||
integrations:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'integrations/**'
|
||||
|
||||
plugin:
|
||||
- changed-files:
|
||||
- all-globs-to-any-file:
|
||||
- 'integrations/**'
|
||||
- '!integrations/vercel-ai-sdk/**'
|
||||
- any-glob-to-any-file:
|
||||
- 'skills/**'
|
||||
- '.agents/**'
|
||||
- '.claude-plugin/**'
|
||||
- '.codex-plugin/**'
|
||||
- '.cursor-plugin/**'
|
||||
- 'marketplace.json'
|
||||
|
||||
cli:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'cli/**'
|
||||
|
||||
documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'docs/**'
|
||||
- 'examples/**'
|
||||
- '*.md'
|
||||
|
||||
ci:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '.github/**'
|
||||
- 'scripts/**'
|
||||
- '.pre-commit-config.yaml'
|
||||
@@ -0,0 +1,44 @@
|
||||
const fs = require('fs');
|
||||
|
||||
function componentLabels(keywords) {
|
||||
return Object.values(keywords).flatMap(Object.keys);
|
||||
}
|
||||
|
||||
function toMatcher(term) {
|
||||
const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const prefix = /^[a-z0-9]/i.test(term) ? '\\b' : '';
|
||||
return new RegExp(prefix + escaped, 'i');
|
||||
}
|
||||
|
||||
function scoreGroup(text, group) {
|
||||
let winner = null;
|
||||
let best = 0;
|
||||
for (const [label, terms] of Object.entries(group)) {
|
||||
const score = terms.reduce((n, term) => n + (toMatcher(term).test(text) ? 1 : 0), 0);
|
||||
if (score > best) {
|
||||
winner = label;
|
||||
best = score;
|
||||
}
|
||||
}
|
||||
return winner;
|
||||
}
|
||||
|
||||
const UMBRELLA = { plugin: 'integrations' };
|
||||
|
||||
function inferComponentLabels(text, keywords) {
|
||||
if (!text) return [];
|
||||
const labels = [scoreGroup(text, keywords.language), scoreGroup(text, keywords.area)].filter(
|
||||
Boolean,
|
||||
);
|
||||
for (const label of labels.slice()) {
|
||||
const parent = UMBRELLA[label];
|
||||
if (parent && !labels.includes(parent)) labels.push(parent);
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
function loadKeywords(file) {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
}
|
||||
|
||||
module.exports = { componentLabels, inferComponentLabels, loadKeywords };
|
||||
@@ -0,0 +1,102 @@
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const { inferComponentLabels, loadKeywords } = require('./infer-component-labels.js');
|
||||
|
||||
const keywords = loadKeywords(path.join(__dirname, '..', 'component-keywords.json'));
|
||||
|
||||
const cases = [
|
||||
{
|
||||
number: 6210,
|
||||
title: "but(anthropic): sampling parameters returns 400 error for new model",
|
||||
body: "### Component\n\nCore / Python SDK\n\n### Description\n\n### Summary\n\nWhen using Anthropic latest models such as `claude-opus-4-7`, `claude-opus-4-8`, or `claude-sonnet-5`, Mem0 still sends sampling parameters like `temperature` / `top_p`. These models do not support those parameters, causing Anthropic API requests to fail.\n\nSee https://platform.claude.com/docs/en/about-claude/models/migration-guide\n\n### Steps to Reproduce\n\n```python\n from mem0 import Memory\n\n m = Memory.from_config({\n \"llm\": {\n \"provider\": \"anthropic\",\n \"config\": {\n \"model\": \"claude-opus-4-8\",\n \"api_key\": \"your-anthropic-api-key\"\n },\n },\n ...\n })\n```\n\n### Expected Behavior\n\nMem0 should detect Anthropic models that do not support sampling parameters and omit temperature and top_p from the request.\n\nFor models that still support sampling parameters, such as claude-opus-4-6, claude-sonnet-4-6, and claude-haiku-4-5, Mem0 should continue sending supported sampling parameters till they're deprecated.\n\n### Actual Behavior\n\nMem0 includes temperature by default for Anthropic requests. With newer Anthropic models that do not support sampling parameters, the API request fails because unsupported parameters are sent.\n\n### Environment\n\n - mem0 version: 2.0.11\n - Python/Node version: Python 3.11\n - OS: macOS\n",
|
||||
expected: ["sdk-python"],
|
||||
},
|
||||
{
|
||||
number: 5770,
|
||||
title: "feat(ts-sdk): add FastEmbed embedding provider",
|
||||
body: "## Summary\n\nThe Python SDK supports **FastEmbed** as an embedding provider, but the TypeScript OSS SDK (`mem0ai/oss`) does not. Add it to bring the TS SDK to parity.\n\n| | |\n|---|---|\n| Python reference | `mem0/embeddings/fastembed.py` |\n| Registered in (Python) | `mem0/utils/factory.py` (EmbedderFactory) |\n| Target file (TypeScript) | `mem0-ts/src/oss/src/embeddings/fastembed.ts` |\n| Suggested implementation | Use the `fastembed` npm package (ONNX local embeddings). |\n\n## Requirements\n\n- [ ] Implement `FastEmbedEmbedder` in `mem0-ts/src/oss/src/embeddings/fastembed.ts`, extending `Embedder` (`mem0-ts/src/oss/src/embeddings/base.ts`) and mirroring the Python provider's behavior (embed / embedBatch).\n- [ ] Register the `\"fastembed\"` provider in `mem0-ts/src/oss/src/utils/factory.ts` (EmbedderFactory).\n- [ ] Add config typing in `mem0-ts/src/oss/src/types/`.\n- [ ] Add a unit test under `mem0-ts/src/oss/src/tests/`.\n- [ ] Add `fastembed` to `mem0-ts/package.json` (optional/peer dependency, lazy-imported like other providers).\n- [ ] Update docs under `docs/` if this provider is user-facing.\n\n## Reference pattern\n\nMirror an existing TS provider: `embeddings/openai.ts`.\n\n## Notes\n\n`fastembed` (v2.x) is the JS port of Qdrant's FastEmbed — local/offline embeddings. Mirror the default model in `mem0/embeddings/fastembed.py`.\n\n---\n_Part of the TypeScript ↔ Python SDK provider-parity effort. One provider per issue (atomic)._\n",
|
||||
expected: ["sdk-typescript"],
|
||||
},
|
||||
{
|
||||
number: 3940,
|
||||
title: "Milvus database will return distance not similarity score",
|
||||
body: "### 🐛 Describe the bug\n\nMilvus database will return distance not similarity score\n\n## in milvus.py\n\ndef _parse_output(self, data: list):\n \"\"\"\n Parse the output data.\n\n Args:\n data (Dict): Output data.\n\n Returns:\n List[OutputData]: Parsed output data.\n \"\"\"\n memory = []\n\n for value in data:\n uid, score, metadata = (\n value.get(\"id\"),\n value.get(\"distance\"), # here\n value.get(\"entity\", {}).get(\"metadata\"),\n )\n\n memory_obj = OutputData(id=uid, score=score, payload=metadata)\n memory.append(memory_obj)\n\n return memory\n",
|
||||
expected: ["vector-store"],
|
||||
},
|
||||
{
|
||||
number: 5290,
|
||||
title: "Recall search failed: Bad Request Using OpenAI Embedding Model",
|
||||
body: "### Component\n\nOpenClaw\n\n### Description\n\n### Summary\nuse openclaw.json config:\n\n```json\n...\n\"embedder\": {\n \"provider\": \"openai\",\n \"config\": {\n \"model\": \"bge-base-zh-v1.5\",\n \"embedding_dims\": 1024,\n \"embeddingDims\": 1024,\n \"url\": \"https://xxxxxxxxx/v1\",\n \"apiKey\": \"xxxxxxxxxxxx\"\n }\n },\n\"vectorStore\": {\n \"provider\": \"qdrant\",\n \"config\": {\n \"url\": \"http://qdrant:6333\",\n \"apiKey\": \"${QDRANT_API_KEY}\",\n \"collectionName\": \"mem0\",\n \"embeddingModelDims\": 1024\n }\n }\n```\n```\n\nopenclaw log info is:\n\n```\n23:14:20 Api key is used with unsecure connection.\n23:14:21 [mem0] Recall search failed: Bad Request\n23:14:21 [plugins] openclaw-mem0: skills-mode recall (strategy=smart) injecting 0 memories (~20 tokens)\n23:14:22 [ws] ⇄ res ✓ sessions.list 256ms conn=d1eb9bc4…17da id=201b8113…c9dc\n23:14:22 [ws] ⇄ res ✓ sessions.list 264ms conn=d1eb9bc4…17da id=4939f962…2f16\n23:14:34 [ws] ⇄ res ✓ sessions.list 250ms conn=d1eb9bc4…17da id=f7ad503f…baa6\n23:15:12 [mem0] **Recall search failed: Bad Request**\n23:15:12 [plugins] openclaw-mem0: skills-mode recall (strategy=smart) injecting 0 memories (~20 tokens)\n23:15:12 [ws] ⇄ res ✓ sessions.list 288ms conn=d1eb9bc4…17da id=9e20bb86…371e\n23:15:13 [ws] ⇄ res ✓ sessions.list 268ms conn=d1eb9bc4…17da id=3b49a2ad…7ada\n23:15:20 [ws] ⇄ res ✓ sessions.list 235ms conn=d1eb9bc4…17da id=a192da30…069f\n```\n\n### Actual Behavior\n\nembedding model response ok,response message has 1024 vectors,but the vectors are submitted to vector-db:qdrant with all zero vectors,and vectors has only 256 size.\n\n```http\nPOST /collections/mem0/points/search HTTP/1.1\nhost: qdrant:6333\nconnection: keep-alive\nuser-agent: qdrant-js/1.13.0\napi-key: xxxxxxxxxxxxxxxxxxxxxxxxxxxx\nContent-Type: application/json\nAccept: application/json\naccept-language: *\nsec-fetch-mode: cors\naccept-encoding: gzip, deflate\ncontent-length: 651\n\n{\"vector\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"limit\":120,\"offset\":0,\"filter\":{\"must\":[{\"key\":\"user_id\",\"match\":{\"value\":\"agent\"}}]},\"with_payload\":true,\"with_vector\":false}\n\n**HTTP/1.1 400 Bad Request**\ntransfer-encoding: chunked\ncontent-type: application/json\nvary: accept-encoding, Origin, Access-Control-Request-Method, Access-Control-Request-Headers\ncontent-encoding: gzip\n\n```\n\n### Expected Behavior\n\nembedding model response ok by tcpdump, response message has 1024 vectors,and this vectors are submitted to vector-db:qdrant with the same vectors,and vectors has also 1024 size.\n\n\n### Environment\n\n- openclaw-mem0 version: 1.0.11\n- qdrant: 1.13.6\n",
|
||||
expected: ["plugin", "integrations"],
|
||||
},
|
||||
{
|
||||
number: 3696,
|
||||
title: "Cannot set expiration_date for memory in REST API server (Docker Compose)",
|
||||
body: "### 🐛 Describe the bug\n\nI'm using docker compose to deploy a REST API server. When adding memory, I'm unable to set the expiration_date. Is this feature not supported?",
|
||||
expected: ["rest-api"],
|
||||
},
|
||||
{
|
||||
number: 6252,
|
||||
title: "cursor: on_file_read_cursor.sh ignores auto_search / MEM0_AUTO_SEARCH",
|
||||
body: "### Component\n\nCursor / mem0-plugin\n\n### Description\n\n`on_file_read_cursor.sh` never checks `MEM0_AUTO_SEARCH`. In Claude Code, #6065/#6071 added a guard on `on_file_read.sh`, but the Cursor PreToolUse variant still always calls `file_context.py` (and thus Platform search) once `MEM0_API_KEY` is set.\n\n### Expected\n\nWhen `auto_search: false` / `MEM0_AUTO_SEARCH=false`, `on_file_read_cursor.sh` should exit 0 without searching.\n\n### Actual\n\nTimeline search still runs.\n\n### Related\n\n#6065, #6071, #6250\n",
|
||||
expected: ["plugin", "integrations"],
|
||||
},
|
||||
{
|
||||
number: 6032,
|
||||
title: "docs: fix typos and punctuation errors across docs",
|
||||
body: "### Description\n\n### Page\nMultiple pages — see list below.\n\n### What's Wrong or Missing\n1. https://docs.mem0.ai/components/llms/overview — \"a llm\" should be \"an LLM\"\n2. https://docs.mem0.ai/components/vectordbs/dbs/azure — 2 comma splices + \"setup\" used as a verb (should be \"set up\")\n3. https://docs.mem0.ai/components/embedders/models/azure_openai — \"from the Azure.\" is an incomplete sentence\n4. https://docs.mem0.ai/components/llms/models/azure_openai — same incomplete \"from the Azure\" phrasing\n5. https://docs.mem0.ai/cookbooks/companions/voice-companion-openai — \"an important information\" (uncountable noun)\n6. https://docs.mem0.ai/cookbooks/essentials/exporting-memories — comma splice\n7. https://docs.mem0.ai/cookbooks/integrations/tavily-search — \"usecase\" should be \"use case\"\n8. https://docs.mem0.ai/cookbooks/overview — broken parallelism in bullet list\n9. README.md — \"Github App\" should be \"GitHub App\"\n10. https://docs.mem0.ai/platform/overview — table cell not capitalized like other rows\n\n### Suggested Fix\nApply the corrections listed above for each page. I will submit a PR soon addressing all of the issues mentioned.",
|
||||
expected: ["documentation"],
|
||||
},
|
||||
];
|
||||
|
||||
const cliRegressionCase = {
|
||||
number: 3144,
|
||||
title: "Bug Report: Memory Score Does Not Match Expected Relevance in Local Search",
|
||||
body: "### 🐛 Describe the bug\n\n#### Description\n\nWhen using the locally deployed `mem0` server, the returned memory `score` from the `search` interface does not align with the expected semantic relevance. In particular, irrelevant or less relevant memories sometimes receive higher scores than directly related ones.\n\n#### Reproduction Steps\n\n```python\nmem0 = mem0_client(mode=\"local\")\nprint(\"Mem0 client initialized successfully.\")\n\nprint(\"Adding memories...\")\nresult = mem0.add(messages=[\n {\"role\": \"user\", \"content\": \"I like drinking coffee in the morning\"},\n {\"role\": \"user\", \"content\": \"I enjoy reading books at night\"}\n], user_id=\"alice\")\nprint(\"Memory added:\", result)\n\nprint(\"Searching memories...\")\nsearch_result = mem0.search(query=\"coffee\", user_id=\"alice\", top_k=2)\nprint(\"Search results:\", search_result)\n```\n\n#### Actual Output\n\n```json\n{\n \"results\": [\n {\n \"id\": \"5099b5be-c673-4f09-99de-a196f43b6476\",\n \"memory\": \"Likes drinking coffee in the morning\",\n \"score\": 0.5115111920687857\n },\n {\n \"id\": \"08df5c51-c52b-4c45-a5b6-b3f864ea149a\",\n \"memory\": \"Enjoys reading books at night\",\n \"score\": 0.7755568273863331\n }\n ],\n \"relations\": [\n {\"source\": \"coffee\", \"relationship\": \"consumed_in\", \"destination\": \"morning\"},\n {\"source\": \"user_id:_alice\", \"relationship\": \"likes\", \"destination\": \"coffee\"},\n {\"source\": \"user_id:_alice\", \"relationship\": \"likes_drinking\", \"destination\": \"coffee\"},\n {\"source\": \"user_id:_alice\", \"relationship\": \"in_time\", \"destination\": \"morning\"},\n {\"source\": \"user_id:_alice\", \"relationship\": \"drinks_in\", \"destination\": \"morning\"}\n ]\n}\n```\n\n#### Expected Behavior\n\nThe memory `\"Likes drinking coffee in the morning\"` should have a **higher score** than `\"Enjoys reading books at night\"` when querying for `\"coffee\"`, since it is directly semantically related.",
|
||||
};
|
||||
|
||||
let failures = 0;
|
||||
|
||||
function run(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(`PASS ${name}`);
|
||||
} catch (err) {
|
||||
failures++;
|
||||
console.error(`FAIL ${name}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const { number, title, body, expected } of cases) {
|
||||
const text = `${title}
|
||||
|
||||
${body}`;
|
||||
run(`#${number}`, () => {
|
||||
assert.deepStrictEqual(inferComponentLabels(text, keywords), expected);
|
||||
});
|
||||
}
|
||||
|
||||
run('#3144 cliKeywordPrefixSubstringRegression', () => {
|
||||
const text = `${cliRegressionCase.title}
|
||||
|
||||
${cliRegressionCase.body}`;
|
||||
const inferred = inferComponentLabels(text, keywords);
|
||||
assert.ok(!inferred.includes('cli'), `expected 'cli' absent (body contains 'Mem0 client', a substring of the removed 'mem0 cli' term), got ${JSON.stringify(inferred)}`);
|
||||
});
|
||||
|
||||
run('noKeywordMatchReturnsEmptyArray', () => {
|
||||
const text = 'The weather today is sunny and I went for a walk in the park with my dog.';
|
||||
assert.deepStrictEqual(inferComponentLabels(text, keywords), []);
|
||||
});
|
||||
|
||||
run('emptyStringReturnsEmptyArray', () => {
|
||||
assert.deepStrictEqual(inferComponentLabels('', keywords), []);
|
||||
});
|
||||
|
||||
if (failures > 0) {
|
||||
console.error(`
|
||||
${failures} test(s) failed.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`
|
||||
All ${cases.length + 3} tests passed.`);
|
||||
@@ -38,8 +38,11 @@ jobs:
|
||||
cli_python: ${{ steps.filter.outputs.cli_python }}
|
||||
cli_node: ${{ steps.filter.outputs.cli_node }}
|
||||
openclaw: ${{ steps.filter.outputs.openclaw }}
|
||||
mem0_plugin: ${{ steps.filter.outputs.mem0_plugin }}
|
||||
opencode_plugin: ${{ steps.filter.outputs.opencode_plugin }}
|
||||
pi_agent_plugin: ${{ steps.filter.outputs.pi_agent_plugin }}
|
||||
n8n_nodes_mem0: ${{ steps.filter.outputs.n8n_nodes_mem0 }}
|
||||
zapier_mem0: ${{ steps.filter.outputs.zapier_mem0 }}
|
||||
docs_llms_txt: ${{ steps.filter.outputs.docs_llms_txt }}
|
||||
steps:
|
||||
- uses: dorny/paths-filter@v3
|
||||
@@ -71,6 +74,11 @@ jobs:
|
||||
- 'integrations/openclaw/**'
|
||||
- '.github/workflows/openclaw-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
mem0_plugin:
|
||||
- 'integrations/mem0-plugin/**'
|
||||
- '!integrations/mem0-plugin/.opencode-plugin/**'
|
||||
- '.github/workflows/mem0-plugin-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
opencode_plugin:
|
||||
- 'integrations/mem0-plugin/.opencode-plugin/**'
|
||||
- '.github/workflows/opencode-plugin-checks.yml'
|
||||
@@ -79,6 +87,13 @@ jobs:
|
||||
- 'integrations/pi-agent-plugin/**'
|
||||
- '.github/workflows/pi-agent-plugin-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
n8n_nodes_mem0:
|
||||
- 'integrations/n8n-nodes-mem0/**'
|
||||
- '.github/workflows/n8n-nodes-mem0-checks.yml'
|
||||
zapier_mem0:
|
||||
- 'integrations/zapier-mem0/**'
|
||||
- '.github/workflows/zapier-mem0-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
docs_llms_txt:
|
||||
- 'docs/**/*.mdx'
|
||||
- 'docs/llms.txt'
|
||||
@@ -122,6 +137,13 @@ jobs:
|
||||
uses: ./.github/workflows/openclaw-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
mem0-plugin:
|
||||
name: Mem0 Plugin
|
||||
needs: changes
|
||||
if: needs.changes.outputs.mem0_plugin == 'true'
|
||||
uses: ./.github/workflows/mem0-plugin-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
opencode-plugin:
|
||||
name: OpenCode Plugin
|
||||
needs: changes
|
||||
@@ -136,6 +158,18 @@ jobs:
|
||||
uses: ./.github/workflows/pi-agent-plugin-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
n8n-nodes-mem0:
|
||||
name: n8n Node
|
||||
needs: changes
|
||||
if: needs.changes.outputs.n8n_nodes_mem0 == 'true'
|
||||
uses: ./.github/workflows/n8n-nodes-mem0-checks.yml
|
||||
zapier-mem0:
|
||||
name: Zapier App
|
||||
needs: changes
|
||||
if: needs.changes.outputs.zapier_mem0 == 'true'
|
||||
uses: ./.github/workflows/zapier-mem0-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
docs-llms-txt:
|
||||
name: docs llms.txt
|
||||
needs: changes
|
||||
@@ -152,8 +186,11 @@ jobs:
|
||||
- cli-python
|
||||
- cli-node
|
||||
- openclaw
|
||||
- mem0-plugin
|
||||
- opencode-plugin
|
||||
- pi-agent-plugin
|
||||
- n8n-nodes-mem0
|
||||
- zapier-mem0
|
||||
- docs-llms-txt
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
run: |
|
||||
pip install --upgrade pip
|
||||
pip install -e ".[test,graph,vector_stores,llms,extras]"
|
||||
pip install ruff
|
||||
pip install ruff==0.16.0
|
||||
- name: Run Linting
|
||||
if: needs.check_changes.outputs.mem0_changed == 'true'
|
||||
run: make lint
|
||||
|
||||
@@ -12,28 +12,56 @@ jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: stefanbuck/github-issue-parser@v3
|
||||
id: issue-parser
|
||||
continue-on-error: true
|
||||
with:
|
||||
template-path: .github/ISSUE_TEMPLATE/bug_report.yml
|
||||
|
||||
- uses: redhat-plumbers-in-action/advanced-issue-labeler@v3
|
||||
continue-on-error: true
|
||||
with:
|
||||
issue-form: ${{ steps.issue-parser.outputs.jsonString }}
|
||||
section: component
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
config-path: .github/advanced-issue-labeler.yml
|
||||
|
||||
- uses: stefanbuck/github-issue-parser@v3
|
||||
id: feature-parser
|
||||
if: contains(github.event.issue.labels.*.name, 'enhancement')
|
||||
- name: Infer component from text when the form was not used
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
template-path: .github/ISSUE_TEMPLATE/feature_request.yml
|
||||
script: |
|
||||
const {
|
||||
componentLabels,
|
||||
inferComponentLabels,
|
||||
loadKeywords,
|
||||
} = require(`${process.env.GITHUB_WORKSPACE}/.github/scripts/infer-component-labels.js`);
|
||||
|
||||
- uses: redhat-plumbers-in-action/advanced-issue-labeler@v3
|
||||
if: contains(github.event.issue.labels.*.name, 'enhancement')
|
||||
with:
|
||||
issue-form: ${{ steps.feature-parser.outputs.jsonString }}
|
||||
section: component
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
config-path: .github/advanced-issue-labeler.yml
|
||||
const { data: issue } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const keywords = loadKeywords(`${process.env.GITHUB_WORKSPACE}/.github/component-keywords.json`);
|
||||
const known = componentLabels(keywords);
|
||||
const existing = issue.labels.map((label) => label.name || label);
|
||||
if (existing.some((name) => known.includes(name))) {
|
||||
core.info(`Component label already present: ${existing.join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const labels = inferComponentLabels(`${issue.title}\n\n${issue.body || ''}`, keywords);
|
||||
|
||||
if (labels.length === 0) {
|
||||
core.info('No component could be inferred from the issue text');
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(`Inferred: ${labels.join(', ')}`);
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
name: Mem0 Plugin Checks
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main and manual runs remain standalone.
|
||||
#
|
||||
# Covers the Python plugin (scripts/ + tests/). The nested .opencode-plugin/
|
||||
# is a separate package with its own workflow (opencode-plugin-checks.yml).
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'integrations/mem0-plugin/**'
|
||||
- '!integrations/mem0-plugin/.opencode-plugin/**'
|
||||
- '.github/workflows/mem0-plugin-checks.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: integrations/mem0-plugin
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
pip install pytest
|
||||
|
||||
- name: Verify hook entry points are executable
|
||||
working-directory: integrations/mem0-plugin
|
||||
run: |
|
||||
missing=$(find scripts -name '*.sh' ! -name '_*' ! -perm -u+x -print)
|
||||
if [ -n "$missing" ]; then
|
||||
echo "Hook entry points must be executable:"
|
||||
echo "$missing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Check hook manifests are valid JSON
|
||||
working-directory: integrations/mem0-plugin
|
||||
run: |
|
||||
for f in plugin.json mcp_config.json hooks.json hooks/*.json; do
|
||||
jq empty "$f" || (echo "Invalid JSON: $f" && exit 1)
|
||||
done
|
||||
|
||||
- name: Run tests
|
||||
working-directory: integrations/mem0-plugin
|
||||
run: pytest -q
|
||||
@@ -0,0 +1,60 @@
|
||||
name: Publish n8n-nodes-mem0 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# n8n-nodes-mem0-v* is published. Can also be dispatched manually to
|
||||
# re-publish a tag.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. n8n-nodes-mem0-v0.1.0)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Publish under the version preid dist-tag instead of latest'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish n8n-nodes-mem0 📦 to npm
|
||||
if: startsWith(inputs.tag, 'n8n-nodes-mem0-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: integrations/n8n-nodes-mem0
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/n8n-nodes-mem0/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
if [ "${{ inputs.prerelease }}" = "true" ]; then
|
||||
PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]")
|
||||
npx npm@latest publish --provenance --access public --tag "$PREID"
|
||||
else
|
||||
npx npm@latest publish --provenance --access public
|
||||
fi
|
||||
@@ -0,0 +1,88 @@
|
||||
name: n8n-nodes-mem0 checks
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main and manual runs remain standalone.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'integrations/n8n-nodes-mem0/**'
|
||||
- '.github/workflows/n8n-nodes-mem0-checks.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/n8n-nodes-mem0/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/n8n-nodes-mem0 && pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Lint
|
||||
run: cd integrations/n8n-nodes-mem0 && pnpm run lint
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/n8n-nodes-mem0/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/n8n-nodes-mem0 && pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Run tests
|
||||
run: cd integrations/n8n-nodes-mem0 && pnpm test
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/n8n-nodes-mem0/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/n8n-nodes-mem0 && pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Build
|
||||
run: cd integrations/n8n-nodes-mem0 && pnpm run build
|
||||
|
||||
- name: Verify dist output exists
|
||||
run: |
|
||||
test -f integrations/n8n-nodes-mem0/dist/nodes/Mem0/Mem0.node.js || (echo "Build output missing: dist/nodes/Mem0/Mem0.node.js" && exit 1)
|
||||
test -f integrations/n8n-nodes-mem0/dist/credentials/Mem0Api.credentials.js || (echo "Build output missing: dist/credentials/Mem0Api.credentials.js" && exit 1)
|
||||
test -f integrations/n8n-nodes-mem0/dist/nodes/Mem0/mem0.svg || (echo "Build output missing: dist/nodes/Mem0/mem0.svg" && exit 1)
|
||||
@@ -0,0 +1,64 @@
|
||||
name: PR Labeler
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
|
||||
concurrency:
|
||||
group: pr-labeler-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: read
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Propagate labels from linked issues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const allowed = new Set([
|
||||
'sdk-python', 'sdk-typescript', 'vector-store', 'plugin',
|
||||
'rest-api', 'documentation', 'ci', 'cli', 'integrations',
|
||||
]);
|
||||
const umbrella = { plugin: 'integrations' };
|
||||
const { repository } = await github.graphql(
|
||||
`query ($owner: String!, $repo: String!, $number: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number: $number) {
|
||||
closingIssuesReferences(first: 20) {
|
||||
nodes { labels(first: 50) { nodes { name } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
{ owner: context.repo.owner, repo: context.repo.repo, number: context.issue.number },
|
||||
);
|
||||
|
||||
const labels = new Set();
|
||||
for (const issue of repository.pullRequest.closingIssuesReferences.nodes) {
|
||||
for (const label of issue.labels.nodes) {
|
||||
if (allowed.has(label.name)) labels.add(label.name);
|
||||
}
|
||||
}
|
||||
|
||||
for (const label of [...labels]) {
|
||||
if (umbrella[label]) labels.add(umbrella[label]);
|
||||
}
|
||||
|
||||
if (labels.size > 0) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: [...labels],
|
||||
});
|
||||
}
|
||||
@@ -45,6 +45,7 @@ jobs:
|
||||
openclaw-v*) workflow="openclaw-cd.yml" ;;
|
||||
opencode-v*) workflow="opencode-plugin-cd.yml" ;;
|
||||
pi-agent-v*) workflow="pi-agent-plugin-cd.yml" ;;
|
||||
n8n-nodes-mem0-v*) workflow="n8n-nodes-mem0-cd.yml" ;;
|
||||
v*) workflow="cd.yml" ;;
|
||||
*)
|
||||
echo "::error::Release tag '$TAG' does not match any known package prefix — nothing will be published. See the tag prefix table in AGENTS.md."
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Deploy zapier-mem0 to Zapier
|
||||
|
||||
# Zapier apps deploy to Zapier's own platform (not npm), so this is NOT wired
|
||||
# into the npm release router (release.yml). It is manual workflow_dispatch
|
||||
# only and requires the ZAPIER_DEPLOY_KEY repo secret.
|
||||
#
|
||||
# gh workflow run zapier-mem0-cd.yml --ref main
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
push:
|
||||
name: Push zapier-mem0 to Zapier
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: integrations/zapier-mem0
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/zapier-mem0/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build TypeScript
|
||||
run: pnpm build
|
||||
|
||||
- name: Push to Zapier
|
||||
env:
|
||||
ZAPIER_DEPLOY_KEY: ${{ secrets.ZAPIER_DEPLOY_KEY }}
|
||||
run: npx zapier-platform-cli@19 push
|
||||
@@ -0,0 +1,47 @@
|
||||
name: zapier-mem0 checks
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main and manual runs remain standalone.
|
||||
#
|
||||
# CI compiles the TypeScript app, runs `zapier validate` (offline schema + style
|
||||
# checks) against the build, plus the offline jest unit suite (test/unit.test.ts —
|
||||
# mocked z.request, no network). The end-to-end jest suite is skipped here because
|
||||
# it hits the live Mem0 API — it runs locally with MEM0_API_KEY set (see README).
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'integrations/zapier-mem0/**'
|
||||
- '.github/workflows/zapier-mem0-checks.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/zapier-mem0/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/zapier-mem0 && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build TypeScript
|
||||
run: cd integrations/zapier-mem0 && pnpm build
|
||||
|
||||
- name: Validate Zapier app definition
|
||||
run: cd integrations/zapier-mem0 && npx zapier-platform-cli@19 validate
|
||||
|
||||
- name: Run offline unit tests
|
||||
run: cd integrations/zapier-mem0 && pnpm test:unit
|
||||
@@ -27,8 +27,9 @@ This is a **polyglot monorepo** containing Python and TypeScript packages, CLIs,
|
||||
| `integrations/openclaw/` | `@mem0/openclaw-mem0` — OpenClaw plugin for Claude Code / AI editors |
|
||||
| `integrations/pi-agent-plugin/` | `@mem0/pi-agent-plugin` — Pi Agent plugin |
|
||||
| `integrations/vercel-ai-sdk/` | `@mem0/vercel-ai-provider` — Vercel AI SDK memory provider |
|
||||
| `integrations/n8n-nodes-mem0/` | `@mem0/n8n-nodes-mem0` — n8n community node; add / search / get / update / delete memories |
|
||||
| `integrations/zapier-mem0/` | `@mem0/zapier` — Zapier Platform CLI app (deploys to Zapier, not npm); add / search / get / delete memories |
|
||||
| `server/` | FastAPI REST server for self-hosted Mem0 (Docker: FastAPI + PostgreSQL/pgvector + Neo4j) |
|
||||
| `openmemory/` | Self-hosted memory platform — `api/` (FastAPI + Alembic + MCP server) and `ui/` (Next.js 15 + React 19) |
|
||||
| `skills/` | Claude Code skill definitions. Reference skills (SDK knowledge, always-on): `mem0/`, `mem0-cli/`, `mem0-vercel-ai-sdk/`. Pipeline skills (run on demand): `mem0-integrate/`, `mem0-test-integration/`, `mem0-oss-to-platform/` |
|
||||
| `docs/` | Documentation site (Mintlify) |
|
||||
| `tests/` | Python SDK tests (pytest) |
|
||||
@@ -62,7 +63,7 @@ integrations/openclaw/ ──▶ mem0ai (npm)
|
||||
- **Node.js**: v18+ (v20 or v22 recommended)
|
||||
- **pnpm**: v10+ (`npm install -g pnpm@10`) — used for all TypeScript packages
|
||||
- **Hatch**: Python build/environment tool (`pip install hatch`)
|
||||
- **Docker**: Required for `server/` and `openmemory/` development
|
||||
- **Docker**: Required for `server/` development
|
||||
|
||||
### Initial Setup
|
||||
|
||||
@@ -214,28 +215,6 @@ docker-compose up # starts all 3 services
|
||||
- **Services:** PostgreSQL with pgvector, Neo4j 5.x with APOC plugin
|
||||
- **Hot reload:** Dev Dockerfile mounts `server/` and `mem0/` for live changes
|
||||
|
||||
### OpenMemory (`openmemory/`)
|
||||
|
||||
```bash
|
||||
# Full stack via Docker Compose
|
||||
cd openmemory
|
||||
docker-compose up
|
||||
# Qdrant: localhost:6333
|
||||
# API (MCP): localhost:8765
|
||||
# UI: localhost:3000
|
||||
|
||||
# Individual development
|
||||
cd openmemory/api && uvicorn main:app --reload # FastAPI backend
|
||||
cd openmemory/ui && npm run dev # Next.js frontend
|
||||
|
||||
# Tests
|
||||
cd openmemory/api && pytest tests/ # API tests (e.g., test_mcp_server.py)
|
||||
```
|
||||
|
||||
- **API:** FastAPI + Alembic (DB migrations) + MCP server (Model Context Protocol)
|
||||
- **UI:** Next.js 15, React 19, Radix UI, Redux Toolkit, TailwindCSS, Recharts
|
||||
- **Vector store:** Qdrant
|
||||
|
||||
### Documentation (`docs/`)
|
||||
|
||||
```bash
|
||||
@@ -331,7 +310,6 @@ python -m benchmarks.beam.run --project-name my-test --backend cloud --mem0-api-
|
||||
- Root SDK: line length **120**
|
||||
- Python CLI: line length **100** with extended rule set (UP, B, SIM, RUF)
|
||||
- **isort** with `profile = "black"` for import sorting.
|
||||
- Ruff excludes `openmemory/` from root config.
|
||||
|
||||
### TypeScript Conventions
|
||||
|
||||
@@ -382,7 +360,6 @@ Optional layer on top of vector memory for relationship-aware retrieval. Configu
|
||||
Model Context Protocol support in multiple places:
|
||||
|
||||
- **Remote:** MCP server at `mcp.mem0.ai`
|
||||
- **Local:** MCP server in `openmemory/api/` (FastAPI-based)
|
||||
- **Plugin:** MCP tools in `integrations/mem0-plugin/` — 9 tools: `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`
|
||||
|
||||
### Plugin & Skills System
|
||||
@@ -429,8 +406,11 @@ PR testing is orchestrated by a single entry point: **`ci-gate.yml` (CI Gate)**
|
||||
| Python CLI | `cli-python-ci.yml` | Push to main (on `cli/python/`), manual | Ruff lint + pytest + hatch build on Python 3.10, 3.11, 3.12 |
|
||||
| Node CLI | `cli-node-ci.yml` | Push to main (on `cli/node/`), manual | Biome lint + tsc + vitest + tsup build on Node 20, 22 |
|
||||
| OpenClaw | `openclaw-checks.yml` | Push to main (on `integrations/openclaw/`), manual | tsc + vitest (with Codecov) + tsup build on Node 20, 22 |
|
||||
| Mem0 Plugin | `mem0-plugin-checks.yml` | Push to main (on `integrations/mem0-plugin/`, excluding `.opencode-plugin/`), manual | pytest + hook entry-point exec bits + JSON manifest validation on Python 3.10, 3.11, 3.12 |
|
||||
| OpenCode Plugin | `opencode-plugin-checks.yml` | Push to main (on `integrations/mem0-plugin/.opencode-plugin/`), manual | Bun: tsc type-check + build + dist artifact check |
|
||||
| Pi Agent Plugin | `pi-agent-plugin-checks.yml` | Push to main (on `integrations/pi-agent-plugin/`), manual | tsc + vitest + tsup build (dist artifact check) on Node 20, 22 |
|
||||
| n8n Node | `n8n-nodes-mem0-checks.yml` | Push to main (on `integrations/n8n-nodes-mem0/`), manual | ESLint (n8n-nodes-base) + tsc build (dist artifact check) on Node 20 |
|
||||
| Zapier App | `zapier-mem0-checks.yml` | Push to main (on `integrations/zapier-mem0/`), manual | build (tsc) + `zapier validate` + offline unit tests on Node 22 |
|
||||
| docs llms.txt | `docs-llms-txt-check.yml` | Manual | `docs/llms.txt` coverage check |
|
||||
|
||||
When adding a new package CI workflow: give it `workflow_call` (plus `push`/`workflow_dispatch` as needed, but no `pull_request` trigger), then register it in `ci-gate.yml` — a path filter under the `changes` job, a call job, and an entry in the gate job's `needs` list.
|
||||
@@ -450,11 +430,13 @@ Publishing is routed through a single entry point: **`release.yml` (Release Rout
|
||||
| OpenClaw | `openclaw-cd.yml` | `openclaw-v*` | npm (`@mem0/openclaw-mem0`) |
|
||||
| OpenCode Plugin | `opencode-plugin-cd.yml` | `opencode-v*` | npm (`@mem0/opencode-plugin`) |
|
||||
| Pi Agent Plugin | `pi-agent-plugin-cd.yml` | `pi-agent-v*` | npm (`@mem0/pi-agent-plugin`) |
|
||||
| n8n Node | `n8n-nodes-mem0-cd.yml` | `n8n-nodes-mem0-v*` | npm (`@mem0/n8n-nodes-mem0`) |
|
||||
|
||||
- Package CD workflows are `workflow_dispatch`-only (inputs: `tag`, `prerelease`); they check out and build the given tag. Registry trusted-publisher settings stay pinned to each package's own workflow filename.
|
||||
- All publishing uses **OIDC trusted publishing** — no tokens or secrets required.
|
||||
- First publish of a new npm package must be done manually; OIDC works for subsequent versions.
|
||||
- To re-publish a release (e.g. after a registry settings fix), do **not** delete/recreate the GitHub release — manually dispatch the package workflow instead: `gh workflow run <package>-cd.yml --ref refs/tags/<tag> -f tag=<tag>`.
|
||||
- The **Zapier app** (`integrations/zapier-mem0`) deploys to Zapier's own platform, not npm, so it is **not** in the release router. Deploy it manually: `gh workflow run zapier-mem0-cd.yml --ref main` (requires the `ZAPIER_DEPLOY_KEY` secret).
|
||||
- When adding a new package: add its CD workflow (`workflow_dispatch` with `tag`/`prerelease` inputs), then register its tag prefix in the `case` block in `release.yml`. Keep the bare `v*` arm last.
|
||||
|
||||
### Utility Workflows
|
||||
@@ -462,6 +444,7 @@ Publishing is routed through a single entry point: **`release.yml` (Release Rout
|
||||
| Workflow | File | Purpose |
|
||||
|----------|------|---------|
|
||||
| Issue Labeler | `issue-labeler.yml` | Automatic issue labeling |
|
||||
| PR Labeler | `pr-labeler.yml` | Path-based PR labeling plus propagating labels from linked issues |
|
||||
| Stale Bot | `stale.yml` | Marks stale issues and PRs |
|
||||
| llms.txt Check | `docs-llms-txt-check.yml` | Blocks PRs touching `docs/**/*.mdx` when `docs/llms.txt` is out of sync. Fix locally with `python scripts/check-llms-txt-coverage.py --write`. |
|
||||
|
||||
@@ -584,7 +567,7 @@ N/A
|
||||
|
||||
- Follow existing code patterns — don't introduce new frameworks or abstractions without discussion.
|
||||
- Version bumps go in `pyproject.toml` (Python) or `package.json` (TypeScript).
|
||||
- For `server/` and `openmemory/` work, use Docker Compose for local development.
|
||||
- For `server/` work, use Docker Compose for local development.
|
||||
- Do NOT use `pip` or `conda` for dependency management — use `hatch` (see `docs/contributing/development.mdx`).
|
||||
|
||||
### Contributing Guides
|
||||
@@ -607,5 +590,4 @@ N/A
|
||||
- Use npm or yarn in TypeScript packages — this repo uses pnpm exclusively.
|
||||
- Use `require()` for imports in TypeScript — use ES module `import` syntax.
|
||||
- Mix up linter configs: root Python SDK uses line-length 120, Python CLI uses 100, Node CLI uses Biome (not ESLint/Ruff).
|
||||
- Modify `openmemory/` database migrations without understanding the Alembic migration chain.
|
||||
- Change public APIs without updating documentation in `docs/`.
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ The two most common contribution targets are the SDKs:
|
||||
| TypeScript SDK (`mem0ai`) | `mem0-ts/` | TypeScript | `pnpm` |
|
||||
|
||||
Other packages include the CLIs (`cli/python/`, `cli/node/`), integrations
|
||||
(`integrations/`), the self-hosted `server/`, `openmemory/`, and the docs site
|
||||
(`integrations/`), the self-hosted `server/`, and the docs site
|
||||
(`docs/`). See [AGENTS.md](./AGENTS.md) for a full map of the repository.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
@@ -11,7 +11,7 @@ install:
|
||||
hatch env create
|
||||
|
||||
install_all:
|
||||
pip install ruff==0.6.9 groq together boto3 litellm ollama chromadb weaviate weaviate-client sentence_transformers vertexai \
|
||||
pip install ruff==0.16.0 groq together boto3 litellm ollama chromadb weaviate weaviate-client sentence_transformers vertexai \
|
||||
google-generativeai elasticsearch opensearch-py vecs "pinecone<7.0.0" pinecone-text faiss-cpu langchain-community \
|
||||
upstash-vector azure-search-documents langchain-memgraph langchain-neo4j langchain-aws rank-bm25 pymochow pymongo psycopg kuzu databricks-sdk valkey
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ privately through one of the following channels:
|
||||
To help us triage and resolve the issue quickly, please include as much of the
|
||||
following as you can:
|
||||
|
||||
- Affected component or package (e.g. Python SDK, TypeScript SDK, server, OpenMemory)
|
||||
- Affected component or package (e.g. Python SDK, TypeScript SDK, server, CLI)
|
||||
- Affected version, tag, or commit
|
||||
- Clear, step-by-step reproduction instructions
|
||||
- The security impact and a proof of concept, if available
|
||||
|
||||
+1
-2
@@ -60,9 +60,8 @@ mem0 delete <memory-id>
|
||||
| `mem0 entity` | List or delete entities (users, agents, apps, runs) |
|
||||
| `mem0 event` | Inspect background processing events (bulk deletes, large add jobs) |
|
||||
| `mem0 status` | Verify API connection and display current project |
|
||||
| `mem0 version` | Print the CLI version |
|
||||
|
||||
Run `mem0 <command> --help` for detailed usage on any command.
|
||||
Run `mem0 <command> --help` for detailed usage on any command, or `mem0 --version` to print the CLI version.
|
||||
|
||||
## Agent mode
|
||||
|
||||
|
||||
+13
-1
@@ -207,7 +207,11 @@
|
||||
{ "name": "immutable", "flags": ["--immutable"], "type": "boolean", "default": false, "help": "Prevent future updates." },
|
||||
{ "name": "no_infer", "flags": ["--no-infer"], "type": "boolean", "default": false, "help": "Skip inference, store raw." },
|
||||
{ "name": "expires", "flags": ["--expires"], "type": "string", "help": "Expiration date (YYYY-MM-DD)." },
|
||||
{ "name": "categories", "flags": ["--categories"], "type": "string", "help": "Categories (JSON array or comma-separated)." },
|
||||
{ "name": "categories", "flags": ["--categories"], "type": "string", "help": "Not supported on add, use --custom-categories instead." },
|
||||
{ "name": "custom_instructions", "flags": ["--custom-instructions"], "type": "string", "help": "Custom instructions for fact extraction." },
|
||||
{ "name": "custom_categories", "flags": ["--custom-categories"], "type": "string", "help": "Custom categories as a JSON array of {name: description} objects." },
|
||||
{ "name": "structured_data_schema", "flags": ["--structured-data-schema"], "type": "string", "help": "Schema for structured data extraction, as JSON." },
|
||||
{ "name": "timestamp", "flags": ["--timestamp"], "type": "integer", "help": "Unix timestamp for the memory." },
|
||||
{ "name": "graph", "flags": ["--graph"], "type": "boolean", "default": false, "help": "Enable graph memory extraction.", "panel": "Scope" },
|
||||
{ "name": "no_graph", "flags": ["--no-graph"], "type": "boolean", "default": false, "help": "Disable graph memory extraction.", "panel": "Scope" },
|
||||
{ "name": "output", "flags": ["--output", "-o"], "type": "string", "default": "text", "help": "Output format: text, json, quiet.", "panel": "Output" }
|
||||
@@ -244,6 +248,9 @@
|
||||
{ "name": "keyword", "flags": ["--keyword"], "type": "boolean", "default": false, "help": "Use keyword search.", "panel": "Search" },
|
||||
{ "name": "filter_json", "flags": ["--filter"], "type": "string", "help": "Advanced filter expression (JSON).", "panel": "Search" },
|
||||
{ "name": "fields", "flags": ["--fields"], "type": "string", "help": "Specific fields to return (comma-separated).", "panel": "Search" },
|
||||
{ "name": "show_expired", "flags": ["--show-expired"], "type": "boolean", "default": false, "help": "Include expired memories.", "panel": "Search" },
|
||||
{ "name": "reference_date", "flags": ["--reference-date"], "type": "string", "help": "Reference date for relative queries (YYYY-MM-DD or unix timestamp).", "panel": "Search" },
|
||||
{ "name": "latest_only", "flags": ["--latest-only"], "type": "boolean", "default": false, "help": "Only return the latest version of each memory.", "panel": "Search" },
|
||||
{ "name": "graph", "flags": ["--graph"], "type": "boolean", "default": false, "help": "Enable graph in search.", "panel": "Search" },
|
||||
{ "name": "no_graph", "flags": ["--no-graph"], "type": "boolean", "default": false, "help": "Disable graph in search.", "panel": "Search" },
|
||||
{ "name": "output", "flags": ["--output", "-o"], "type": "string", "default": "text", "help": "Output: text, json, table.", "panel": "Output" }
|
||||
@@ -296,6 +303,8 @@
|
||||
{ "name": "category", "flags": ["--category"], "type": "string", "help": "Filter by category.", "panel": "Filters" },
|
||||
{ "name": "after", "flags": ["--after"], "type": "string", "help": "Created after (YYYY-MM-DD).", "panel": "Filters" },
|
||||
{ "name": "before", "flags": ["--before"], "type": "string", "help": "Created before (YYYY-MM-DD).", "panel": "Filters" },
|
||||
{ "name": "show_expired", "flags": ["--show-expired"], "type": "boolean", "default": false, "help": "Include expired memories.", "panel": "Filters" },
|
||||
{ "name": "latest_only", "flags": ["--latest-only"], "type": "boolean", "default": false, "help": "Only return the latest version of each memory.", "panel": "Filters" },
|
||||
{ "name": "graph", "flags": ["--graph"], "type": "boolean", "default": false, "help": "Enable graph in listing.", "panel": "Filters" },
|
||||
{ "name": "no_graph", "flags": ["--no-graph"], "type": "boolean", "default": false, "help": "Disable graph in listing.", "panel": "Filters" },
|
||||
{ "name": "output", "flags": ["--output", "-o"], "type": "string", "default": "table", "help": "Output: text, json, table.", "panel": "Output" }
|
||||
@@ -329,6 +338,8 @@
|
||||
],
|
||||
"options": [
|
||||
{ "name": "metadata", "flags": ["--metadata", "-m"], "type": "string", "help": "Update metadata (JSON)." },
|
||||
{ "name": "expires", "flags": ["--expires"], "type": "string", "help": "Expiration date (YYYY-MM-DD)." },
|
||||
{ "name": "timestamp", "flags": ["--timestamp"], "type": "integer", "help": "Unix timestamp for the memory." },
|
||||
{ "name": "output", "flags": ["--output", "-o"], "type": "string", "default": "text", "help": "Output: text, json, quiet.", "panel": "Output" }
|
||||
],
|
||||
"apiEndpoint": "update"
|
||||
@@ -356,6 +367,7 @@
|
||||
{ "name": "all", "flags": ["--all"], "type": "boolean", "default": false, "help": "Delete all memories matching scope filters." },
|
||||
{ "name": "entity", "flags": ["--entity"], "type": "boolean", "default": false, "help": "Delete the entity itself and all its memories (cascade)." },
|
||||
{ "name": "project", "flags": ["--project"], "type": "boolean", "default": false, "help": "With --all: delete ALL memories project-wide." },
|
||||
{ "name": "delete_linked", "flags": ["--delete-linked"], "type": "boolean", "default": false, "help": "Also delete memories linked to this memory." },
|
||||
{ "name": "dry_run", "flags": ["--dry-run"], "type": "boolean", "default": false, "help": "Show what would be deleted without deleting." },
|
||||
{ "name": "force", "flags": ["--force"], "type": "boolean", "default": false, "help": "Skip confirmation." },
|
||||
{ "name": "user_id", "flags": ["--user-id", "-u"], "type": "string", "help": "Scope to user.", "panel": "Scope" },
|
||||
|
||||
+2
-8
@@ -232,14 +232,6 @@ Verify your API connection and display the current project.
|
||||
mem0 status
|
||||
```
|
||||
|
||||
### `mem0 version`
|
||||
|
||||
Print the CLI version.
|
||||
|
||||
```bash
|
||||
mem0 version
|
||||
```
|
||||
|
||||
## Agent mode
|
||||
|
||||
Pass `--agent` (or its alias `--json`) as a **global flag** on any command to get output designed for AI agent tool loops:
|
||||
@@ -298,6 +290,8 @@ These flags are available on all commands:
|
||||
| `--base-url` | Override the configured API base URL for this request |
|
||||
| `-o, --output` | Set the output format |
|
||||
|
||||
`mem0 --version` prints the CLI version. It is only valid before a subcommand, not after one.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Description |
|
||||
|
||||
@@ -51,7 +51,7 @@ pnpm link --global
|
||||
|
||||
# Now use it like a normal CLI
|
||||
mem0 --help
|
||||
mem0 version
|
||||
mem0 --version
|
||||
```
|
||||
|
||||
> **Warning:** If you also have the Python CLI installed, both register the `mem0` command. The last one linked/installed wins. Unlink with `pnpm unlink --global`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/cli",
|
||||
"version": "0.2.11",
|
||||
"version": "0.2.12",
|
||||
"description": "The official CLI for mem0 — the memory layer for AI agents",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -51,8 +51,8 @@
|
||||
"langsmith@<0.6.0": "^0.6.0",
|
||||
"tar-fs@>=2.0.0 <2.1.4": "^2.1.4",
|
||||
"picomatch@<2.3.2": "^2.3.2",
|
||||
"postcss@<8.5.10": ">=8.5.10",
|
||||
"esbuild": ">=0.28.1"
|
||||
"esbuild": ">=0.28.1",
|
||||
"postcss@<8.5.18": ">=8.5.18 <9.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+34
-17
@@ -9,8 +9,8 @@ overrides:
|
||||
langsmith@<0.6.0: ^0.6.0
|
||||
tar-fs@>=2.0.0 <2.1.4: ^2.1.4
|
||||
picomatch@<2.3.2: ^2.3.2
|
||||
postcss@<8.5.10: '>=8.5.10'
|
||||
esbuild: '>=0.28.1'
|
||||
postcss@<8.5.18: '>=8.5.18 <9.0.0'
|
||||
|
||||
importers:
|
||||
|
||||
@@ -40,7 +40,7 @@ importers:
|
||||
version: 20.19.37
|
||||
tsup:
|
||||
specifier: ^8.0.0
|
||||
version: 8.5.1(postcss@8.5.15)(tsx@4.21.0)(typescript@5.9.3)
|
||||
version: 8.5.1(postcss@8.5.23)(tsx@4.21.0)(typescript@5.9.3)
|
||||
tsx:
|
||||
specifier: ^4.7.0
|
||||
version: 4.21.0
|
||||
@@ -78,24 +78,28 @@ packages:
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@biomejs/cli-linux-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@1.9.4':
|
||||
resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@biomejs/cli-linux-x64@1.9.4':
|
||||
resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@biomejs/cli-win32-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==}
|
||||
@@ -316,66 +320,79 @@ packages:
|
||||
resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.60.0':
|
||||
resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.60.0':
|
||||
resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.60.0':
|
||||
resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.60.0':
|
||||
resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.60.0':
|
||||
resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.60.0':
|
||||
resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.60.0':
|
||||
resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.60.0':
|
||||
resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.60.0':
|
||||
resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.60.0':
|
||||
resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.60.0':
|
||||
resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.60.0':
|
||||
resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.60.0':
|
||||
resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==}
|
||||
@@ -653,8 +670,8 @@ packages:
|
||||
mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
|
||||
nanoid@3.3.12:
|
||||
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
|
||||
nanoid@3.3.16:
|
||||
resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
@@ -695,7 +712,7 @@ packages:
|
||||
engines: {node: '>= 18'}
|
||||
peerDependencies:
|
||||
jiti: '>=1.21.0'
|
||||
postcss: '>=8.5.10'
|
||||
postcss: '>=8.5.18 <9.0.0'
|
||||
tsx: ^4.8.1
|
||||
yaml: ^2.4.2
|
||||
peerDependenciesMeta:
|
||||
@@ -708,8 +725,8 @@ packages:
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
postcss@8.5.15:
|
||||
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
|
||||
postcss@8.5.23:
|
||||
resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
readdirp@4.1.2:
|
||||
@@ -821,7 +838,7 @@ packages:
|
||||
peerDependencies:
|
||||
'@microsoft/api-extractor': ^7.36.0
|
||||
'@swc/core': ^1
|
||||
postcss: '>=8.5.10'
|
||||
postcss: '>=8.5.18 <9.0.0'
|
||||
typescript: '>=4.5.0'
|
||||
peerDependenciesMeta:
|
||||
'@microsoft/api-extractor':
|
||||
@@ -1388,7 +1405,7 @@ snapshots:
|
||||
object-assign: 4.1.1
|
||||
thenify-all: 1.6.0
|
||||
|
||||
nanoid@3.3.12: {}
|
||||
nanoid@3.3.16: {}
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
@@ -1424,16 +1441,16 @@ snapshots:
|
||||
mlly: 1.8.2
|
||||
pathe: 2.0.3
|
||||
|
||||
postcss-load-config@6.0.1(postcss@8.5.15)(tsx@4.21.0):
|
||||
postcss-load-config@6.0.1(postcss@8.5.23)(tsx@4.21.0):
|
||||
dependencies:
|
||||
lilconfig: 3.1.3
|
||||
optionalDependencies:
|
||||
postcss: 8.5.15
|
||||
postcss: 8.5.23
|
||||
tsx: 4.21.0
|
||||
|
||||
postcss@8.5.15:
|
||||
postcss@8.5.23:
|
||||
dependencies:
|
||||
nanoid: 3.3.12
|
||||
nanoid: 3.3.16
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
@@ -1554,7 +1571,7 @@ snapshots:
|
||||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
|
||||
tsup@8.5.1(postcss@8.5.15)(tsx@4.21.0)(typescript@5.9.3):
|
||||
tsup@8.5.1(postcss@8.5.23)(tsx@4.21.0)(typescript@5.9.3):
|
||||
dependencies:
|
||||
bundle-require: 5.1.0(esbuild@0.28.1)
|
||||
cac: 6.7.14
|
||||
@@ -1565,7 +1582,7 @@ snapshots:
|
||||
fix-dts-default-cjs-exports: 1.0.1
|
||||
joycon: 3.1.1
|
||||
picocolors: 1.1.1
|
||||
postcss-load-config: 6.0.1(postcss@8.5.15)(tsx@4.21.0)
|
||||
postcss-load-config: 6.0.1(postcss@8.5.23)(tsx@4.21.0)
|
||||
resolve-from: 5.0.0
|
||||
rollup: 4.60.0
|
||||
source-map: 0.7.6
|
||||
@@ -1574,7 +1591,7 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
tree-kill: 1.2.2
|
||||
optionalDependencies:
|
||||
postcss: 8.5.15
|
||||
postcss: 8.5.23
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
@@ -1602,7 +1619,7 @@ snapshots:
|
||||
esbuild: 0.28.1
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.15
|
||||
postcss: 8.5.23
|
||||
rollup: 4.60.0
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
|
||||
@@ -10,5 +10,5 @@ overrides:
|
||||
langsmith@<0.6.0: ^0.6.0
|
||||
tar-fs@>=2.0.0 <2.1.4: ^2.1.4
|
||||
picomatch@<2.3.2: ^2.3.2
|
||||
"postcss@<8.5.10": ">=8.5.10"
|
||||
"esbuild": ">=0.28.1"
|
||||
"postcss@<8.5.18": ">=8.5.18 <9.0.0"
|
||||
|
||||
@@ -14,7 +14,10 @@ export interface AddOptions {
|
||||
immutable?: boolean;
|
||||
infer?: boolean;
|
||||
expires?: string;
|
||||
categories?: string[];
|
||||
customInstructions?: string;
|
||||
customCategories?: Record<string, string>[];
|
||||
structuredDataSchema?: Record<string, unknown>;
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
export interface SearchOptions {
|
||||
@@ -28,6 +31,9 @@ export interface SearchOptions {
|
||||
keyword?: boolean;
|
||||
filters?: Record<string, unknown>;
|
||||
fields?: string[];
|
||||
showExpired?: boolean;
|
||||
referenceDate?: string | number;
|
||||
latestOnly?: boolean;
|
||||
}
|
||||
|
||||
export interface ListOptions {
|
||||
@@ -40,6 +46,8 @@ export interface ListOptions {
|
||||
category?: string;
|
||||
after?: string;
|
||||
before?: string;
|
||||
showExpired?: boolean;
|
||||
latestOnly?: boolean;
|
||||
}
|
||||
|
||||
export interface DeleteOptions {
|
||||
@@ -48,6 +56,12 @@ export interface DeleteOptions {
|
||||
agentId?: string;
|
||||
appId?: string;
|
||||
runId?: string;
|
||||
deleteLinked?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateOptions {
|
||||
expirationDate?: string;
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
export interface EntityIds {
|
||||
@@ -77,6 +91,7 @@ export interface Backend {
|
||||
memoryId: string,
|
||||
content?: string,
|
||||
metadata?: Record<string, unknown>,
|
||||
opts?: UpdateOptions,
|
||||
): Promise<Record<string, unknown>>;
|
||||
|
||||
delete(
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
type ListOptions,
|
||||
NotFoundError,
|
||||
type SearchOptions,
|
||||
type UpdateOptions,
|
||||
} from "./base.js";
|
||||
|
||||
function encodePathSegment(value: unknown): string {
|
||||
@@ -150,7 +151,13 @@ export class PlatformBackend implements Backend {
|
||||
if (opts.immutable) payload.immutable = true;
|
||||
if (opts.infer === false) payload.infer = false;
|
||||
if (opts.expires) payload.expiration_date = opts.expires;
|
||||
if (opts.categories) payload.categories = opts.categories;
|
||||
if (opts.customInstructions)
|
||||
payload.custom_instructions = opts.customInstructions;
|
||||
if (opts.customCategories)
|
||||
payload.custom_categories = opts.customCategories;
|
||||
if (opts.structuredDataSchema)
|
||||
payload.structured_data_schema = opts.structuredDataSchema;
|
||||
if (opts.timestamp !== undefined) payload.timestamp = opts.timestamp;
|
||||
payload.source = "CLI";
|
||||
|
||||
return (await this._request("POST", "/v3/memories/add/", {
|
||||
@@ -211,6 +218,10 @@ export class PlatformBackend implements Backend {
|
||||
if (opts.rerank) payload.rerank = true;
|
||||
if (opts.keyword) payload.keyword_search = true;
|
||||
if (opts.fields) payload.fields = opts.fields;
|
||||
if (opts.showExpired) payload.show_expired = true;
|
||||
if (opts.referenceDate !== undefined)
|
||||
payload.reference_date = opts.referenceDate;
|
||||
if (opts.latestOnly) payload.latest_only = true;
|
||||
payload.source = "CLI";
|
||||
|
||||
const result = (await this._request("POST", "/v3/memories/search/", {
|
||||
@@ -265,6 +276,8 @@ export class PlatformBackend implements Backend {
|
||||
extraFilters: Object.keys(extra).length > 0 ? extra : undefined,
|
||||
});
|
||||
if (apiFilters) payload.filters = apiFilters;
|
||||
if (opts.showExpired) payload.show_expired = true;
|
||||
if (opts.latestOnly) payload.latest_only = true;
|
||||
payload.source = "CLI";
|
||||
|
||||
const result = (await this._request("POST", "/v3/memories/", {
|
||||
@@ -280,10 +293,13 @@ export class PlatformBackend implements Backend {
|
||||
memoryId: string,
|
||||
content?: string,
|
||||
metadata?: Record<string, unknown>,
|
||||
opts: UpdateOptions = {},
|
||||
): Promise<Record<string, unknown>> {
|
||||
const payload: Record<string, unknown> = {};
|
||||
if (content) payload.text = content;
|
||||
if (metadata) payload.metadata = metadata;
|
||||
if (opts.expirationDate) payload.expiration_date = opts.expirationDate;
|
||||
if (opts.timestamp !== undefined) payload.timestamp = opts.timestamp;
|
||||
payload.source = "CLI";
|
||||
return (await this._request(
|
||||
"PUT",
|
||||
@@ -309,12 +325,12 @@ export class PlatformBackend implements Backend {
|
||||
})) as Record<string, unknown>;
|
||||
}
|
||||
if (memoryId) {
|
||||
const params: Record<string, string> = { source: "CLI" };
|
||||
if (opts.deleteLinked) params.delete_linked = "true";
|
||||
return (await this._request(
|
||||
"DELETE",
|
||||
`/v1/memories/${encodePathSegment(memoryId)}/`,
|
||||
{
|
||||
params: { source: "CLI" },
|
||||
},
|
||||
{ params },
|
||||
)) as Record<string, unknown>;
|
||||
}
|
||||
throw new Error("Either memoryId or --all is required");
|
||||
|
||||
@@ -21,16 +21,19 @@ import {
|
||||
formatSingleMemory,
|
||||
printResultSummary,
|
||||
} from "../output.js";
|
||||
import { isAgentMode, setCurrentCommand } from "../state.js";
|
||||
import { isAgentMode, setCurrentCommand, stdinIsPiped } from "../state.js";
|
||||
|
||||
/** True only when stdin is an actual pipe or file redirect — never in agent mode. */
|
||||
function _stdinIsPiped(): boolean {
|
||||
if (isAgentMode()) return false;
|
||||
try {
|
||||
const stat = fs.fstatSync(0);
|
||||
return stat.isFIFO() || stat.isFile();
|
||||
} catch {
|
||||
return false;
|
||||
/** Exit 1 if value is not a future YYYY-MM-DD date. */
|
||||
function _validateExpires(value: string): void {
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
||||
printError(
|
||||
"Invalid date format for --expires. Use YYYY-MM-DD (e.g. 2025-12-31).",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if (new Date(value) <= new Date()) {
|
||||
printError("--expires date must be in the future.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,10 +52,22 @@ export async function cmdAdd(
|
||||
infer?: boolean;
|
||||
expires?: string;
|
||||
categories?: string;
|
||||
customInstructions?: string;
|
||||
customCategories?: string;
|
||||
structuredDataSchema?: string;
|
||||
timestamp?: number;
|
||||
output: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
setCurrentCommand("add");
|
||||
|
||||
if (opts.categories) {
|
||||
printError(
|
||||
"--categories is not supported on add. Use --custom-categories instead.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let msgs: Record<string, unknown>[] | undefined;
|
||||
let content = text;
|
||||
|
||||
@@ -78,7 +93,7 @@ export async function cmdAdd(
|
||||
}
|
||||
}
|
||||
// Read from stdin only if stdin is an actual pipe or file redirect
|
||||
else if (!content && _stdinIsPiped()) {
|
||||
else if (!content && stdinIsPiped()) {
|
||||
content = fs.readFileSync(0, "utf-8").trim();
|
||||
}
|
||||
|
||||
@@ -93,20 +108,6 @@ export async function cmdAdd(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate --expires
|
||||
if (opts.expires) {
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(opts.expires)) {
|
||||
printError(
|
||||
"Invalid date format for --expires. Use YYYY-MM-DD (e.g. 2025-12-31).",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if (new Date(opts.expires) <= new Date()) {
|
||||
printError("--expires date must be in the future.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let meta: Record<string, unknown> | undefined;
|
||||
if (opts.metadata) {
|
||||
try {
|
||||
@@ -117,15 +118,28 @@ export async function cmdAdd(
|
||||
}
|
||||
}
|
||||
|
||||
let cats: string[] | undefined;
|
||||
if (opts.categories) {
|
||||
let customCats: Record<string, string>[] | undefined;
|
||||
if (opts.customCategories) {
|
||||
try {
|
||||
cats = JSON.parse(opts.categories);
|
||||
customCats = JSON.parse(opts.customCategories);
|
||||
} catch {
|
||||
cats = opts.categories.split(",").map((c) => c.trim());
|
||||
printError("Invalid JSON in --custom-categories.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let schema: Record<string, unknown> | undefined;
|
||||
if (opts.structuredDataSchema) {
|
||||
try {
|
||||
schema = JSON.parse(opts.structuredDataSchema);
|
||||
} catch {
|
||||
printError("Invalid JSON in --structured-data-schema.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.expires) _validateExpires(opts.expires);
|
||||
|
||||
let result: Record<string, unknown>;
|
||||
try {
|
||||
result = await timedStatus("Adding memory...", async () => {
|
||||
@@ -138,7 +152,10 @@ export async function cmdAdd(
|
||||
immutable: opts.immutable,
|
||||
infer: opts.infer !== false,
|
||||
expires: opts.expires,
|
||||
categories: cats,
|
||||
customInstructions: opts.customInstructions,
|
||||
customCategories: customCats,
|
||||
structuredDataSchema: schema,
|
||||
timestamp: opts.timestamp,
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -223,6 +240,9 @@ export async function cmdSearch(
|
||||
keyword: boolean;
|
||||
filterJson?: string;
|
||||
fields?: string;
|
||||
showExpired?: boolean;
|
||||
referenceDate?: string;
|
||||
latestOnly?: boolean;
|
||||
output: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
@@ -271,6 +291,9 @@ export async function cmdSearch(
|
||||
keyword: opts.keyword,
|
||||
filters,
|
||||
fields: fieldList,
|
||||
showExpired: opts.showExpired,
|
||||
referenceDate: opts.referenceDate,
|
||||
latestOnly: opts.latestOnly,
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -364,6 +387,8 @@ export async function cmdList(
|
||||
category?: string;
|
||||
after?: string;
|
||||
before?: string;
|
||||
showExpired?: boolean;
|
||||
latestOnly?: boolean;
|
||||
output: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
@@ -391,6 +416,8 @@ export async function cmdList(
|
||||
category: opts.category,
|
||||
after: opts.after,
|
||||
before: opts.before,
|
||||
showExpired: opts.showExpired,
|
||||
latestOnly: opts.latestOnly,
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -450,7 +477,12 @@ export async function cmdUpdate(
|
||||
backend: Backend,
|
||||
memoryId: string,
|
||||
text: string | undefined,
|
||||
opts: { metadata?: string; output: string },
|
||||
opts: {
|
||||
metadata?: string;
|
||||
expires?: string;
|
||||
timestamp?: number;
|
||||
output: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
setCurrentCommand("update");
|
||||
let meta: Record<string, unknown> | undefined;
|
||||
@@ -463,11 +495,16 @@ export async function cmdUpdate(
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.expires) _validateExpires(opts.expires);
|
||||
|
||||
const start = performance.now();
|
||||
let result: Record<string, unknown>;
|
||||
try {
|
||||
result = await timedStatus("Updating memory...", async () => {
|
||||
return backend.update(memoryId, text, meta);
|
||||
return backend.update(memoryId, text, meta, {
|
||||
expirationDate: opts.expires,
|
||||
timestamp: opts.timestamp,
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
printError(e instanceof Error ? e.message : String(e));
|
||||
@@ -493,7 +530,12 @@ export async function cmdUpdate(
|
||||
export async function cmdDelete(
|
||||
backend: Backend,
|
||||
memoryId: string,
|
||||
opts: { output: string; dryRun?: boolean; force?: boolean },
|
||||
opts: {
|
||||
output: string;
|
||||
dryRun?: boolean;
|
||||
force?: boolean;
|
||||
deleteLinked?: boolean;
|
||||
},
|
||||
): Promise<void> {
|
||||
setCurrentCommand("delete");
|
||||
if (opts.dryRun) {
|
||||
@@ -514,7 +556,7 @@ export async function cmdDelete(
|
||||
let result: Record<string, unknown>;
|
||||
try {
|
||||
result = await timedStatus("Deleting...", async () => {
|
||||
return backend.delete(memoryId);
|
||||
return backend.delete(memoryId, { deleteLinked: opts.deleteLinked });
|
||||
});
|
||||
} catch (e) {
|
||||
printError(e instanceof Error ? e.message : String(e));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Utility commands: status, version, import.
|
||||
* Utility commands: status, import.
|
||||
*/
|
||||
|
||||
import fs from "node:fs";
|
||||
@@ -8,7 +8,6 @@ import type { Backend } from "../backend/base.js";
|
||||
import { colors, printError, printSuccess, timedStatus } from "../branding.js";
|
||||
import { formatAgentEnvelope, formatJsonEnvelope } from "../output.js";
|
||||
import { setCurrentCommand } from "../state.js";
|
||||
import { CLI_VERSION } from "../version.js";
|
||||
|
||||
const { brand, dim, success, error: errorColor } = colors;
|
||||
|
||||
@@ -82,10 +81,6 @@ export async function cmdStatus(
|
||||
console.log();
|
||||
}
|
||||
|
||||
export function cmdVersion(): void {
|
||||
console.log(` ${brand("◆ Mem0")} CLI v${CLI_VERSION}`);
|
||||
}
|
||||
|
||||
export async function cmdImport(
|
||||
backend: Backend,
|
||||
filePath: string,
|
||||
|
||||
+57
-5
@@ -17,6 +17,7 @@ import {
|
||||
isAgentMode,
|
||||
setAgentMode,
|
||||
setCurrentCommand,
|
||||
stdinIsPiped,
|
||||
takeNotice,
|
||||
} from "./state.js";
|
||||
import { captureEvent } from "./telemetry.js";
|
||||
@@ -319,7 +320,25 @@ program
|
||||
.option("--immutable", "Prevent future updates.", false)
|
||||
.option("--no-infer", "Skip inference, store raw.")
|
||||
.option("--expires <date>", "Expiration date (YYYY-MM-DD).")
|
||||
.option("--categories <value>", "Categories (JSON array or comma-separated).")
|
||||
.option(
|
||||
"--categories <value>",
|
||||
"Not supported on add, use --custom-categories instead.",
|
||||
)
|
||||
.option(
|
||||
"--custom-instructions <text>",
|
||||
"Custom instructions for fact extraction.",
|
||||
)
|
||||
.option(
|
||||
"--custom-categories <json>",
|
||||
"Custom categories as a JSON array of {name: description} objects.",
|
||||
)
|
||||
.option(
|
||||
"--structured-data-schema <json>",
|
||||
"Schema for structured data extraction, as JSON.",
|
||||
)
|
||||
.option("--timestamp <unix>", "Unix timestamp for the memory.", (v) =>
|
||||
Number.parseInt(v),
|
||||
)
|
||||
.option("-o, --output <format>", "Output format: text, json, quiet.", "text")
|
||||
.option("--api-key <key>", "Override API key.")
|
||||
.option("--base-url <url>", "Override API base URL.")
|
||||
@@ -366,6 +385,16 @@ program
|
||||
.option("--keyword", "Use keyword search.", false)
|
||||
.option("--filter <json>", "Advanced filter expression (JSON).")
|
||||
.option("--fields <list>", "Specific fields to return (comma-separated).")
|
||||
.option("--show-expired", "Include expired memories.", false)
|
||||
.option(
|
||||
"--reference-date <date>",
|
||||
"Reference date for relative queries (YYYY-MM-DD or unix timestamp).",
|
||||
)
|
||||
.option(
|
||||
"--latest-only",
|
||||
"Only return the latest version of each memory.",
|
||||
false,
|
||||
)
|
||||
.option("-o, --output <format>", "Output: text, json, table.", "text")
|
||||
.option("--api-key <key>", "Override API key.")
|
||||
.option("--base-url <url>", "Override API base URL.")
|
||||
@@ -375,7 +404,7 @@ program
|
||||
)
|
||||
.action(async (query, opts) => {
|
||||
let resolvedQuery = query;
|
||||
if (!resolvedQuery && !process.stdin.isTTY) {
|
||||
if (!resolvedQuery && stdinIsPiped()) {
|
||||
resolvedQuery = fs.readFileSync(0, "utf-8").trim();
|
||||
}
|
||||
if (!resolvedQuery) {
|
||||
@@ -398,6 +427,9 @@ program
|
||||
keyword: opts.keyword,
|
||||
filterJson: opts.filter,
|
||||
fields: opts.fields,
|
||||
showExpired: opts.showExpired,
|
||||
referenceDate: opts.referenceDate,
|
||||
latestOnly: opts.latestOnly,
|
||||
output,
|
||||
});
|
||||
});
|
||||
@@ -441,6 +473,12 @@ program
|
||||
.option("--category <name>", "Filter by category.")
|
||||
.option("--after <date>", "Created after (YYYY-MM-DD).")
|
||||
.option("--before <date>", "Created before (YYYY-MM-DD).")
|
||||
.option("--show-expired", "Include expired memories.", false)
|
||||
.option(
|
||||
"--latest-only",
|
||||
"Only return the latest version of each memory.",
|
||||
false,
|
||||
)
|
||||
.option("-o, --output <format>", "Output: text, json, table.", "table")
|
||||
.option("--api-key <key>", "Override API key.")
|
||||
.option("--base-url <url>", "Override API base URL.")
|
||||
@@ -464,6 +502,8 @@ program
|
||||
category: opts.category,
|
||||
after: opts.after,
|
||||
before: opts.before,
|
||||
showExpired: opts.showExpired,
|
||||
latestOnly: opts.latestOnly,
|
||||
output,
|
||||
});
|
||||
});
|
||||
@@ -474,6 +514,10 @@ program
|
||||
.command("update <memoryId> [text]")
|
||||
.description("Update a memory's text or metadata.")
|
||||
.option("-m, --metadata <json>", "Update metadata (JSON).")
|
||||
.option("--expires <date>", "Expiration date (YYYY-MM-DD).")
|
||||
.option("--timestamp <unix>", "Unix timestamp for the memory.", (v) =>
|
||||
Number.parseInt(v),
|
||||
)
|
||||
.option("-o, --output <format>", "Output: text, json, quiet.", "text")
|
||||
.option("--api-key <key>", "Override API key.")
|
||||
.option("--base-url <url>", "Override API base URL.")
|
||||
@@ -483,7 +527,7 @@ program
|
||||
)
|
||||
.action(async (memoryId, text, opts) => {
|
||||
let resolvedText = text;
|
||||
if (!resolvedText && !opts.metadata && !process.stdin.isTTY) {
|
||||
if (!resolvedText && stdinIsPiped()) {
|
||||
resolvedText = fs.readFileSync(0, "utf-8").trim();
|
||||
}
|
||||
const { cmdUpdate } = await import("./commands/memory.js");
|
||||
@@ -492,6 +536,8 @@ program
|
||||
const output = isAgent ? "agent" : opts.output;
|
||||
await cmdUpdate(backend, memoryId, resolvedText, {
|
||||
metadata: opts.metadata,
|
||||
expires: opts.expires,
|
||||
timestamp: opts.timestamp,
|
||||
output,
|
||||
});
|
||||
});
|
||||
@@ -510,6 +556,11 @@ program
|
||||
.option("--project", "With --all: delete ALL memories project-wide.", false)
|
||||
.option("--dry-run", "Show what would be deleted without deleting.", false)
|
||||
.option("--force", "Skip confirmation.", false)
|
||||
.option(
|
||||
"--delete-linked",
|
||||
"Also delete memories linked to this memory.",
|
||||
false,
|
||||
)
|
||||
.option("-u, --user-id <id>", "Scope to user.")
|
||||
.option("--agent-id <id>", "Scope to agent.")
|
||||
.option("--app-id <id>", "Scope to app.")
|
||||
@@ -563,6 +614,7 @@ program
|
||||
output,
|
||||
dryRun: opts.dryRun,
|
||||
force: opts.force,
|
||||
deleteLinked: opts.deleteLinked,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -806,8 +858,8 @@ program
|
||||
.addHelpText("after", "\nExamples:\n $ mem0 help\n $ mem0 help --json")
|
||||
.action((opts) => {
|
||||
// opts.json is set when `mem0 help --json` is used (subcommand flag).
|
||||
// program.opts().json is set when the root --json global flag was used first.
|
||||
if (opts.json || program.opts().json) {
|
||||
// program.opts().json/.agent is set when a root global flag was used first.
|
||||
if (opts.json || program.opts().json || program.opts().agent) {
|
||||
// Load spec from parent directory
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const specPath = path.join(__dirname, "..", "..", "cli-spec.json");
|
||||
|
||||
+17
-3
@@ -282,11 +282,24 @@ export function sanitizeAgentData(command: string, data: unknown): unknown {
|
||||
}
|
||||
case "search":
|
||||
return (data as Record<string, unknown>[]).map((r) =>
|
||||
pick(r, ["id", "memory", "score", "created_at", "categories"]),
|
||||
pick(r, [
|
||||
"id",
|
||||
"memory",
|
||||
"score",
|
||||
"created_at",
|
||||
"categories",
|
||||
"expiration_date",
|
||||
]),
|
||||
);
|
||||
case "list":
|
||||
return (data as Record<string, unknown>[]).map((r) =>
|
||||
pick(r, ["id", "memory", "created_at", "categories"]),
|
||||
pick(r, [
|
||||
"id",
|
||||
"memory",
|
||||
"created_at",
|
||||
"categories",
|
||||
"expiration_date",
|
||||
]),
|
||||
);
|
||||
case "get": {
|
||||
const r = data as Record<string, unknown>;
|
||||
@@ -297,11 +310,12 @@ export function sanitizeAgentData(command: string, data: unknown): unknown {
|
||||
"updated_at",
|
||||
"categories",
|
||||
"metadata",
|
||||
"expiration_date",
|
||||
]);
|
||||
}
|
||||
case "update": {
|
||||
const r = data as Record<string, unknown>;
|
||||
return pick(r, ["id", "memory"]);
|
||||
return pick(r, ["id", "memory", "expiration_date"]);
|
||||
}
|
||||
case "delete":
|
||||
case "delete-all":
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* read by commands and branding functions.
|
||||
*/
|
||||
|
||||
import fs from "node:fs";
|
||||
|
||||
let _agentMode = false;
|
||||
let _currentCommand = "";
|
||||
let _pendingNotice = "";
|
||||
@@ -38,3 +40,14 @@ export function takeNotice(): string {
|
||||
_pendingNotice = "";
|
||||
return msg;
|
||||
}
|
||||
|
||||
/** True only when stdin is an actual pipe or file redirect (never in agent mode). */
|
||||
export function stdinIsPiped(): boolean {
|
||||
if (isAgentMode()) return false;
|
||||
try {
|
||||
const stat = fs.fstatSync(0);
|
||||
return stat.isFIFO() || stat.isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,15 +44,25 @@ describe("CLI Integration — help and version", () => {
|
||||
expect(result.stdout).toContain("search");
|
||||
});
|
||||
|
||||
it("help --json produces valid JSON", () => {
|
||||
const result = run(["help", "--json"]);
|
||||
expect(result.exitCode).toBe(0);
|
||||
const parsed = JSON.parse(result.stdout);
|
||||
// spec may have cli.name or top-level name
|
||||
const name = parsed.name ?? parsed.cli?.name;
|
||||
expect(name).toBe("mem0");
|
||||
it("prints the version with --version, and has no version subcommand", () => {
|
||||
const flag = run(["--version"]);
|
||||
expect(flag.exitCode).toBe(0);
|
||||
expect(flag.stdout).toContain("Mem0");
|
||||
expect(run(["version"]).exitCode).not.toBe(0);
|
||||
});
|
||||
|
||||
it.each([["help", "--json"], ["--json", "help"], ["--agent", "help"]])(
|
||||
"%s %s produces valid JSON",
|
||||
(...args) => {
|
||||
const result = run(args);
|
||||
expect(result.exitCode).toBe(0);
|
||||
const parsed = JSON.parse(result.stdout);
|
||||
// spec may have cli.name or top-level name
|
||||
const name = parsed.name ?? parsed.cli?.name;
|
||||
expect(name).toBe("mem0");
|
||||
},
|
||||
);
|
||||
|
||||
it("shows add help", () => {
|
||||
const result = run(["add", "--help"]);
|
||||
expect(result.exitCode).toBe(0);
|
||||
|
||||
+580
-373
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* Drift test: every documented v3 add/search/list param must be reachable from the Node CLI.
|
||||
*/
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const OPENAPI_PATH = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"docs",
|
||||
"openapi.json",
|
||||
);
|
||||
|
||||
const KNOWN_UNSURFACED: Record<string, Record<string, string>> = {
|
||||
"/v3/memories/add/": {
|
||||
includes: "extraction hint, no CLI flag yet",
|
||||
excludes: "extraction hint, no CLI flag yet",
|
||||
enable_graph: "graph memory toggle, no CLI flag yet",
|
||||
output_format: "response envelope is pinned by the CLI",
|
||||
prompt_profile_id: "no CLI flag yet",
|
||||
temporal_reasoning: "no CLI flag yet",
|
||||
timezone: "no CLI flag yet",
|
||||
observation_datetime: "no CLI flag yet, --timestamp backdates instead",
|
||||
observation_date: "no CLI flag yet, --timestamp backdates instead",
|
||||
},
|
||||
"/v3/memories/search/": {
|
||||
categories: "expressible through --filter",
|
||||
metadata: "expressible through --filter",
|
||||
},
|
||||
"/v3/memories/": {
|
||||
start_date: "covered by --after via filters.created_at.gte",
|
||||
end_date: "covered by --before via filters.created_at.lte",
|
||||
categories: "covered by --category via filters.categories",
|
||||
fields: "no CLI flag yet",
|
||||
keywords: "no CLI flag yet",
|
||||
},
|
||||
};
|
||||
|
||||
const ADD_MAPPING: Record<string, string[]> = {
|
||||
messages: ["--messages", "--file", "text"],
|
||||
user_id: ["--user-id"],
|
||||
agent_id: ["--agent-id"],
|
||||
app_id: ["--app-id"],
|
||||
run_id: ["--run-id"],
|
||||
metadata: ["--metadata"],
|
||||
expiration_date: ["--expires"],
|
||||
custom_instructions: ["--custom-instructions"],
|
||||
custom_categories: ["--custom-categories"],
|
||||
infer: ["--no-infer"],
|
||||
immutable: ["--immutable"],
|
||||
structured_data_schema: ["--structured-data-schema"],
|
||||
timestamp: ["--timestamp"],
|
||||
};
|
||||
|
||||
const SEARCH_MAPPING: Record<string, string[]> = {
|
||||
query: ["query"],
|
||||
filters: ["--filter", "--user-id", "--agent-id", "--run-id"],
|
||||
show_expired: ["--show-expired"],
|
||||
top_k: ["--top-k"],
|
||||
threshold: ["--threshold"],
|
||||
rerank: ["--rerank"],
|
||||
reference_date: ["--reference-date"],
|
||||
fields: ["--fields"],
|
||||
};
|
||||
|
||||
const LIST_MAPPING: Record<string, string[]> = {
|
||||
filters: [
|
||||
"--user-id",
|
||||
"--agent-id",
|
||||
"--run-id",
|
||||
"--category",
|
||||
"--after",
|
||||
"--before",
|
||||
],
|
||||
show_expired: ["--show-expired"],
|
||||
page: ["--page"],
|
||||
page_size: ["--page-size"],
|
||||
};
|
||||
|
||||
function documentedFields(endpoint: string): string[] {
|
||||
const spec = JSON.parse(fs.readFileSync(OPENAPI_PATH, "utf-8"));
|
||||
const schema =
|
||||
spec.paths[endpoint].post.requestBody.content["application/json"].schema;
|
||||
return Object.keys(schema.properties);
|
||||
}
|
||||
|
||||
function helpText(command: string): string {
|
||||
return execSync(`npx tsx src/index.ts ${command} --help`, {
|
||||
cwd: path.join(__dirname, ".."),
|
||||
encoding: "utf-8",
|
||||
timeout: 15000,
|
||||
});
|
||||
}
|
||||
|
||||
function assertAllReachable(
|
||||
endpoint: string,
|
||||
mapping: Record<string, string[]>,
|
||||
command: string,
|
||||
) {
|
||||
const documented = documentedFields(endpoint);
|
||||
const help = helpText(command);
|
||||
for (const field of documented) {
|
||||
if (KNOWN_UNSURFACED[endpoint]?.[field]) continue;
|
||||
const candidates = mapping[field];
|
||||
expect(
|
||||
candidates,
|
||||
`${endpoint}: documented field "${field}" has no mapping entry for command "${command}"`,
|
||||
).toBeDefined();
|
||||
const reachable = candidates.some((flag) =>
|
||||
flag.startsWith("--") ? help.includes(flag) : true,
|
||||
);
|
||||
expect(
|
||||
reachable,
|
||||
`${endpoint}: documented field "${field}" not reachable via any of ${JSON.stringify(candidates)} on command "${command}"`,
|
||||
).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
describe("Option parity: Node CLI reachability of documented v3 params", () => {
|
||||
it("add covers documented fields", () => {
|
||||
assertAllReachable("/v3/memories/add/", ADD_MAPPING, "add");
|
||||
});
|
||||
|
||||
it("search covers documented fields", () => {
|
||||
assertAllReachable("/v3/memories/search/", SEARCH_MAPPING, "search");
|
||||
});
|
||||
|
||||
it("list covers documented fields", () => {
|
||||
assertAllReachable("/v3/memories/", LIST_MAPPING, "list");
|
||||
});
|
||||
});
|
||||
|
||||
describe("stdin fallback uses the shared piped-stdin guard", () => {
|
||||
const SOURCES = ["src/index.ts", "src/commands/memory.ts"];
|
||||
|
||||
for (const rel of SOURCES) {
|
||||
it(`${rel} never checks process.stdin.isTTY directly`, () => {
|
||||
const src = fs.readFileSync(path.join(__dirname, "..", rel), "utf-8");
|
||||
expect(
|
||||
src.includes("process.stdin.isTTY"),
|
||||
`${rel}: use stdinIsPiped() from state.ts. A bare !isTTY check is also true for /dev/null and sockets, so readFileSync(0) crashes with EAGAIN in scripts, CI, and agent mode.`,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it(`${rel} guards every readFileSync(0) with stdinIsPiped()`, () => {
|
||||
const src = fs.readFileSync(path.join(__dirname, "..", rel), "utf-8");
|
||||
const lines = src.split("\n");
|
||||
for (const [i, line] of lines.entries()) {
|
||||
if (!line.includes("readFileSync(0")) continue;
|
||||
const guard = lines.slice(Math.max(0, i - 3), i).join("\n");
|
||||
expect(
|
||||
guard.includes("stdinIsPiped()"),
|
||||
`${rel}:${i + 1}: readFileSync(0) must be guarded by stdinIsPiped()`,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -7,94 +7,231 @@ import { PlatformBackend } from "../src/backend/platform.js";
|
||||
import { createDefaultConfig } from "../src/config.js";
|
||||
|
||||
function makeBackend(): PlatformBackend {
|
||||
// apiKey/baseUrl only build request headers; every test spies on _request,
|
||||
// so no real network calls are made.
|
||||
return new PlatformBackend(createDefaultConfig().platform);
|
||||
return new PlatformBackend(createDefaultConfig().platform);
|
||||
}
|
||||
|
||||
function mockFetch() {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
headers: { get: vi.fn().mockReturnValue(null) },
|
||||
json: vi.fn().mockResolvedValue({ message: "ok" }),
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
return fetchMock;
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
headers: { get: vi.fn().mockReturnValue(null) },
|
||||
json: vi.fn().mockResolvedValue({ message: "ok" }),
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
return fetchMock;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("deleteEntities", () => {
|
||||
it("returns all results keyed by entity type for a multi-entity delete", async () => {
|
||||
const backend = makeBackend();
|
||||
const responses: Record<string, unknown> = {
|
||||
"/v2/entities/user/alice/": { message: "user deleted" },
|
||||
"/v2/entities/agent/bob/": { message: "agent deleted" },
|
||||
};
|
||||
const spy = vi
|
||||
// biome-ignore lint/suspicious/noExplicitAny: spying on a private method
|
||||
.spyOn(backend as any, "_request")
|
||||
.mockImplementation(async (_method: string, path: string) => responses[path]);
|
||||
it("returns all results keyed by entity type for a multi-entity delete", async () => {
|
||||
const backend = makeBackend();
|
||||
const responses: Record<string, unknown> = {
|
||||
"/v2/entities/user/alice/": { message: "user deleted" },
|
||||
"/v2/entities/agent/bob/": { message: "agent deleted" },
|
||||
};
|
||||
const spy = vi
|
||||
// biome-ignore lint/suspicious/noExplicitAny: spying on a private method
|
||||
.spyOn(backend as any, "_request")
|
||||
.mockImplementation(
|
||||
async (_method: string, path: string) => responses[path],
|
||||
);
|
||||
|
||||
const result = await backend.deleteEntities({ userId: "alice", agentId: "bob" });
|
||||
const result = await backend.deleteEntities({
|
||||
userId: "alice",
|
||||
agentId: "bob",
|
||||
});
|
||||
|
||||
// Regression: previously only the last entity's response survived.
|
||||
expect(result).toEqual({
|
||||
user: { message: "user deleted" },
|
||||
agent: { message: "agent deleted" },
|
||||
});
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
expect(result).toEqual({
|
||||
user: { message: "user deleted" },
|
||||
agent: { message: "agent deleted" },
|
||||
});
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("keys a single-entity delete by its type", async () => {
|
||||
const backend = makeBackend();
|
||||
// biome-ignore lint/suspicious/noExplicitAny: spying on a private method
|
||||
vi.spyOn(backend as any, "_request").mockResolvedValue({ message: "user deleted" });
|
||||
it("keys a single-entity delete by its type", async () => {
|
||||
const backend = makeBackend();
|
||||
// biome-ignore lint/suspicious/noExplicitAny: spying on a private method
|
||||
vi.spyOn(backend as any, "_request").mockResolvedValue({
|
||||
message: "user deleted",
|
||||
});
|
||||
|
||||
const result = await backend.deleteEntities({ userId: "alice" });
|
||||
expect(result).toEqual({ user: { message: "user deleted" } });
|
||||
});
|
||||
const result = await backend.deleteEntities({ userId: "alice" });
|
||||
expect(result).toEqual({ user: { message: "user deleted" } });
|
||||
});
|
||||
|
||||
it("throws when no entity id is provided", async () => {
|
||||
const backend = makeBackend();
|
||||
await expect(backend.deleteEntities({})).rejects.toThrow(
|
||||
"At least one entity ID is required",
|
||||
);
|
||||
});
|
||||
it("throws when no entity id is provided", async () => {
|
||||
const backend = makeBackend();
|
||||
await expect(backend.deleteEntities({})).rejects.toThrow(
|
||||
"At least one entity ID is required",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PlatformBackend option-parity payloads (MEM-5893)", () => {
|
||||
it("add: custom_instructions, custom_categories, structured_data_schema, timestamp reach the payload alongside existing fields", async () => {
|
||||
const backend = makeBackend();
|
||||
const spy = vi
|
||||
// biome-ignore lint/suspicious/noExplicitAny: spying on a private method
|
||||
.spyOn(backend as any, "_request")
|
||||
.mockResolvedValue({ results: [] });
|
||||
|
||||
await backend.add("hello", undefined, {
|
||||
userId: "alice",
|
||||
metadata: { source: "test" },
|
||||
expires: "2099-01-01",
|
||||
customInstructions: "Extract only preferences.",
|
||||
customCategories: [{ prefs: "user preferences" }],
|
||||
structuredDataSchema: { type: "object" },
|
||||
timestamp: 1700000000,
|
||||
});
|
||||
|
||||
const payload = spy.mock.calls[0][2].json;
|
||||
expect(payload.custom_instructions).toBe("Extract only preferences.");
|
||||
expect(payload.custom_categories).toEqual([{ prefs: "user preferences" }]);
|
||||
expect(payload.structured_data_schema).toEqual({ type: "object" });
|
||||
expect(payload.timestamp).toBe(1700000000);
|
||||
expect(payload.metadata).toEqual({ source: "test" });
|
||||
expect(payload.expiration_date).toBe("2099-01-01");
|
||||
});
|
||||
|
||||
it("add: omitted optional fields are absent from the payload", async () => {
|
||||
const backend = makeBackend();
|
||||
const spy = vi
|
||||
// biome-ignore lint/suspicious/noExplicitAny: spying on a private method
|
||||
.spyOn(backend as any, "_request")
|
||||
.mockResolvedValue({ results: [] });
|
||||
|
||||
await backend.add("hello", undefined, { userId: "alice" });
|
||||
|
||||
const payload = spy.mock.calls[0][2].json;
|
||||
expect(payload).not.toHaveProperty("custom_instructions");
|
||||
expect(payload).not.toHaveProperty("custom_categories");
|
||||
expect(payload).not.toHaveProperty("structured_data_schema");
|
||||
expect(payload).not.toHaveProperty("timestamp");
|
||||
});
|
||||
|
||||
it("search: show_expired, reference_date, latest_only reach the payload", async () => {
|
||||
const backend = makeBackend();
|
||||
// biome-ignore lint/suspicious/noExplicitAny: spying on a private method
|
||||
const spy = vi.spyOn(backend as any, "_request").mockResolvedValue([]);
|
||||
|
||||
await backend.search("query", {
|
||||
showExpired: true,
|
||||
referenceDate: "2024-01-01",
|
||||
latestOnly: true,
|
||||
});
|
||||
|
||||
const payload = spy.mock.calls[0][2].json;
|
||||
expect(payload.show_expired).toBe(true);
|
||||
expect(payload.reference_date).toBe("2024-01-01");
|
||||
expect(payload.latest_only).toBe(true);
|
||||
});
|
||||
|
||||
it("search: keyword_search and fields reach the payload", async () => {
|
||||
const backend = makeBackend();
|
||||
// biome-ignore lint/suspicious/noExplicitAny: spying on a private method
|
||||
const spy = vi.spyOn(backend as any, "_request").mockResolvedValue([]);
|
||||
|
||||
await backend.search("query", {
|
||||
keyword: true,
|
||||
fields: ["memory", "score"],
|
||||
});
|
||||
|
||||
const payload = spy.mock.calls[0][2].json;
|
||||
expect(payload.keyword_search).toBe(true);
|
||||
expect(payload.fields).toEqual(["memory", "score"]);
|
||||
});
|
||||
|
||||
it("search: omitted keyword and fields are absent from the payload", async () => {
|
||||
const backend = makeBackend();
|
||||
// biome-ignore lint/suspicious/noExplicitAny: spying on a private method
|
||||
const spy = vi.spyOn(backend as any, "_request").mockResolvedValue([]);
|
||||
|
||||
await backend.search("query", {});
|
||||
|
||||
const payload = spy.mock.calls[0][2].json;
|
||||
expect(payload).not.toHaveProperty("keyword_search");
|
||||
expect(payload).not.toHaveProperty("fields");
|
||||
});
|
||||
|
||||
it("listMemories: show_expired and latest_only are top-level, not nested inside filters", async () => {
|
||||
const backend = makeBackend();
|
||||
// biome-ignore lint/suspicious/noExplicitAny: spying on a private method
|
||||
const spy = vi.spyOn(backend as any, "_request").mockResolvedValue([]);
|
||||
|
||||
await backend.listMemories({
|
||||
userId: "alice",
|
||||
showExpired: true,
|
||||
latestOnly: true,
|
||||
});
|
||||
|
||||
const payload = spy.mock.calls[0][2].json;
|
||||
expect(payload.show_expired).toBe(true);
|
||||
expect(payload.latest_only).toBe(true);
|
||||
expect(payload.filters ?? {}).not.toHaveProperty("show_expired");
|
||||
expect(payload.filters ?? {}).not.toHaveProperty("latest_only");
|
||||
});
|
||||
|
||||
it("update: expiration_date and timestamp reach the payload", async () => {
|
||||
const backend = makeBackend();
|
||||
// biome-ignore lint/suspicious/noExplicitAny: spying on a private method
|
||||
const spy = vi.spyOn(backend as any, "_request").mockResolvedValue({});
|
||||
|
||||
await backend.update("mem-123", undefined, undefined, {
|
||||
expirationDate: "2099-01-01",
|
||||
timestamp: 1700000000,
|
||||
});
|
||||
|
||||
const payload = spy.mock.calls[0][2].json;
|
||||
expect(payload.expiration_date).toBe("2099-01-01");
|
||||
expect(payload.timestamp).toBe(1700000000);
|
||||
});
|
||||
|
||||
it("delete: delete_linked is a query param, not part of the JSON body", async () => {
|
||||
const backend = makeBackend();
|
||||
// biome-ignore lint/suspicious/noExplicitAny: spying on a private method
|
||||
const spy = vi.spyOn(backend as any, "_request").mockResolvedValue({});
|
||||
|
||||
await backend.delete("mem-123", { deleteLinked: true });
|
||||
|
||||
const opts = spy.mock.calls[0][2];
|
||||
expect(opts.params.delete_linked).toBe("true");
|
||||
expect(opts.json).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("PlatformBackend path encoding", () => {
|
||||
it("encodes memory IDs before interpolating them into paths", async () => {
|
||||
const fetchMock = mockFetch();
|
||||
const backend = makeBackend();
|
||||
it("encodes memory IDs before interpolating them into paths", async () => {
|
||||
const fetchMock = mockFetch();
|
||||
const backend = makeBackend();
|
||||
|
||||
await backend.get("mem/a?b#c");
|
||||
await backend.update("mem/a?b#c", "updated");
|
||||
await backend.delete("mem/a?b#c");
|
||||
await backend.get("mem/a?b#c");
|
||||
await backend.update("mem/a?b#c", "updated");
|
||||
await backend.delete("mem/a?b#c");
|
||||
|
||||
const urls = fetchMock.mock.calls.map((call) => call[0]);
|
||||
expect(urls).toEqual([
|
||||
"https://api.mem0.ai/v1/memories/mem%2Fa%3Fb%23c/?source=CLI",
|
||||
"https://api.mem0.ai/v1/memories/mem%2Fa%3Fb%23c/",
|
||||
"https://api.mem0.ai/v1/memories/mem%2Fa%3Fb%23c/?source=CLI",
|
||||
]);
|
||||
});
|
||||
const urls = fetchMock.mock.calls.map((call) => call[0]);
|
||||
expect(urls).toEqual([
|
||||
"https://api.mem0.ai/v1/memories/mem%2Fa%3Fb%23c/?source=CLI",
|
||||
"https://api.mem0.ai/v1/memories/mem%2Fa%3Fb%23c/",
|
||||
"https://api.mem0.ai/v1/memories/mem%2Fa%3Fb%23c/?source=CLI",
|
||||
]);
|
||||
});
|
||||
|
||||
it("encodes entity and event IDs before interpolating them into paths", async () => {
|
||||
const fetchMock = mockFetch();
|
||||
const backend = makeBackend();
|
||||
it("encodes entity and event IDs before interpolating them into paths", async () => {
|
||||
const fetchMock = mockFetch();
|
||||
const backend = makeBackend();
|
||||
|
||||
await backend.deleteEntities({ userId: "org/team?active#frag" });
|
||||
await backend.getEvent("evt/a?b#c");
|
||||
await backend.deleteEntities({ userId: "org/team?active#frag" });
|
||||
await backend.getEvent("evt/a?b#c");
|
||||
|
||||
const urls = fetchMock.mock.calls.map((call) => call[0]);
|
||||
expect(urls).toEqual([
|
||||
"https://api.mem0.ai/v2/entities/user/org%2Fteam%3Factive%23frag/?source=CLI",
|
||||
"https://api.mem0.ai/v1/event/evt%2Fa%3Fb%23c/",
|
||||
]);
|
||||
});
|
||||
const urls = fetchMock.mock.calls.map((call) => call[0]);
|
||||
expect(urls).toEqual([
|
||||
"https://api.mem0.ai/v2/entities/user/org%2Fteam%3Factive%23frag/?source=CLI",
|
||||
"https://api.mem0.ai/v1/event/evt%2Fa%3Fb%23c/",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -241,14 +241,6 @@ Verify your API connection and display the current project.
|
||||
mem0 status
|
||||
```
|
||||
|
||||
### `mem0 version`
|
||||
|
||||
Print the CLI version.
|
||||
|
||||
```bash
|
||||
mem0 version
|
||||
```
|
||||
|
||||
## Agent mode
|
||||
|
||||
Pass `--agent` (or its alias `--json`) as a **global flag** on any command to get output designed for AI agent tool loops:
|
||||
@@ -307,6 +299,8 @@ These flags are available on all commands:
|
||||
| `--base-url` | Override the configured API base URL for this request |
|
||||
| `-o, --output` | Set the output format |
|
||||
|
||||
`mem0 --version` prints the CLI version. It is only valid before a subcommand, not after one.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Description |
|
||||
|
||||
@@ -80,7 +80,7 @@ mem0 --help
|
||||
# Using Python directly (with venv activated)
|
||||
source .venv/bin/activate
|
||||
mem0 --help
|
||||
mem0 version
|
||||
mem0 --version
|
||||
|
||||
# Or run without activating
|
||||
.venv/bin/mem0 --help
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "mem0-cli"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
description = "The official CLI for mem0 — the memory layer for AI agents"
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""mem0 CLI — the command-line interface for the mem0 memory layer."""
|
||||
|
||||
__version__ = "0.2.10"
|
||||
__version__ = "0.2.11"
|
||||
|
||||
@@ -273,7 +273,21 @@ def add(
|
||||
no_infer: bool = typer.Option(False, "--no-infer", help="Skip inference, store raw."),
|
||||
expires: str | None = typer.Option(None, "--expires", help="Expiration date (YYYY-MM-DD)."),
|
||||
categories: str | None = typer.Option(
|
||||
None, "--categories", help="Categories (JSON array or comma-separated)."
|
||||
None, "--categories", help="Not supported on add, use --custom-categories instead."
|
||||
),
|
||||
custom_instructions: str | None = typer.Option(
|
||||
None, "--custom-instructions", help="Custom instructions for fact extraction."
|
||||
),
|
||||
custom_categories: str | None = typer.Option(
|
||||
None,
|
||||
"--custom-categories",
|
||||
help="Custom categories as a JSON array of {name: description} objects.",
|
||||
),
|
||||
structured_data_schema: str | None = typer.Option(
|
||||
None, "--structured-data-schema", help="Schema for structured data extraction, as JSON."
|
||||
),
|
||||
timestamp: int | None = typer.Option(
|
||||
None, "--timestamp", help="Unix timestamp for the memory."
|
||||
),
|
||||
output: str = typer.Option(
|
||||
"text", "--output", "-o", help="Output format: text, json, quiet.", rich_help_panel="Output"
|
||||
@@ -312,6 +326,10 @@ def add(
|
||||
no_infer=no_infer,
|
||||
expires=expires,
|
||||
categories=categories,
|
||||
custom_instructions=custom_instructions,
|
||||
custom_categories=custom_categories,
|
||||
structured_data_schema=structured_data_schema,
|
||||
timestamp=timestamp,
|
||||
output=output,
|
||||
)
|
||||
|
||||
@@ -355,6 +373,21 @@ def search(
|
||||
help="Specific fields to return (comma-separated).",
|
||||
rich_help_panel="Search",
|
||||
),
|
||||
show_expired: bool = typer.Option(
|
||||
False, "--show-expired", help="Include expired memories.", rich_help_panel="Search"
|
||||
),
|
||||
reference_date: str | None = typer.Option(
|
||||
None,
|
||||
"--reference-date",
|
||||
help="Reference date for relative queries (YYYY-MM-DD or unix timestamp).",
|
||||
rich_help_panel="Search",
|
||||
),
|
||||
latest_only: bool = typer.Option(
|
||||
False,
|
||||
"--latest-only",
|
||||
help="Only return the latest version of each memory.",
|
||||
rich_help_panel="Search",
|
||||
),
|
||||
output: str = typer.Option(
|
||||
"text", "--output", "-o", help="Output: text, json, table.", rich_help_panel="Output"
|
||||
),
|
||||
@@ -398,6 +431,9 @@ def search(
|
||||
keyword=keyword,
|
||||
filter_json=filter_json,
|
||||
fields=fields,
|
||||
show_expired=show_expired,
|
||||
reference_date=reference_date,
|
||||
latest_only=latest_only,
|
||||
output=output,
|
||||
)
|
||||
|
||||
@@ -464,6 +500,15 @@ def list_cmd(
|
||||
before: str | None = typer.Option(
|
||||
None, "--before", help="Created before (YYYY-MM-DD).", rich_help_panel="Filters"
|
||||
),
|
||||
show_expired: bool = typer.Option(
|
||||
False, "--show-expired", help="Include expired memories.", rich_help_panel="Filters"
|
||||
),
|
||||
latest_only: bool = typer.Option(
|
||||
False,
|
||||
"--latest-only",
|
||||
help="Only return the latest version of each memory.",
|
||||
rich_help_panel="Filters",
|
||||
),
|
||||
output: str = typer.Option(
|
||||
"table", "--output", "-o", help="Output: text, json, table.", rich_help_panel="Output"
|
||||
),
|
||||
@@ -497,6 +542,8 @@ def list_cmd(
|
||||
category=category,
|
||||
after=after,
|
||||
before=before,
|
||||
show_expired=show_expired,
|
||||
latest_only=latest_only,
|
||||
output=output,
|
||||
)
|
||||
|
||||
@@ -509,6 +556,10 @@ def update(
|
||||
memory_id: str = typer.Argument(..., help="Memory ID to update."),
|
||||
text: str | None = typer.Argument(None, help="New memory text."),
|
||||
metadata: str | None = typer.Option(None, "--metadata", "-m", help="Update metadata (JSON)."),
|
||||
expires: str | None = typer.Option(None, "--expires", help="Expiration date (YYYY-MM-DD)."),
|
||||
timestamp: int | None = typer.Option(
|
||||
None, "--timestamp", help="Unix timestamp for the memory."
|
||||
),
|
||||
output: str = typer.Option(
|
||||
"text", "--output", "-o", help="Output: text, json, quiet.", rich_help_panel="Output"
|
||||
),
|
||||
@@ -537,7 +588,15 @@ def update(
|
||||
text = _read_stdin()
|
||||
|
||||
backend = _get_backend(api_key, base_url)
|
||||
cmd_update(backend, memory_id, text, metadata=metadata, output=output)
|
||||
cmd_update(
|
||||
backend,
|
||||
memory_id,
|
||||
text,
|
||||
metadata=metadata,
|
||||
expires=expires,
|
||||
timestamp=timestamp,
|
||||
output=output,
|
||||
)
|
||||
|
||||
|
||||
# ── Memory: delete ────────────────────────────────────────────────────────
|
||||
@@ -559,6 +618,9 @@ def delete(
|
||||
False, "--dry-run", help="Show what would be deleted without deleting."
|
||||
),
|
||||
force: bool = typer.Option(False, "--force", help="Skip confirmation."),
|
||||
delete_linked: bool = typer.Option(
|
||||
False, "--delete-linked", help="Also delete memories linked to this memory."
|
||||
),
|
||||
user_id: str | None = typer.Option(
|
||||
None, "--user-id", "-u", help="Scope to user.", rich_help_panel="Scope"
|
||||
),
|
||||
@@ -616,7 +678,14 @@ def delete(
|
||||
from mem0_cli.commands.memory import cmd_delete
|
||||
|
||||
backend = _get_backend(api_key, base_url)
|
||||
cmd_delete(backend, memory_id, dry_run=dry_run, force=force, output=output)
|
||||
cmd_delete(
|
||||
backend,
|
||||
memory_id,
|
||||
dry_run=dry_run,
|
||||
force=force,
|
||||
delete_linked=delete_linked,
|
||||
output=output,
|
||||
)
|
||||
|
||||
elif all_:
|
||||
_fire_telemetry("delete", {"delete_mode": "all"})
|
||||
@@ -1068,7 +1137,11 @@ def _build_help_json() -> dict:
|
||||
"--immutable": "Prevent future updates.",
|
||||
"--no-infer": "Skip inference, store raw.",
|
||||
"--expires": "Expiration date (YYYY-MM-DD).",
|
||||
"--categories": "Categories (JSON array or comma-separated).",
|
||||
"--categories": "Not supported on add, use --custom-categories instead.",
|
||||
"--custom-instructions": "Custom instructions for fact extraction.",
|
||||
"--custom-categories": "Custom categories as a JSON array of {name: description} objects.",
|
||||
"--structured-data-schema": "Schema for structured data extraction, as JSON.",
|
||||
"--timestamp": "Unix timestamp for the memory.",
|
||||
"--graph": "Enable graph memory extraction.",
|
||||
"--no-graph": "Disable graph memory extraction.",
|
||||
"--output, -o": "Output format: text, json, quiet.",
|
||||
@@ -1087,6 +1160,9 @@ def _build_help_json() -> dict:
|
||||
"--keyword": "Use keyword search instead of semantic.",
|
||||
"--filter": "Advanced filter expression (JSON).",
|
||||
"--fields": "Specific fields to return (comma-separated).",
|
||||
"--show-expired": "Include expired memories.",
|
||||
"--reference-date": "Reference date for relative queries (YYYY-MM-DD or unix timestamp).",
|
||||
"--latest-only": "Only return the latest version of each memory.",
|
||||
"--graph": "Enable graph in search.",
|
||||
"--no-graph": "Disable graph in search.",
|
||||
"--output, -o": "Output format: text, json, table.",
|
||||
@@ -1110,6 +1186,8 @@ def _build_help_json() -> dict:
|
||||
"--category": "Filter by category.",
|
||||
"--after": "Created after (YYYY-MM-DD).",
|
||||
"--before": "Created before (YYYY-MM-DD).",
|
||||
"--show-expired": "Include expired memories.",
|
||||
"--latest-only": "Only return the latest version of each memory.",
|
||||
"--graph": "Enable graph in listing.",
|
||||
"--no-graph": "Disable graph in listing.",
|
||||
"--output, -o": "Output format: text, json, table.",
|
||||
@@ -1124,6 +1202,8 @@ def _build_help_json() -> dict:
|
||||
},
|
||||
"options": {
|
||||
"--metadata, -m": "Update metadata (JSON).",
|
||||
"--expires": "Expiration date (YYYY-MM-DD).",
|
||||
"--timestamp": "Unix timestamp for the memory.",
|
||||
"--output, -o": "Output format: text, json, quiet.",
|
||||
},
|
||||
},
|
||||
@@ -1140,6 +1220,7 @@ def _build_help_json() -> dict:
|
||||
"--all": "Delete all memories matching scope filters.",
|
||||
"--entity": "Delete the entity itself and all its memories (cascade).",
|
||||
"--project": "With --all: delete ALL memories project-wide.",
|
||||
"--delete-linked": "Also delete memories linked to this memory.",
|
||||
"--dry-run": "Show what would be deleted without deleting.",
|
||||
"--force": "Skip confirmation.",
|
||||
"--user-id, -u": "Scope to user.",
|
||||
@@ -1269,8 +1350,10 @@ def help(
|
||||
mem0 help
|
||||
mem0 help --json
|
||||
"""
|
||||
if json:
|
||||
console.print(_json.dumps(_build_help_json(), indent=2))
|
||||
from mem0_cli.state import is_agent_mode
|
||||
|
||||
if json or is_agent_mode():
|
||||
console.print_json(_json.dumps(_build_help_json()))
|
||||
else:
|
||||
console.print(
|
||||
f"[{BRAND_COLOR}]◆ mem0 CLI[/] v{__version__} — The Memory Layer for AI Agents\n"
|
||||
|
||||
@@ -25,7 +25,10 @@ class Backend(ABC):
|
||||
immutable: bool = False,
|
||||
infer: bool = True,
|
||||
expires: str | None = None,
|
||||
categories: list[str] | None = None,
|
||||
custom_instructions: str | None = None,
|
||||
custom_categories: list[dict] | None = None,
|
||||
structured_data_schema: dict | None = None,
|
||||
timestamp: int | None = None,
|
||||
) -> dict: ...
|
||||
|
||||
@abstractmethod
|
||||
@@ -43,6 +46,9 @@ class Backend(ABC):
|
||||
keyword: bool = False,
|
||||
filters: dict | None = None,
|
||||
fields: list[str] | None = None,
|
||||
show_expired: bool = False,
|
||||
reference_date: str | None = None,
|
||||
latest_only: bool = False,
|
||||
) -> list[dict]: ...
|
||||
|
||||
@abstractmethod
|
||||
@@ -61,11 +67,19 @@ class Backend(ABC):
|
||||
category: str | None = None,
|
||||
after: str | None = None,
|
||||
before: str | None = None,
|
||||
show_expired: bool = False,
|
||||
latest_only: bool = False,
|
||||
) -> list[dict]: ...
|
||||
|
||||
@abstractmethod
|
||||
def update(
|
||||
self, memory_id: str, content: str | None = None, metadata: dict | None = None
|
||||
self,
|
||||
memory_id: str,
|
||||
content: str | None = None,
|
||||
metadata: dict | None = None,
|
||||
*,
|
||||
expiration_date: str | None = None,
|
||||
timestamp: int | None = None,
|
||||
) -> dict: ...
|
||||
|
||||
@abstractmethod
|
||||
@@ -78,6 +92,7 @@ class Backend(ABC):
|
||||
agent_id: str | None = None,
|
||||
app_id: str | None = None,
|
||||
run_id: str | None = None,
|
||||
delete_linked: bool = False,
|
||||
) -> dict: ...
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@@ -87,7 +87,10 @@ class PlatformBackend(Backend):
|
||||
immutable: bool = False,
|
||||
infer: bool = True,
|
||||
expires: str | None = None,
|
||||
categories: list[str] | None = None,
|
||||
custom_instructions: str | None = None,
|
||||
custom_categories: list[dict] | None = None,
|
||||
structured_data_schema: dict | None = None,
|
||||
timestamp: int | None = None,
|
||||
) -> dict:
|
||||
payload: dict[str, Any] = {}
|
||||
|
||||
@@ -112,8 +115,14 @@ class PlatformBackend(Backend):
|
||||
payload["infer"] = False
|
||||
if expires:
|
||||
payload["expiration_date"] = expires
|
||||
if categories:
|
||||
payload["categories"] = categories
|
||||
if custom_instructions:
|
||||
payload["custom_instructions"] = custom_instructions
|
||||
if custom_categories:
|
||||
payload["custom_categories"] = custom_categories
|
||||
if structured_data_schema:
|
||||
payload["structured_data_schema"] = structured_data_schema
|
||||
if timestamp is not None:
|
||||
payload["timestamp"] = timestamp
|
||||
payload["source"] = "CLI"
|
||||
|
||||
return self._request("POST", "/v3/memories/add/", json=payload)
|
||||
@@ -173,6 +182,9 @@ class PlatformBackend(Backend):
|
||||
keyword: bool = False,
|
||||
filters: dict | None = None,
|
||||
fields: list[str] | None = None,
|
||||
show_expired: bool = False,
|
||||
reference_date: str | None = None,
|
||||
latest_only: bool = False,
|
||||
) -> list[dict]:
|
||||
payload: dict[str, Any] = {"query": query, "top_k": top_k, "threshold": threshold}
|
||||
|
||||
@@ -191,6 +203,12 @@ class PlatformBackend(Backend):
|
||||
payload["keyword_search"] = True
|
||||
if fields:
|
||||
payload["fields"] = fields
|
||||
if show_expired:
|
||||
payload["show_expired"] = True
|
||||
if reference_date is not None:
|
||||
payload["reference_date"] = reference_date
|
||||
if latest_only:
|
||||
payload["latest_only"] = True
|
||||
payload["source"] = "CLI"
|
||||
|
||||
result = self._request("POST", "/v3/memories/search/", json=payload)
|
||||
@@ -219,6 +237,8 @@ class PlatformBackend(Backend):
|
||||
category: str | None = None,
|
||||
after: str | None = None,
|
||||
before: str | None = None,
|
||||
show_expired: bool = False,
|
||||
latest_only: bool = False,
|
||||
) -> list[dict]:
|
||||
payload: dict[str, Any] = {}
|
||||
params = {"page": str(page), "page_size": str(page_size)}
|
||||
@@ -241,6 +261,10 @@ class PlatformBackend(Backend):
|
||||
)
|
||||
if api_filters:
|
||||
payload["filters"] = api_filters
|
||||
if show_expired:
|
||||
payload["show_expired"] = True
|
||||
if latest_only:
|
||||
payload["latest_only"] = True
|
||||
payload["source"] = "CLI"
|
||||
|
||||
result = self._request("POST", "/v3/memories/", json=payload, params=params)
|
||||
@@ -251,13 +275,23 @@ class PlatformBackend(Backend):
|
||||
)
|
||||
|
||||
def update(
|
||||
self, memory_id: str, content: str | None = None, metadata: dict | None = None
|
||||
self,
|
||||
memory_id: str,
|
||||
content: str | None = None,
|
||||
metadata: dict | None = None,
|
||||
*,
|
||||
expiration_date: str | None = None,
|
||||
timestamp: int | None = None,
|
||||
) -> dict:
|
||||
payload: dict[str, Any] = {}
|
||||
if content:
|
||||
payload["text"] = content
|
||||
if metadata:
|
||||
payload["metadata"] = metadata
|
||||
if expiration_date:
|
||||
payload["expiration_date"] = expiration_date
|
||||
if timestamp is not None:
|
||||
payload["timestamp"] = timestamp
|
||||
payload["source"] = "CLI"
|
||||
return self._request(
|
||||
"PUT",
|
||||
@@ -274,6 +308,7 @@ class PlatformBackend(Backend):
|
||||
agent_id: str | None = None,
|
||||
app_id: str | None = None,
|
||||
run_id: str | None = None,
|
||||
delete_linked: bool = False,
|
||||
) -> dict:
|
||||
if all:
|
||||
params: dict[str, str] = {"source": "CLI"}
|
||||
@@ -287,10 +322,13 @@ class PlatformBackend(Backend):
|
||||
params["run_id"] = run_id
|
||||
return self._request("DELETE", "/v1/memories/", params=params)
|
||||
elif memory_id:
|
||||
params = {"source": "CLI"}
|
||||
if delete_linked:
|
||||
params["delete_linked"] = "true"
|
||||
return self._request(
|
||||
"DELETE",
|
||||
f"/v1/memories/{_encode_path_segment(memory_id)}/",
|
||||
params={"source": "CLI"},
|
||||
params=params,
|
||||
)
|
||||
else:
|
||||
raise ValueError("Either memory_id or --all is required")
|
||||
|
||||
@@ -4,9 +4,11 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import stat as _stat_mod
|
||||
import sys
|
||||
import time as _time
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
import typer
|
||||
@@ -47,6 +49,18 @@ def _stdin_is_piped() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _validate_expires(value: str) -> None:
|
||||
"""Exit 1 if value is not a future YYYY-MM-DD date."""
|
||||
if not re.match(r"^\d{4}-\d{2}-\d{2}$", value):
|
||||
print_error(
|
||||
err_console, "Invalid date format for --expires. Use YYYY-MM-DD (e.g. 2025-12-31)."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
if date.fromisoformat(value) <= date.today():
|
||||
print_error(err_console, "--expires date must be in the future.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
def cmd_add(
|
||||
backend: Backend,
|
||||
text: str | None,
|
||||
@@ -62,6 +76,10 @@ def cmd_add(
|
||||
no_infer: bool,
|
||||
expires: str | None,
|
||||
categories: str | None,
|
||||
custom_instructions: str | None = None,
|
||||
custom_categories: str | None = None,
|
||||
structured_data_schema: str | None = None,
|
||||
timestamp: int | None = None,
|
||||
output: str = "text",
|
||||
) -> None:
|
||||
"""Add a memory."""
|
||||
@@ -70,6 +88,13 @@ def cmd_add(
|
||||
set_current_command("add")
|
||||
if is_agent_mode():
|
||||
output = "agent"
|
||||
|
||||
if categories:
|
||||
print_error(
|
||||
err_console, "--categories is not supported on add. Use --custom-categories instead."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
msgs = None
|
||||
content = text
|
||||
|
||||
@@ -108,27 +133,24 @@ def cmd_add(
|
||||
print_error(err_console, "Invalid JSON in --metadata.")
|
||||
raise typer.Exit(1) from None
|
||||
|
||||
cats = None
|
||||
if categories:
|
||||
custom_cats = None
|
||||
if custom_categories:
|
||||
try:
|
||||
cats = json.loads(categories)
|
||||
custom_cats = json.loads(custom_categories)
|
||||
except json.JSONDecodeError:
|
||||
cats = [c.strip() for c in categories.split(",")]
|
||||
print_error(err_console, "Invalid JSON in --custom-categories.")
|
||||
raise typer.Exit(1) from None
|
||||
|
||||
schema = None
|
||||
if structured_data_schema:
|
||||
try:
|
||||
schema = json.loads(structured_data_schema)
|
||||
except json.JSONDecodeError:
|
||||
print_error(err_console, "Invalid JSON in --structured-data-schema.")
|
||||
raise typer.Exit(1) from None
|
||||
|
||||
# Validate --expires
|
||||
if expires:
|
||||
import re
|
||||
|
||||
if not re.match(r"^\d{4}-\d{2}-\d{2}$", expires):
|
||||
print_error(
|
||||
err_console, "Invalid date format for --expires. Use YYYY-MM-DD (e.g. 2025-12-31)."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
from datetime import date
|
||||
|
||||
if date.fromisoformat(expires) <= date.today():
|
||||
print_error(err_console, "--expires date must be in the future.")
|
||||
raise typer.Exit(1)
|
||||
_validate_expires(expires)
|
||||
|
||||
with timed_status(err_console, "Adding memory...") as ts:
|
||||
try:
|
||||
@@ -143,7 +165,10 @@ def cmd_add(
|
||||
immutable=immutable,
|
||||
infer=not no_infer,
|
||||
expires=expires,
|
||||
categories=cats,
|
||||
custom_instructions=custom_instructions,
|
||||
custom_categories=custom_cats,
|
||||
structured_data_schema=schema,
|
||||
timestamp=timestamp,
|
||||
)
|
||||
except Exception as e:
|
||||
ts.error_msg = str(e)
|
||||
@@ -224,6 +249,9 @@ def cmd_search(
|
||||
keyword: bool,
|
||||
filter_json: str | None,
|
||||
fields: str | None,
|
||||
show_expired: bool = False,
|
||||
reference_date: str | None = None,
|
||||
latest_only: bool = False,
|
||||
output: str = "text",
|
||||
) -> None:
|
||||
"""Search memories."""
|
||||
@@ -266,6 +294,9 @@ def cmd_search(
|
||||
keyword=keyword,
|
||||
filters=filters,
|
||||
fields=field_list,
|
||||
show_expired=show_expired,
|
||||
reference_date=reference_date,
|
||||
latest_only=latest_only,
|
||||
)
|
||||
except Exception as e:
|
||||
print_error(err_console, str(e))
|
||||
@@ -352,6 +383,8 @@ def cmd_list(
|
||||
category: str | None,
|
||||
after: str | None,
|
||||
before: str | None,
|
||||
show_expired: bool = False,
|
||||
latest_only: bool = False,
|
||||
output: str = "table",
|
||||
) -> None:
|
||||
"""List memories."""
|
||||
@@ -380,6 +413,8 @@ def cmd_list(
|
||||
category=category,
|
||||
after=after,
|
||||
before=before,
|
||||
show_expired=show_expired,
|
||||
latest_only=latest_only,
|
||||
)
|
||||
except Exception as e:
|
||||
print_error(err_console, str(e))
|
||||
@@ -446,6 +481,8 @@ def cmd_update(
|
||||
text: str | None,
|
||||
*,
|
||||
metadata: str | None,
|
||||
expires: str | None = None,
|
||||
timestamp: int | None = None,
|
||||
output: str,
|
||||
) -> None:
|
||||
"""Update a memory."""
|
||||
@@ -462,10 +499,19 @@ def cmd_update(
|
||||
print_error(err_console, "Invalid JSON in --metadata.")
|
||||
raise typer.Exit(1) from None
|
||||
|
||||
if expires:
|
||||
_validate_expires(expires)
|
||||
|
||||
_start = _time.perf_counter()
|
||||
with timed_status(err_console, "Updating memory...") as _ts:
|
||||
try:
|
||||
result = backend.update(memory_id, content=text, metadata=meta)
|
||||
result = backend.update(
|
||||
memory_id,
|
||||
content=text,
|
||||
metadata=meta,
|
||||
expiration_date=expires,
|
||||
timestamp=timestamp,
|
||||
)
|
||||
except Exception as e:
|
||||
print_error(err_console, str(e))
|
||||
raise typer.Exit(1) from None
|
||||
@@ -490,6 +536,7 @@ def cmd_delete(
|
||||
*,
|
||||
dry_run: bool = False,
|
||||
force: bool = False,
|
||||
delete_linked: bool = False,
|
||||
output: str,
|
||||
) -> None:
|
||||
"""Delete a single memory by ID."""
|
||||
@@ -512,7 +559,7 @@ def cmd_delete(
|
||||
_start = _time.perf_counter()
|
||||
with timed_status(err_console, "Deleting...") as _ts:
|
||||
try:
|
||||
result = backend.delete(memory_id=memory_id)
|
||||
result = backend.delete(memory_id=memory_id, delete_linked=delete_linked)
|
||||
except Exception as e:
|
||||
print_error(err_console, str(e))
|
||||
raise typer.Exit(1) from None
|
||||
|
||||
@@ -262,16 +262,32 @@ def sanitize_agent_data(command: str, data: Any) -> Any:
|
||||
return result
|
||||
|
||||
if command == "search":
|
||||
return [pick(r, ["id", "memory", "score", "created_at", "categories"]) for r in data]
|
||||
return [
|
||||
pick(r, ["id", "memory", "score", "created_at", "categories", "expiration_date"])
|
||||
for r in data
|
||||
]
|
||||
|
||||
if command == "list":
|
||||
return [pick(r, ["id", "memory", "created_at", "categories"]) for r in data]
|
||||
return [
|
||||
pick(r, ["id", "memory", "created_at", "categories", "expiration_date"]) for r in data
|
||||
]
|
||||
|
||||
if command == "get":
|
||||
return pick(data, ["id", "memory", "created_at", "updated_at", "categories", "metadata"])
|
||||
return pick(
|
||||
data,
|
||||
[
|
||||
"id",
|
||||
"memory",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"categories",
|
||||
"metadata",
|
||||
"expiration_date",
|
||||
],
|
||||
)
|
||||
|
||||
if command == "update":
|
||||
return pick(data, ["id", "memory"])
|
||||
return pick(data, ["id", "memory", "expiration_date"])
|
||||
|
||||
if command in ("delete", "delete-all", "entity delete"):
|
||||
return data
|
||||
|
||||
@@ -7,6 +7,7 @@ boundaries).
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
@@ -84,6 +85,31 @@ class TestCLIIntegration:
|
||||
assert "add" in result.stdout
|
||||
assert "search" in result.stdout
|
||||
|
||||
def test_version_flag_only(self):
|
||||
from mem0_cli import __version__
|
||||
|
||||
flag = _run(["--version"])
|
||||
assert flag.returncode == 0
|
||||
assert __version__ in flag.stdout
|
||||
assert _run(["version"]).returncode != 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"args",
|
||||
[["help", "--json"], ["--json", "help"], ["help", "--agent"], ["--agent", "help"]],
|
||||
)
|
||||
def test_help_json_produces_valid_json(self, args):
|
||||
result = _run(args)
|
||||
assert result.returncode == 0
|
||||
spec = json.loads(result.stdout)
|
||||
assert spec["name"] == "mem0"
|
||||
assert "add" in spec["commands"]
|
||||
|
||||
def test_help_without_json_is_text(self):
|
||||
result = _run(["help"])
|
||||
assert result.returncode == 0
|
||||
with pytest.raises(json.JSONDecodeError):
|
||||
json.loads(result.stdout)
|
||||
|
||||
def test_add_help(self):
|
||||
result = _run(["add", "--help"])
|
||||
assert result.returncode == 0
|
||||
|
||||
@@ -252,12 +252,13 @@ class TestAddCommand:
|
||||
)
|
||||
mock_backend.add.assert_called_once()
|
||||
|
||||
def test_add_categories_csv(self, mock_backend):
|
||||
def test_add_categories_rejected(self, mock_backend):
|
||||
console, _buf = _make_console()
|
||||
err_console, _err_buf = _make_err_console()
|
||||
err_console, err_buf = _make_err_console()
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_add(
|
||||
mock_backend,
|
||||
@@ -275,7 +276,93 @@ class TestAddCommand:
|
||||
categories="health,prefs",
|
||||
output="text",
|
||||
)
|
||||
mock_backend.add.assert_called_once()
|
||||
assert "--custom-categories" in err_buf.getvalue()
|
||||
mock_backend.add.assert_not_called()
|
||||
|
||||
def test_add_invalid_custom_categories_json(self, mock_backend):
|
||||
console, _buf = _make_console()
|
||||
err_console, err_buf = _make_err_console()
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_add(
|
||||
mock_backend,
|
||||
"test",
|
||||
user_id="alice",
|
||||
agent_id=None,
|
||||
app_id=None,
|
||||
run_id=None,
|
||||
messages=None,
|
||||
file=None,
|
||||
metadata=None,
|
||||
immutable=False,
|
||||
no_infer=False,
|
||||
expires=None,
|
||||
categories=None,
|
||||
custom_categories="not-json",
|
||||
output="text",
|
||||
)
|
||||
assert "--custom-categories" in err_buf.getvalue()
|
||||
mock_backend.add.assert_not_called()
|
||||
|
||||
def test_add_invalid_structured_data_schema_json(self, mock_backend):
|
||||
console, _buf = _make_console()
|
||||
err_console, err_buf = _make_err_console()
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_add(
|
||||
mock_backend,
|
||||
"test",
|
||||
user_id="alice",
|
||||
agent_id=None,
|
||||
app_id=None,
|
||||
run_id=None,
|
||||
messages=None,
|
||||
file=None,
|
||||
metadata=None,
|
||||
immutable=False,
|
||||
no_infer=False,
|
||||
expires=None,
|
||||
categories=None,
|
||||
structured_data_schema="not-json",
|
||||
output="text",
|
||||
)
|
||||
assert "--structured-data-schema" in err_buf.getvalue()
|
||||
mock_backend.add.assert_not_called()
|
||||
|
||||
def test_add_regression_metadata_expiration_custom_categories_together(self, mock_backend):
|
||||
console, _buf = _make_console()
|
||||
err_console, _err_buf = _make_err_console()
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
):
|
||||
cmd_add(
|
||||
mock_backend,
|
||||
"test",
|
||||
user_id="alice",
|
||||
agent_id=None,
|
||||
app_id=None,
|
||||
run_id=None,
|
||||
messages=None,
|
||||
file=None,
|
||||
metadata='{"source": "test"}',
|
||||
immutable=False,
|
||||
no_infer=False,
|
||||
expires="2099-01-01",
|
||||
categories=None,
|
||||
custom_categories='[{"prefs": "user preferences"}]',
|
||||
output="text",
|
||||
)
|
||||
call_kwargs = mock_backend.add.call_args.kwargs
|
||||
assert call_kwargs["metadata"] == {"source": "test"}
|
||||
assert call_kwargs["expires"] == "2099-01-01"
|
||||
assert call_kwargs["custom_categories"] == [{"prefs": "user preferences"}]
|
||||
|
||||
|
||||
class TestAddDeduplicatesPending:
|
||||
@@ -464,6 +551,36 @@ class TestSearchCommand:
|
||||
)
|
||||
mock_backend.search.assert_called_once()
|
||||
|
||||
def test_search_new_flags_reach_backend(self, mock_backend):
|
||||
console, _buf = _make_console()
|
||||
err_console, _err_buf = _make_err_console()
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
):
|
||||
cmd_search(
|
||||
mock_backend,
|
||||
"preferences",
|
||||
user_id="alice",
|
||||
agent_id=None,
|
||||
app_id=None,
|
||||
run_id=None,
|
||||
top_k=10,
|
||||
threshold=0.3,
|
||||
rerank=False,
|
||||
keyword=False,
|
||||
filter_json=None,
|
||||
fields=None,
|
||||
show_expired=True,
|
||||
reference_date="2024-01-01",
|
||||
latest_only=True,
|
||||
output="text",
|
||||
)
|
||||
call_kwargs = mock_backend.search.call_args.kwargs
|
||||
assert call_kwargs["show_expired"] is True
|
||||
assert call_kwargs["reference_date"] == "2024-01-01"
|
||||
assert call_kwargs["latest_only"] is True
|
||||
|
||||
|
||||
class TestGetCommand:
|
||||
def test_get_text(self, mock_backend):
|
||||
@@ -561,6 +678,32 @@ class TestListCommand:
|
||||
output = buf.getvalue()
|
||||
assert "No memories found" in output
|
||||
|
||||
def test_list_new_flags_reach_backend(self, mock_backend):
|
||||
console, _buf = _make_console()
|
||||
err_console, _err_buf = _make_err_console()
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
):
|
||||
cmd_list(
|
||||
mock_backend,
|
||||
user_id="alice",
|
||||
agent_id=None,
|
||||
app_id=None,
|
||||
run_id=None,
|
||||
page=1,
|
||||
page_size=100,
|
||||
category=None,
|
||||
after=None,
|
||||
before=None,
|
||||
show_expired=True,
|
||||
latest_only=True,
|
||||
output="table",
|
||||
)
|
||||
call_kwargs = mock_backend.list_memories.call_args.kwargs
|
||||
assert call_kwargs["show_expired"] is True
|
||||
assert call_kwargs["latest_only"] is True
|
||||
|
||||
|
||||
class TestUpdateCommand:
|
||||
def test_update(self, mock_backend):
|
||||
@@ -585,6 +728,26 @@ class TestUpdateCommand:
|
||||
output = buf.getvalue()
|
||||
assert '"memory"' in output
|
||||
|
||||
def test_update_new_fields_reach_backend(self, mock_backend):
|
||||
console, _buf = _make_console()
|
||||
err_console, _err_buf = _make_err_console()
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
):
|
||||
cmd_update(
|
||||
mock_backend,
|
||||
"abc-123",
|
||||
"New text",
|
||||
metadata=None,
|
||||
expires="2099-01-01",
|
||||
timestamp=1700000000,
|
||||
output="text",
|
||||
)
|
||||
call_kwargs = mock_backend.update.call_args.kwargs
|
||||
assert call_kwargs["expiration_date"] == "2099-01-01"
|
||||
assert call_kwargs["timestamp"] == 1700000000
|
||||
|
||||
|
||||
class TestDeleteCommand:
|
||||
def test_delete_single(self, mock_backend):
|
||||
@@ -610,6 +773,17 @@ class TestDeleteCommand:
|
||||
assert "dry run" in output.lower()
|
||||
mock_backend.delete.assert_not_called()
|
||||
|
||||
def test_delete_linked_reaches_backend(self, mock_backend):
|
||||
console, _buf = _make_console()
|
||||
err_console, _err_buf = _make_err_console()
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
):
|
||||
cmd_delete(mock_backend, "abc-123", delete_linked=True, output="text")
|
||||
call_kwargs = mock_backend.delete.call_args.kwargs
|
||||
assert call_kwargs["delete_linked"] is True
|
||||
|
||||
|
||||
class TestDeleteAllCommand:
|
||||
def test_delete_all_force(self, mock_backend):
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
"""Drift test: every documented v3 add/search/list param must be reachable from the Python CLI."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import typer.main
|
||||
|
||||
from mem0_cli.app import app
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||
OPENAPI_PATH = REPO_ROOT / "docs" / "openapi.json"
|
||||
|
||||
KNOWN_UNSURFACED: dict[tuple[str, str], str] = {
|
||||
("/v3/memories/add/", "includes"): "extraction hint, no CLI flag yet",
|
||||
("/v3/memories/add/", "excludes"): "extraction hint, no CLI flag yet",
|
||||
("/v3/memories/add/", "enable_graph"): "graph memory toggle, no CLI flag yet",
|
||||
("/v3/memories/add/", "output_format"): "response envelope is pinned by the CLI",
|
||||
("/v3/memories/add/", "prompt_profile_id"): "no CLI flag yet",
|
||||
("/v3/memories/add/", "temporal_reasoning"): "no CLI flag yet",
|
||||
("/v3/memories/add/", "timezone"): "no CLI flag yet",
|
||||
("/v3/memories/add/", "observation_datetime"): "no CLI flag yet, --timestamp backdates instead",
|
||||
("/v3/memories/add/", "observation_date"): "no CLI flag yet, --timestamp backdates instead",
|
||||
("/v3/memories/search/", "categories"): "expressible through --filter",
|
||||
("/v3/memories/search/", "metadata"): "expressible through --filter",
|
||||
("/v3/memories/", "start_date"): "covered by --after via filters.created_at.gte",
|
||||
("/v3/memories/", "end_date"): "covered by --before via filters.created_at.lte",
|
||||
("/v3/memories/", "categories"): "covered by --category via filters.categories",
|
||||
("/v3/memories/", "fields"): "no CLI flag yet",
|
||||
("/v3/memories/", "keywords"): "no CLI flag yet",
|
||||
}
|
||||
|
||||
ADD_MAPPING: dict[str, list[str]] = {
|
||||
"messages": ["messages", "file", "text"],
|
||||
"user_id": ["user_id"],
|
||||
"agent_id": ["agent_id"],
|
||||
"app_id": ["app_id"],
|
||||
"run_id": ["run_id"],
|
||||
"metadata": ["metadata"],
|
||||
"expiration_date": ["expires"],
|
||||
"custom_instructions": ["custom_instructions"],
|
||||
"custom_categories": ["custom_categories"],
|
||||
"infer": ["no_infer"],
|
||||
"immutable": ["immutable"],
|
||||
"structured_data_schema": ["structured_data_schema"],
|
||||
"timestamp": ["timestamp"],
|
||||
}
|
||||
|
||||
SEARCH_MAPPING: dict[str, list[str]] = {
|
||||
"query": ["query"],
|
||||
"filters": ["filter_json", "user_id", "agent_id", "run_id"],
|
||||
"show_expired": ["show_expired"],
|
||||
"top_k": ["top_k"],
|
||||
"threshold": ["threshold"],
|
||||
"rerank": ["rerank"],
|
||||
"reference_date": ["reference_date"],
|
||||
"fields": ["fields"],
|
||||
}
|
||||
|
||||
LIST_MAPPING: dict[str, list[str]] = {
|
||||
"filters": ["user_id", "agent_id", "run_id", "category", "after", "before"],
|
||||
"show_expired": ["show_expired"],
|
||||
"page": ["page"],
|
||||
"page_size": ["page_size"],
|
||||
}
|
||||
|
||||
|
||||
def _documented_fields(endpoint: str) -> set[str]:
|
||||
spec = json.loads(OPENAPI_PATH.read_text())
|
||||
schema = spec["paths"][endpoint]["post"]["requestBody"]["content"]["application/json"]["schema"]
|
||||
return set(schema["properties"])
|
||||
|
||||
|
||||
def _cli_param_names(command_name: str) -> set[str]:
|
||||
click_app = typer.main.get_command(app)
|
||||
command = click_app.commands[command_name]
|
||||
return {param.name for param in command.params}
|
||||
|
||||
|
||||
def _assert_all_reachable(endpoint: str, mapping: dict[str, list[str]], command_name: str) -> None:
|
||||
documented = _documented_fields(endpoint)
|
||||
reachable = _cli_param_names(command_name)
|
||||
for field in documented:
|
||||
if (endpoint, field) in KNOWN_UNSURFACED:
|
||||
continue
|
||||
candidates = mapping.get(field)
|
||||
assert candidates, (
|
||||
f"{endpoint}: documented field {field!r} has no mapping entry for command {command_name!r}"
|
||||
)
|
||||
assert any(candidate in reachable for candidate in candidates), (
|
||||
f"{endpoint}: documented field {field!r} not reachable via any of {candidates} on command {command_name!r}"
|
||||
)
|
||||
|
||||
|
||||
class TestOptionParity:
|
||||
def test_add_covers_documented_fields(self):
|
||||
_assert_all_reachable("/v3/memories/add/", ADD_MAPPING, "add")
|
||||
|
||||
def test_search_covers_documented_fields(self):
|
||||
_assert_all_reachable("/v3/memories/search/", SEARCH_MAPPING, "search")
|
||||
|
||||
def test_list_covers_documented_fields(self):
|
||||
_assert_all_reachable("/v3/memories/", LIST_MAPPING, "list")
|
||||
@@ -0,0 +1,109 @@
|
||||
"""Tests that the MEM-5893 option-parity flags reach the correct request payload/params."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from mem0_cli.backend.platform import PlatformBackend
|
||||
from mem0_cli.config import PlatformConfig
|
||||
|
||||
|
||||
def _make_backend() -> PlatformBackend:
|
||||
return PlatformBackend(PlatformConfig(api_key="test-key", base_url="https://api.mem0.ai"))
|
||||
|
||||
|
||||
class TestAddOptions:
|
||||
def test_new_fields_and_existing_fields_land_in_payload_together(self):
|
||||
backend = _make_backend()
|
||||
with patch.object(backend, "_request", return_value={"results": []}) as mock_request:
|
||||
backend.add(
|
||||
content="hello",
|
||||
user_id="alice",
|
||||
metadata={"source": "test"},
|
||||
expires="2099-01-01",
|
||||
custom_instructions="Extract only preferences.",
|
||||
custom_categories=[{"prefs": "user preferences"}],
|
||||
structured_data_schema={"type": "object"},
|
||||
timestamp=1700000000,
|
||||
)
|
||||
payload = mock_request.call_args.kwargs["json"]
|
||||
assert payload["custom_instructions"] == "Extract only preferences."
|
||||
assert payload["custom_categories"] == [{"prefs": "user preferences"}]
|
||||
assert payload["structured_data_schema"] == {"type": "object"}
|
||||
assert payload["timestamp"] == 1700000000
|
||||
assert payload["metadata"] == {"source": "test"}
|
||||
assert payload["expiration_date"] == "2099-01-01"
|
||||
|
||||
def test_omitted_fields_are_absent_from_payload(self):
|
||||
backend = _make_backend()
|
||||
with patch.object(backend, "_request", return_value={"results": []}) as mock_request:
|
||||
backend.add(content="hello", user_id="alice")
|
||||
payload = mock_request.call_args.kwargs["json"]
|
||||
assert "custom_instructions" not in payload
|
||||
assert "custom_categories" not in payload
|
||||
assert "structured_data_schema" not in payload
|
||||
assert "timestamp" not in payload
|
||||
|
||||
|
||||
class TestSearchOptions:
|
||||
def test_show_expired_reference_date_latest_only_reach_payload(self):
|
||||
backend = _make_backend()
|
||||
with patch.object(backend, "_request", return_value=[]) as mock_request:
|
||||
backend.search(
|
||||
"query",
|
||||
show_expired=True,
|
||||
reference_date="2024-01-01",
|
||||
latest_only=True,
|
||||
)
|
||||
payload = mock_request.call_args.kwargs["json"]
|
||||
assert payload["show_expired"] is True
|
||||
assert payload["reference_date"] == "2024-01-01"
|
||||
assert payload["latest_only"] is True
|
||||
|
||||
def test_keyword_and_fields_reach_payload(self):
|
||||
backend = _make_backend()
|
||||
with patch.object(backend, "_request", return_value=[]) as mock_request:
|
||||
backend.search("query", keyword=True, fields=["memory", "score"])
|
||||
payload = mock_request.call_args.kwargs["json"]
|
||||
assert payload["keyword_search"] is True
|
||||
assert payload["fields"] == ["memory", "score"]
|
||||
|
||||
def test_keyword_and_fields_omitted_are_absent_from_payload(self):
|
||||
backend = _make_backend()
|
||||
with patch.object(backend, "_request", return_value=[]) as mock_request:
|
||||
backend.search("query")
|
||||
payload = mock_request.call_args.kwargs["json"]
|
||||
assert "keyword_search" not in payload
|
||||
assert "fields" not in payload
|
||||
|
||||
|
||||
class TestListOptions:
|
||||
def test_show_expired_and_latest_only_are_top_level_not_in_filters(self):
|
||||
backend = _make_backend()
|
||||
with patch.object(backend, "_request", return_value=[]) as mock_request:
|
||||
backend.list_memories(user_id="alice", show_expired=True, latest_only=True)
|
||||
payload = mock_request.call_args.kwargs["json"]
|
||||
assert payload["show_expired"] is True
|
||||
assert payload["latest_only"] is True
|
||||
assert "show_expired" not in payload.get("filters", {})
|
||||
assert "latest_only" not in payload.get("filters", {})
|
||||
|
||||
|
||||
class TestUpdateOptions:
|
||||
def test_expires_and_timestamp_reach_payload(self):
|
||||
backend = _make_backend()
|
||||
with patch.object(backend, "_request", return_value={}) as mock_request:
|
||||
backend.update("mem-123", expiration_date="2099-01-01", timestamp=1700000000)
|
||||
payload = mock_request.call_args.kwargs["json"]
|
||||
assert payload["expiration_date"] == "2099-01-01"
|
||||
assert payload["timestamp"] == 1700000000
|
||||
|
||||
|
||||
class TestDeleteOptions:
|
||||
def test_delete_linked_is_a_query_param_not_json_body(self):
|
||||
backend = _make_backend()
|
||||
with patch.object(backend, "_request", return_value={}) as mock_request:
|
||||
backend.delete(memory_id="mem-123", delete_linked=True)
|
||||
call = mock_request.call_args
|
||||
assert call.kwargs["params"]["delete_linked"] == "true"
|
||||
assert "json" not in call.kwargs
|
||||
@@ -65,9 +65,8 @@ The request is queued for background processing. The response contains an `event
|
||||
<CodeGroup>
|
||||
```json 200 response
|
||||
{
|
||||
"message": "Memory processing has been queued for background execution",
|
||||
"status": "PENDING",
|
||||
"event_id": "evt-uuid"
|
||||
"event_id": "evt-uuid",
|
||||
"status": "PENDING"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ new_project = client.project.create(
|
||||
|
||||
### Update Project Settings
|
||||
|
||||
Modify project configuration including custom instructions, categories, language preferences, retrieval criteria, and memory decay:
|
||||
Modify project configuration including custom instructions, categories, language preferences, and memory decay:
|
||||
|
||||
```python
|
||||
# Update project with custom categories
|
||||
@@ -98,14 +98,6 @@ client.project.update(
|
||||
# Use the input language for memory storage and retrieval
|
||||
client.project.update(multilingual=True)
|
||||
|
||||
# Set retrieval criteria to control which memories are surfaced in search
|
||||
client.project.update(
|
||||
retrieval_criteria=[
|
||||
{"name": "relevance", "description": "How directly relevant this memory is to the current topic or user query", "weight": 3},
|
||||
{"name": "access_frequency", "description": "How often this memory has been accessed or surfaced recently", "weight": 1}
|
||||
]
|
||||
)
|
||||
|
||||
# Enable Memory Decay (boosts recently-accessed memories at search time)
|
||||
client.project.update(decay=True)
|
||||
|
||||
@@ -120,34 +112,6 @@ client.project.update(
|
||||
)
|
||||
```
|
||||
|
||||
#### Set Retrieval Criteria
|
||||
|
||||
`retrieval_criteria` is a per-project list of dictionaries (`List[Dict]`) that shapes how memories are ranked and filtered during search. Each dictionary has three fields: `name` (identifier), `description` (interpreted by the LLM to score each memory), and `weight` (relative influence on the final score). Use this to focus retrieval on intent-aligned or signal-specific memories:
|
||||
|
||||
```python
|
||||
client.project.update(
|
||||
retrieval_criteria=[
|
||||
{
|
||||
"name": "joy",
|
||||
"description": "Measure the intensity of positive emotions such as happiness, excitement, or amusement expressed in the memory. A higher score reflects greater joy.",
|
||||
"weight": 3
|
||||
},
|
||||
{
|
||||
"name": "curiosity",
|
||||
"description": "Assess the extent to which the memory reflects inquisitiveness or interest in exploring new information. A higher score reflects stronger curiosity.",
|
||||
"weight": 2
|
||||
},
|
||||
{
|
||||
"name": "access_frequency",
|
||||
"description": "How often this memory has been accessed or surfaced recently.",
|
||||
"weight": 1
|
||||
}
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
Pass an empty list to clear all criteria and restore default retrieval behaviour.
|
||||
|
||||
#### Toggle Memory Decay
|
||||
|
||||
`decay` is a per-project boolean that turns on [Memory Decay](/platform/features/memory-decay): a search-time ranking bias that reinforces recently-accessed memories and gently dampens stale ones. The flag is `false` by default; set it via the same project-update endpoint:
|
||||
|
||||
@@ -4,6 +4,23 @@ description: "Major product launches, headline features, and milestones for Mem0
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
<Update label="2026-07-30" description="n8n and Zapier integrations">
|
||||
|
||||
**Workflow Automation: Mem0 Memory in n8n and Zapier**
|
||||
|
||||
Mem0 now plugs into two no-code automation platforms, so workflows that used to start from zero on every run can store durable facts and recall them later.
|
||||
|
||||
- **n8n community node:** [`@mem0/n8n-nodes-mem0`](https://www.npmjs.com/package/@mem0/n8n-nodes-mem0) adds a **Mem0** node with a Memory resource covering Add, Search, Get, Get Many, Update, and Delete. Install it from **Settings → Community Nodes** on a self-hosted instance, then connect your API key once as a Mem0 API credential. See [n8n](/integrations/n8n).
|
||||
- **n8n AI Agent tool:** Attach the same node to an [AI Agent](https://docs.n8n.io/advanced-ai/) node and it becomes a tool the agent calls on its own, so it can decide when to remember and when to recall.
|
||||
- **Zapier app:** Add Memory, Search Memories, Get Memories, and Delete Memory actions let any of Zapier's thousands of apps write and read Mem0 context with no code and no server. See [Zapier](/integrations/zapier).
|
||||
- **One-time connection:** Both integrations authenticate with a single Mem0 API key and default to `https://api.mem0.ai`, with a configurable base URL for self-hosted deployments.
|
||||
|
||||
<Note>
|
||||
The Zapier app is not yet listed in Zapier's public App Directory. Email [support@mem0.ai](mailto:support@mem0.ai) for an invite link.
|
||||
</Note>
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-13" description="TypeScript provider expansion">
|
||||
|
||||
**TypeScript OSS SDK: 26 New Providers, Reranking, and Zero-Dependency Imports**
|
||||
|
||||
@@ -7,6 +7,67 @@ mode: "wide"
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
|
||||
<Update label="2026-08-04" description="v2.0.16">
|
||||
|
||||
**New Features:**
|
||||
- **Client:** Add `reference_date`, `latest_only`, and `keyword_search` to `SearchMemoryOptions`, and `latest_only` to `GetAllMemoryOptions`, keeping the Python client's typed options in sync with the Platform API and the CLIs ([#6696](https://github.com/mem0ai/mem0/pull/6696))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Core:** Stop `add()` metadata from setting a memory's identity scope. `_build_filters_and_metadata()` now strips `user_id`, `agent_id`, `run_id`, and `actor_id` from caller-supplied `metadata` before building the creation template, so metadata can no longer place a memory into a scope that was never passed through the entity params ([#6656](https://github.com/mem0ai/mem0/pull/6656))
|
||||
- **Vector Stores:** Validate Upstash filter keys and values in `search()`, `keyword_search()`, and `list()`. Filter keys must match a safe identifier pattern, values must be `str`/`int`/`float`/`bool`, and string values containing a double quote or backslash are now rejected instead of being interpolated unescaped into the generated query string ([#5981](https://github.com/mem0ai/mem0/pull/5981))
|
||||
- **Embeddings:** `FastEmbedEmbedding.embed()` now converts its result with `.tolist()` before returning, so callers get a plain `List[float]` instead of a numpy array ([#6770](https://github.com/mem0ai/mem0/pull/6770))
|
||||
- **Embeddings:** `HuggingFaceEmbedding` now passes `api_key` to the OpenAI-compatible client when `huggingface_base_url` is set. The configured key was previously dropped, so the client fell back to `OPENAI_API_KEY` from the environment or raised `OpenAIError` at construction when that was unset ([#6770](https://github.com/mem0ai/mem0/pull/6770))
|
||||
- **Embeddings:** Replace Ollama's interactive `pip install` prompt on import with a plain `ImportError`. Importing `mem0.embeddings.ollama` without the `ollama` package previously blocked on stdin and then called `sys.exit(1)`, killing the host process instead of raising ([#6770](https://github.com/mem0ai/mem0/pull/6770))
|
||||
- **Core:** `remove_code_blocks()` now returns an empty string for `None` input instead of raising `AttributeError` ([#6770](https://github.com/mem0ai/mem0/pull/6770))
|
||||
- **Core:** `parse_vision_messages()` now chains the original exception (`raise ... from e`) when an image download fails, so the root cause is preserved in the traceback ([#6770](https://github.com/mem0ai/mem0/pull/6770))
|
||||
- **Core:** `process_telemetry_filters(None)` now returns `([], {})`, matching the two-value tuple every caller unpacks, instead of `{}` ([#6770](https://github.com/mem0ai/mem0/pull/6770))
|
||||
- **Core:** `LlmFactory.create()` no longer mutates the caller's config dict in place via `.update(kwargs)`; the merge now builds a new dict ([#6770](https://github.com/mem0ai/mem0/pull/6770))
|
||||
- **Rerankers:** The Cohere, HuggingFace, SentenceTransformer, and Zero Entropy rerankers' failure-fallback path no longer mutates the caller's document dicts in place when stamping `rerank_score`; it now falls back on copies ([#6770](https://github.com/mem0ai/mem0/pull/6770))
|
||||
- **Vector Stores:** Remove `logging.basicConfig()` calls from the MongoDB and Vertex AI Vector Search providers, so selecting either provider no longer reconfigures the host application's root logger as a side effect ([#6770](https://github.com/mem0ai/mem0/pull/6770))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-08-01" description="v2.0.15">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Core:** `delete_all()` now paginates through the vector store in batches of 1000 instead of listing once, so accounts with more memories than a single page (most vector stores default to ~100) had the remainder silently left behind ([#6636](https://github.com/mem0ai/mem0/pull/6636))
|
||||
- **Vector Stores:** Cap Supabase `search()`/`list()` `top_k` at the `vecs` query limit of 1000 instead of erroring, and fix a `col_info()` crash by reading collection attributes directly instead of calling the removed `describe()` method ([#6695](https://github.com/mem0ai/mem0/pull/6695))
|
||||
- **Vector Stores:** Set `size` on Elasticsearch KNN search queries, so results respect `top_k` instead of being capped at Elasticsearch's default of 10 hits ([#5910](https://github.com/mem0ai/mem0/pull/5910))
|
||||
|
||||
**Changes:**
|
||||
- **Rerankers:** `LLMReranker`'s default model is now `gpt-5-mini` (was `gpt-4o-mini`) ([#6703](https://github.com/mem0ai/mem0/pull/6703))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-25" description="v2.0.14">
|
||||
|
||||
**New Features:**
|
||||
- **Vector Stores:** Add an Oracle AI Vector Search provider (`oracledb`) with connection pooling, `HNSW`/`IVF` indexes, JSON metadata filtering, and six selectable distance metrics ([#5358](https://github.com/mem0ai/mem0/pull/5358))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Vector Stores:** Translate a `"*"` filter value in OpenSearch into an `exists` query for every key, not just identity keys. It was previously ignored or matched literally against the string `"*"`, so a wildcard filter returned nothing ([#6522](https://github.com/mem0ai/mem0/pull/6522))
|
||||
- **Vector Stores:** Re-raise errors from OpenSearch `search()` instead of returning `[]`, so a transport, auth, or index misconfiguration surfaces instead of looking like zero matches. `keyword_search()` still degrades on failure, since it is a best-effort BM25 signal ([#6519](https://github.com/mem0ai/mem0/pull/6519))
|
||||
- **Vector Stores:** Guard the `text` field in Milvus `update()` behind the `_has_bm25_schema` check, matching `insert()`, so updating a memory in a collection without the BM25 `text`/`sparse` schema no longer fails ([#5705](https://github.com/mem0ai/mem0/pull/5705))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-22" description="v2.0.13">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Vector Stores:** Fix `reset()` silently leaving stale vectors behind on local (on-disk) Qdrant when the old collection directory could not be removed, for example an open file handle on Windows or NFS ([#6412](https://github.com/mem0ai/mem0/pull/6412))
|
||||
- **Core:** Stop `update()` metadata from overwriting or injecting `user_id`, `agent_id`, `run_id`, or `actor_id`. These identity fields are immutable after creation, so passing them in `metadata` can no longer move a memory into a different tenant's scope ([#6278](https://github.com/mem0ai/mem0/pull/6278))
|
||||
- **Vector Stores:** Scope Pinecone `delete_col()`/`reset()` to the configured namespace instead of deleting the whole index, so resetting a namespaced Pinecone store no longer wipes out the other namespaces sharing that index ([#6287](https://github.com/mem0ai/mem0/pull/6287))
|
||||
- **Vector Stores:** Convert Baidu Mochow's raw L2 distance into a similarity score in `search()` (`1 / (1 + distance)`), so closer matches rank higher instead of lower, matching the Milvus provider and the rest of the `VectorStoreBase` contract ([#6435](https://github.com/mem0ai/mem0/pull/6435))
|
||||
- **LLMs:** Read `OPENAI_BASE_URL` (was `OPENAI_API_BASE`) in `OpenAIStructuredLLM`, matching the official OpenAI SDK's environment variable and the rest of the OpenAI-compatible providers ([#6322](https://github.com/mem0ai/mem0/pull/6322))
|
||||
|
||||
**Improvements:**
|
||||
- **LLMs:** Remove a dead, no-op `api_key` attribute check from `LLMBase.__init__` ([#6460](https://github.com/mem0ai/mem0/pull/6460))
|
||||
|
||||
**Changes:**
|
||||
- **Client:** Remove the `retrieval_criteria` parameter from `MemoryClient.update_project()`/`AsyncMemoryClient.update_project()` and `Project.update()`/`AsyncProject.update()`. It was accepted and forwarded but never affected retrieval, so removing it is not a behavior change ([#6313](https://github.com/mem0ai/mem0/pull/6313))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-13" description="v2.0.12">
|
||||
|
||||
**New Features:**
|
||||
@@ -1128,6 +1189,66 @@ See the [OSS v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-
|
||||
|
||||
<Tab title="TypeScript">
|
||||
|
||||
<Update label="2026-08-04" description="v3.1.4">
|
||||
|
||||
**New Features:**
|
||||
- **Client:** Add `referenceDate` and `keywordSearch` to `SearchMemoryOptions`, keeping the TypeScript client's typed search options in sync with the Platform API ([#6696](https://github.com/mem0ai/mem0/pull/6696))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Client:** Take the `/v1/ping/` identity/telemetry call off the request critical path. Every method previously did `if (this.telemetryId === "") await this.ping();`, blocking the first call on each client instance on a network round trip; the ping now resolves in the background through a shared, credentials-keyed promise cache (FIFO-capped at 50 entries), so concurrent clients on the same host/API key share one ping instead of issuing one each, and a failed ping is not cached so the process can retry ([#6788](https://github.com/mem0ai/mem0/pull/6788))
|
||||
- **Client:** `deleteUsers()` now issues its per-entity DELETE requests through the shared `fetch()`-based helper instead of the axios instance, which defaulted to `keepAlive: false`. Deleting every user, agent, app, and run now reuses pooled connections instead of paying a fresh TCP/TLS handshake per entity ([#6788](https://github.com/mem0ai/mem0/pull/6788))
|
||||
- **Memory (OSS):** Stop `add()` metadata from setting or overwriting a memory's identity scope. Metadata now runs through the same `stripIdentityKeys()` helper as `update()`, so `user_id`/`agent_id`/`run_id` (snake_case or camelCase) and `actor_id` passed in metadata can no longer place a memory into a scope the caller didn't request through `userId`/`agentId`/`runId`/`filters` ([#6377](https://github.com/mem0ai/mem0/pull/6377))
|
||||
- **Vector Stores:** Fix Redis `search()` and `list()` building an invalid, empty RediSearch filter expression when `filters` was an empty object or contained only null/undefined values. The shared `buildRedisFilterExpr()` helper now falls back to `"*"` (match all) instead of joining zero conditions into `""` ([#6014](https://github.com/mem0ai/mem0/pull/6014))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-08-01" description="v3.1.3">
|
||||
|
||||
**New Features:**
|
||||
- **Vector Stores:** Add Qdrant server-side BM25 `keywordSearch()` (requires Qdrant >= 1.15.2) plus payload filter indexes, so keyword search runs without a client-side BM25 dependency ([#5851](https://github.com/mem0ai/mem0/pull/5851))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Core:** `deleteAll()` now paginates through the vector store in batches of 1000 instead of listing once, so accounts with more memories than a single page had the remainder silently left behind ([#4872](https://github.com/mem0ai/mem0/pull/4872))
|
||||
- **Vector Stores:** Supabase `list()` now paginates past PostgREST's 1000-row cap instead of stopping at the first page, `search()` warns when results may have been truncated by that same cap, and the initialization probe reads a row instead of writing a test vector, so Row Level Security policies that only grant read access no longer fail table verification ([#6695](https://github.com/mem0ai/mem0/pull/6695))
|
||||
- **Embeddings:** Honor `TOGETHER_API_BASE` in the Together embedder, matching the Together LLM provider, so a custom gateway URL is no longer silently ignored for embeddings ([#6572](https://github.com/mem0ai/mem0/pull/6572))
|
||||
|
||||
**Changes:**
|
||||
- **Rerankers:** `RerankerFactory`'s default LLM reranker model is now `gpt-5-mini` (was `gpt-4o-mini`) ([#6703](https://github.com/mem0ai/mem0/pull/6703))
|
||||
|
||||
**Security:**
|
||||
- **Dependencies:** Patched 32 high and 57 medium severity dependency vulnerabilities across the pnpm workspace via `pnpm.overrides` (`axios`, `brace-expansion`, `js-yaml`, `postcss`, `protobufjs`, `mongoose`, `tar`, `fast-xml-parser`, `thrift`) ([#6639](https://github.com/mem0ai/mem0/pull/6639))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-25" description="v3.1.2">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Vector Stores:** Apply every operator in a Cassandra compound field filter (e.g. `{ age: { gte: 10, lte: 20 } }`) instead of stopping after the first, so the remaining bounds are no longer silently ignored ([#6511](https://github.com/mem0ai/mem0/pull/6511))
|
||||
- **Vector Stores:** Stop the Chroma where-clause translator from dropping filter conditions. Same-field ranges (`gte` + `lte`), multi-field conditions inside `$or`, and negated `contains`/`icontains` under `$not` each collapsed to a single clause or vanished, widening the search instead of narrowing it ([#6521](https://github.com/mem0ai/mem0/pull/6521))
|
||||
- **Vector Stores:** Skip `"*"` wildcard filter values in Milvus instead of matching them literally, so a filter like `{ user_id: "*" }` no longer returns zero memories ([#6508](https://github.com/mem0ai/mem0/pull/6508))
|
||||
- **Vector Stores:** Read `textLemmatized` for BM25 keyword search on Milvus, OpenSearch, and MongoDB, matching the field the memory layer actually writes, so hybrid search on those backends no longer loses the keyword signal ([#6497](https://github.com/mem0ai/mem0/pull/6497))
|
||||
- **LLMs:** Forward `responseFormat` to Gemini's `responseMimeType` in `generateResponse()`, so requesting `json_object` returns JSON instead of free-form text ([#6468](https://github.com/mem0ai/mem0/pull/6468))
|
||||
- **LLMs:** Find the Anthropic text block by type instead of indexing `content[0]`, so a thinking-enabled model whose `thinking` block comes first no longer throws `Unexpected response type from Anthropic API` ([#6506](https://github.com/mem0ai/mem0/pull/6506))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-22" description="v3.1.1">
|
||||
|
||||
**New Features:**
|
||||
- **Embeddings:** Add an AWS Bedrock embedding provider ([#6185](https://github.com/mem0ai/mem0/pull/6185))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Packaging:** Finish the lazy-loading work started in v3.1.0. The remaining LLMs (Anthropic, Google, Groq, LangChain, Mistral, Ollama), embedders (Google, LangChain, Ollama, Vertex AI), vector stores (Azure AI Search, Azure MySQL, Baidu, LangChain, Qdrant, Redis, Supabase, Valkey, Vectorize), and the Supabase history store still imported their SDKs at module load, so importing `mem0ai/oss` required every provider package to be installed ([#6389](https://github.com/mem0ai/mem0/pull/6389))
|
||||
- **Vector Stores:** Convert Baidu Mochow's raw L2 distance into a similarity score in `search()` (`1 / (1 + distance)`), so closer matches rank higher instead of lower. A row the backend returns without a score is now left `undefined` instead of being treated as the closest match ([#6485](https://github.com/mem0ai/mem0/pull/6485))
|
||||
- **Memory (OSS):** Coerce non-string entity IDs (e.g. a numeric `user_id`) to strings instead of crashing on `.trim()` ([#6263](https://github.com/mem0ai/mem0/pull/6263))
|
||||
- **Memory (OSS):** Stop `update()` metadata from overwriting or injecting `user_id`, `agent_id`, `run_id`, or `actor_id` (in either snake_case or camelCase). These identity fields are immutable after creation, so passing them in `metadata` can no longer move a memory into a different tenant's scope ([#6343](https://github.com/mem0ai/mem0/pull/6343))
|
||||
- **Vector Stores:** Scope Pinecone `deleteCol()`/`reset()` to the configured namespace instead of deleting the whole index, so resetting a namespaced Pinecone store no longer wipes out the other namespaces sharing that index ([#6287](https://github.com/mem0ai/mem0/pull/6287))
|
||||
|
||||
**Changes:**
|
||||
- **Client:** Remove the unused `retrievalCriteria` field from `PromptUpdatePayload`. It was accepted and forwarded but never affected retrieval, so removing it is not a behavior change ([#6313](https://github.com/mem0ai/mem0/pull/6313))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-13" description="v3.1.0">
|
||||
|
||||
The largest provider release for the TypeScript OSS SDK so far: 17 new vector stores, 5 new LLM providers, 4 new embedders, and reranking support. Importing `mem0ai/oss` no longer pulls in any provider SDK, so you only install what you actually configure.
|
||||
@@ -1660,6 +1781,26 @@ See the [TypeScript SDK migration guide](https://docs.mem0.ai/migration/ts-v2-to
|
||||
|
||||
<Tab title="CLI">
|
||||
|
||||
<Update label="2026-08-04" description="Python v0.2.11 / Node v0.2.12">
|
||||
|
||||
**New Features:**
|
||||
- **`add`:** New `--custom-instructions`, `--custom-categories`, `--structured-data-schema`, and `--timestamp` flags, matching the Platform `/v3/memories/add/` payload fields (Python and Node [#6696](https://github.com/mem0ai/mem0/pull/6696))
|
||||
- **`search`:** New `--show-expired`, `--reference-date`, and `--latest-only` flags, forwarded to `/v3/memories/search/` as `show_expired`, `reference_date`, and `latest_only` (Python and Node [#6696](https://github.com/mem0ai/mem0/pull/6696))
|
||||
- **`list`:** New `--show-expired` and `--latest-only` flags (Python and Node [#6696](https://github.com/mem0ai/mem0/pull/6696))
|
||||
- **`update`:** New `--expires` and `--timestamp` flags (Python and Node [#6696](https://github.com/mem0ai/mem0/pull/6696))
|
||||
- **`delete`:** New `--delete-linked` flag to also delete memories linked to the target memory (Python and Node [#6696](https://github.com/mem0ai/mem0/pull/6696))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **`help --json`:** Emit JSON automatically under agent mode (`--agent`), not only when `--json` is passed explicitly. Python checks `is_agent_mode()` and now renders through `console.print_json()` instead of a plain `console.print(json.dumps(...))` call; Node also checks the root `--agent` flag in addition to the subcommand's `--json` flag (Python and Node [#6773](https://github.com/mem0ai/mem0/pull/6773))
|
||||
|
||||
**Changes:**
|
||||
- **`add`:** `--categories` is no longer forwarded on `add`. The `/v3/memories/add/` payload has no `categories` field (only `custom_categories`), so the CLI was sending a key the endpoint does not accept. Passing the flag now exits 1 with a message pointing to `--custom-categories` (Python and Node [#6696](https://github.com/mem0ai/mem0/pull/6696))
|
||||
|
||||
**Security:**
|
||||
- **Dependencies (Node):** Tighten the `postcss` pnpm override from `<8.5.10 → >=8.5.10` to `<8.5.18 → >=8.5.18 <9.0.0`, closing a newer CVE range the previous floor didn't cover, as part of a wider dependency patch sweep across the pnpm workspaces ([#6639](https://github.com/mem0ai/mem0/pull/6639))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-13" description="Python v0.2.10 / Node v0.2.11">
|
||||
|
||||
**Bug Fixes:**
|
||||
@@ -1825,6 +1966,28 @@ A full-featured command-line interface for Mem0, available in both Python and No
|
||||
<Tabs>
|
||||
<Tab title="Mem0 Plugin">
|
||||
|
||||
<Update label="2026-08-04" description="mem0-plugin v0.2.14">
|
||||
|
||||
**Fixes:**
|
||||
- **Settings loading no longer crashes on a malformed `settings.json`:** If `~/.mem0/settings.json` parsed as valid JSON but wasn't an object (a list or a bare string, for example), `load_settings()` called `.items()` on it and raised. `resolve_config()` guards only against `ImportError`, so the failure escaped into every hook that resolves identity, not just setup. Settings loading now keeps the defaults instead. Setup also stopped claiming it "Created" the settings file when one already existed; it only prints that when it actually wrote one. Shared across Claude Code, Cursor, Codex, and Antigravity.
|
||||
|
||||
**New Features:**
|
||||
- **Unknown settings key warning:** Setup now flags keys in `settings.json` that the plugin doesn't recognize, so a typo'd setting no longer fails silently.
|
||||
|
||||
**Removed:**
|
||||
- Dropped the `mem0_doc_search` "openmemory" section now that OpenMemory has been removed from the docs site, so the skill no longer links to pages that don't exist.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-14" description="mem0-plugin v0.2.13">
|
||||
|
||||
**Fixes:**
|
||||
- **Assistant messages no longer stored as your own:** The session-summary hook (fires at the end of every assistant turn) and the post-compaction hook were sending the assistant's own message to Mem0 tagged `role: "user"`. Because Mem0 extracts *facts about the user* from each message and uses `role` to decide who spoke, the assistant's first-person prose was being saved as the human's stated preferences: "I recommend we drop Redis" became `User prefers dropping Redis entirely`. Both hooks now send `role: "assistant"`, so the same session is stored as `Assistant recommended...`. Affects Claude Code, Cursor, Codex, and Antigravity, which share these hooks.
|
||||
|
||||
Existing memories written by the previous versions are not rewritten. If your memories contain preferences you never expressed, delete them; the plugin will not recreate them.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-30" description="mem0-plugin v0.2.12">
|
||||
|
||||
**New Features:**
|
||||
@@ -2075,6 +2238,13 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
|
||||
|
||||
<Tab title="OpenCode">
|
||||
|
||||
<Update label="2026-07-22" description="OpenCode plugin v0.2.2">
|
||||
|
||||
**Fixes:**
|
||||
- **Shell-profile API key recovery:** When `MEM0_API_KEY` isn't set in the process environment, the plugin now falls back to reading it from `.zshrc`, `.bashrc`, `.zprofile`, `.bash_profile`, or `.profile`, fixing startup failures on clients (e.g. Desktop) that launch without shell-exported environment variables.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-30" description="OpenCode plugin v0.2.1">
|
||||
|
||||
**Improvements:**
|
||||
@@ -2148,6 +2318,28 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
|
||||
|
||||
<Tab title="Antigravity">
|
||||
|
||||
<Update label="2026-08-04" description="Antigravity plugin v0.1.6">
|
||||
|
||||
**Fixes:**
|
||||
- **Settings loading no longer crashes on a malformed `settings.json`:** If `~/.mem0/settings.json` parsed as valid JSON but wasn't an object, every hook that resolves identity previously raised, not just setup. It now keeps the defaults instead, and setup only reports it "Created" the settings file when it actually wrote one.
|
||||
|
||||
**Improvements:**
|
||||
- **Unknown settings key warning:** Setup now flags keys in `settings.json` it doesn't recognize instead of ignoring them silently.
|
||||
|
||||
**Removed:**
|
||||
- Dropped the doc-search skill's "openmemory" section now that OpenMemory has been removed from the docs site.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-14" description="Antigravity plugin v0.1.5">
|
||||
|
||||
**Fixes:**
|
||||
- **Assistant messages no longer stored as your own:** The session-summary hook (fires at the end of every assistant turn) and the post-compaction hook were sending the assistant's own message to Mem0 tagged `role: "user"`. Because Mem0 extracts *facts about the user* from each message and uses `role` to decide who spoke, the assistant's first-person prose was being saved as the human's stated preferences: "I recommend we drop Redis" became `User prefers dropping Redis entirely`. Both hooks now send `role: "assistant"`, so the same session is stored as `Assistant recommended...`.
|
||||
|
||||
Existing memories written by the previous versions are not rewritten. If your memories contain preferences you never expressed, delete them; the plugin will not recreate them.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-30" description="Antigravity plugin v0.1.4">
|
||||
|
||||
**New Features:**
|
||||
@@ -2195,6 +2387,16 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
|
||||
|
||||
<Tab title="OpenClaw">
|
||||
|
||||
<Update label="2026-08-01" description="openclaw-mem0 v1.0.15">
|
||||
|
||||
**Improvements:**
|
||||
- **Onboarding suggestions:** The example commands shown by `openclaw mem0 config show` now suggest `gpt-5-mini` instead of `gpt-4o` ([#6704](https://github.com/mem0ai/mem0/pull/6704))
|
||||
|
||||
**Security:**
|
||||
- **Dependencies:** Patched high and medium severity dependency vulnerabilities via `pnpm.overrides` (`protobufjs`, `axios`, `postcss`, `mongoose`) ([#6639](https://github.com/mem0ai/mem0/pull/6639))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-30" description="openclaw-mem0 v1.0.14">
|
||||
|
||||
**Improvements:**
|
||||
@@ -2448,6 +2650,13 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
|
||||
|
||||
<Tab title="Pi Agent">
|
||||
|
||||
<Update label="2026-08-01" description="Pi Agent plugin v0.1.4">
|
||||
|
||||
**Security:**
|
||||
- **Dependencies:** Patched high and medium severity dependency vulnerabilities via `pnpm.overrides` (`axios`, `brace-expansion`, `postcss`, `mongoose`, `protobufjs`) ([#6639](https://github.com/mem0ai/mem0/pull/6639))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-30" description="Pi Agent plugin v0.1.3">
|
||||
|
||||
**New Features:**
|
||||
@@ -2500,6 +2709,13 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
|
||||
|
||||
<Tab title="Vercel AI SDK">
|
||||
|
||||
<Update label="2026-08-01" description="Vercel AI SDK v3.0.1">
|
||||
|
||||
**Security:**
|
||||
- **Dependencies:** Patched high and medium severity dependency vulnerabilities via `pnpm.overrides` (`brace-expansion`, `js-yaml`) ([#6639](https://github.com/mem0ai/mem0/pull/6639))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-10" description="Vercel AI SDK v3.0.0">
|
||||
|
||||
**Major Release**: Migrated to Vercel AI SDK v6 (`LanguageModelV3` / `ProviderV3`) and Mem0 v3 API.
|
||||
@@ -2581,6 +2797,72 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
|
||||
- Added support for graph memories.
|
||||
</Update>
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="n8n">
|
||||
|
||||
<Update label="2026-08-04" description="n8n-nodes-mem0 v0.1.2">
|
||||
|
||||
**Changes:**
|
||||
- **Package contact:** `author.email` in the published package is now `integrations@mem0.ai` (was `founders@mem0.ai`), so npm and n8n Creator Portal correspondence reaches the integrations team directly. No functional changes ([#6791](https://github.com/mem0ai/mem0/pull/6791))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-30" description="n8n-nodes-mem0 v0.1.1">
|
||||
|
||||
**Changes:**
|
||||
- **Published with npm provenance:** Republished through the `n8n-nodes-mem0-cd.yml` GitHub Actions workflow so the package carries a signed provenance attestation. `0.1.0` was published manually and has none, which blocks submission for n8n Creator Portal verification. No functional changes ([#6685](https://github.com/mem0ai/mem0/pull/6685))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-29" description="n8n-nodes-mem0 v0.1.0">
|
||||
|
||||
**Initial release** of [`@mem0/n8n-nodes-mem0`](https://www.npmjs.com/package/@mem0/n8n-nodes-mem0), a community node that adds long-term memory to n8n workflows and AI Agents ([#6517](https://github.com/mem0ai/mem0/pull/6517))
|
||||
|
||||
**New Features:**
|
||||
- **Memory operations:** A single **Mem0** node covers Add, Search, Get, Get Many, Update, and Delete on the Memory resource.
|
||||
- **AI Agent tool:** The node sets `usableAsTool`, so it can be attached to an n8n AI Agent node and invoked by the agent itself rather than wired into a fixed workflow path.
|
||||
- **Scoping:** Add, Search, and Get Many accept User ID, Agent ID, App ID, and Run ID, so memories stay partitioned per user, agent, or session.
|
||||
- **Add options:** Metadata JSON, custom categories, custom instructions, includes/excludes, an `infer` toggle, and a **Wait for Completion** switch that polls until the write lands instead of returning immediately.
|
||||
- **Pagination:** Get Many supports Return All, or explicit Page and Page Size.
|
||||
- **Credential:** A **Mem0 API** credential holds the API key plus a configurable base URL, defaulting to `https://api.mem0.ai` for self-hosted deployments.
|
||||
|
||||
<Note>
|
||||
Community nodes install from npm, which is a self-hosted n8n feature. See [n8n](/integrations/n8n) for setup.
|
||||
</Note>
|
||||
|
||||
</Update>
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Zapier">
|
||||
|
||||
<Update label="2026-08-04" description="Zapier app v0.1.1">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Add Memory:** **User ID** is now a required field. Mem0 rejects a write that carries no entity ID, so a Zap left blank failed at the API instead of in the editor. If you scoped memories only by Agent ID or Run ID, set a User ID as well ([#6790](https://github.com/mem0ai/mem0/pull/6790))
|
||||
- **Deployment:** Add a root `index.js` that re-exports the compiled app from `dist/`, and point `main` at it. Zapier's Lambda wrapper loads `<root>/index.js` and ignores the `package.json` `main` field, so the deployed app could not resolve its entry point ([#6789](https://github.com/mem0ai/mem0/pull/6789))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-07-29" description="Zapier app v0.1.0">
|
||||
|
||||
**Initial release** of the Mem0 Zapier app, built on the Zapier Platform CLI ([#6518](https://github.com/mem0ai/mem0/pull/6518))
|
||||
|
||||
**New Features:**
|
||||
- **Actions:** Add Memory and Delete Memory.
|
||||
- **Searches:** Search Memories and Get Memories, usable as lookup steps in any Zap.
|
||||
- **Authentication:** An API key connection validated against Mem0 the moment it is saved, sent as `Authorization: Token <key>`, with a configurable base URL for self-hosted deployments.
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Add Memory:** Raise the poll budget past the real API latency tail, so a slower write is no longer reported as a failure ([#6680](https://github.com/mem0ai/mem0/pull/6680))
|
||||
|
||||
<Note>
|
||||
The app deploys to Zapier's platform rather than npm and is not yet listed in the public App Directory. See [Zapier](/integrations/zapier) for invite access.
|
||||
</Note>
|
||||
|
||||
</Update>
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
@@ -3,11 +3,27 @@ title: AWS Bedrock
|
||||
description: "Configure AWS Bedrock as an embedding provider in Mem0 with IAM credentials and boto3 authentication."
|
||||
---
|
||||
|
||||
To use AWS Bedrock embedding models, you need to have the appropriate AWS credentials and permissions. The embeddings implementation relies on the `boto3` library.
|
||||
To use AWS Bedrock embedding models, you need the appropriate AWS credentials and permissions. Python uses `boto3`, and TypeScript uses `@aws-sdk/client-bedrock-runtime`.
|
||||
|
||||
Both SDKs support the Amazon Titan and Cohere embedding model families.
|
||||
|
||||
### Setup
|
||||
- Ensure you have model access from the [AWS Bedrock Console](https://us-east-1.console.aws.amazon.com/bedrock/home?region=us-east-1#/modelaccess)
|
||||
- Authenticate the boto3 client using a method described in the [AWS documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html)
|
||||
|
||||
- Model access is automatic: Bedrock enables serverless foundation models on first invocation in AWS commercial regions, and the [Model access page has been retired](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html). Cohere models are served from AWS Marketplace, so an account's first invocation must come from a principal with the `aws-marketplace:Subscribe` permission; after that, any user in the account can invoke them. Browse the models available to you in the [Bedrock model catalog](https://console.aws.amazon.com/bedrock/).
|
||||
- Install the AWS client for your language:
|
||||
|
||||
<CodeGroup>
|
||||
```bash Python
|
||||
pip install boto3
|
||||
```
|
||||
|
||||
```bash TypeScript
|
||||
npm install @aws-sdk/client-bedrock-runtime
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
In TypeScript this package is an optional peer dependency, so it is only required when you actually use the Bedrock embedder.
|
||||
|
||||
- Set up environment variables for authentication:
|
||||
```bash
|
||||
export AWS_REGION=us-east-1
|
||||
@@ -15,6 +31,8 @@ To use AWS Bedrock embedding models, you need to have the appropriate AWS creden
|
||||
export AWS_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
Both SDKs fall back to the standard AWS credential chain (environment variables, shared config, SSO, or an instance role) when you do not pass credentials in the config, so you rarely need to hardcode keys. See the [boto3 credentials guide](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html) for the Python resolution order.
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeGroup>
|
||||
@@ -48,8 +66,46 @@ messages = [
|
||||
]
|
||||
m.add(messages, user_id="alice")
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
// Credentials are read from the AWS default chain (AWS_REGION,
|
||||
// AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, SSO, or an instance role).
|
||||
const memory = new Memory({
|
||||
embedder: {
|
||||
provider: "aws_bedrock",
|
||||
config: {
|
||||
model: "amazon.titan-embed-text-v2:0",
|
||||
awsRegion: "us-west-2",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const messages = [
|
||||
{ role: "user", content: "I'm planning to watch a movie tonight. Any recommendations?" },
|
||||
{ role: "assistant", content: "How about thriller movies? They can be quite engaging." },
|
||||
{ role: "user", content: "I'm not a big fan of thriller movies but I love sci-fi movies." },
|
||||
{ role: "assistant", content: "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future." },
|
||||
];
|
||||
await memory.add(messages, { userId: "alice" });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Choosing a model
|
||||
|
||||
| Model | Notes |
|
||||
| --- | --- |
|
||||
| `amazon.titan-embed-text-v1` | Default. Fixed 1536-dimension output. |
|
||||
| `amazon.titan-embed-text-v2:0` | Supports a configurable output size of 256, 512, or 1024. |
|
||||
| `cohere.embed-english-v3` | English text. Embeds up to 96 texts per request. |
|
||||
| `cohere.embed-multilingual-v3` | Multilingual text. Embeds up to 96 texts per request. |
|
||||
| `cohere.embed-v4:0` | Text. Embeds up to 96 texts per request. Supports a configurable output size of 256, 512, 1024, or 1536. TypeScript only. |
|
||||
|
||||
Custom output sizes are model specific. In Python, only Titan Text Embeddings V2 accepts one. In TypeScript, Titan Text Embeddings V2 and Cohere Embed v4 both do, and `embeddingDims` is ignored on Titan V1 and on Cohere v3, which have no such parameter. When you do set it, make sure your vector store dimension matches, otherwise inserts will fail.
|
||||
|
||||
Bedrock caps a Cohere embedding call at 96 texts. The TypeScript SDK splits larger batches into multiple requests for you, so a 200 text batch becomes 3 calls.
|
||||
|
||||
### Config
|
||||
|
||||
Here are the parameters available for configuring AWS Bedrock embedder:
|
||||
@@ -64,4 +120,16 @@ Here are the parameters available for configuring AWS Bedrock embedder:
|
||||
| `aws_secret_access_key` | AWS secret access key for authentication | `None` |
|
||||
| `aws_session_token` | AWS session token for temporary credentials | `None` |
|
||||
</Tab>
|
||||
<Tab title="TypeScript">
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `model` | The name of the embedding model to use | `amazon.titan-embed-text-v1` |
|
||||
| `awsRegion` | AWS region for the Bedrock client. Falls back to the `AWS_REGION` environment variable | `us-west-2` |
|
||||
| `embeddingDims` | Output vector size. Titan Text Embeddings V2 (256, 512, or 1024) and Cohere Embed v4 (256, 512, 1024, or 1536) only | `undefined` |
|
||||
| `awsAccessKeyId` | AWS access key ID for authentication | `undefined` |
|
||||
| `awsSecretAccessKey` | AWS secret access key for authentication | `undefined` |
|
||||
| `awsSessionToken` | AWS session token for temporary credentials | `undefined` |
|
||||
|
||||
Omit the three credential fields to use the AWS default credential chain. If you do pass them, `awsAccessKeyId` and `awsSecretAccessKey` are both required.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -10,7 +10,7 @@ Mem0 offers support for various embedding models, allowing users to choose the o
|
||||
See the list of supported embedders below.
|
||||
|
||||
<Note>
|
||||
All embedders listed below are supported in the Python implementation. The TypeScript implementation supports: **OpenAI**, **Azure OpenAI**, **FastEmbed**, **Google AI**, **Langchain**, **LM Studio**, **Ollama**, and **Together**.
|
||||
All embedders listed below are supported in the Python implementation. The TypeScript implementation supports: **OpenAI**, **Azure OpenAI**, **AWS Bedrock**, **FastEmbed**, **Google AI**, **Hugging Face**, **Langchain**, **LM Studio**, **Ollama**, **Together**, and **Vertex AI**.
|
||||
</Note>
|
||||
|
||||
<CardGroup cols={4}>
|
||||
|
||||
@@ -52,7 +52,7 @@ All rerankers share these common configuration parameters:
|
||||
|
||||
| Parameter | Description | Type | Default |
|
||||
| ---------------- | ------------------------------------------ | ------- | ---------------------- |
|
||||
| `model` | LLM model to use for scoring | `str` | `"gpt-4o-mini"` |
|
||||
| `model` | LLM model to use for scoring | `str` | `"gpt-5-mini"` |
|
||||
| `provider` | LLM provider (`openai`, `anthropic`, etc.) | `str` | `"openai"` |
|
||||
| `api_key` | API key for LLM provider | `str` | `None` |
|
||||
| `temperature` | Temperature for LLM generation | `float` | `0.0` |
|
||||
@@ -114,7 +114,7 @@ The self-hosted [TypeScript SDK](/open-source/features/reranker-search#typescrip
|
||||
| `zero_entropy` | `pnpm add zeroentropy` | `zerank-1` | `apiKey`, `model`, `topK` |
|
||||
| `sentence_transformer` | `pnpm add @huggingface/transformers` | `Xenova/ms-marco-MiniLM-L-6-v2` | `model`, `device`, `maxLength`, `normalize`, `topK` |
|
||||
| `huggingface` | `pnpm add @huggingface/transformers` | `Xenova/bge-reranker-base` | `model`, `device`, `maxLength`, `normalize`, `topK` |
|
||||
| `llm_reranker` | None (uses your LLM provider's own SDK) | `openai` / `gpt-4o-mini` | `provider`, `model`, `apiKey`, `llm` (nested override), `topK` |
|
||||
| `llm_reranker` | None (uses your LLM provider's own SDK) | `openai` / `gpt-5-mini` | `provider`, `model`, `apiKey`, `llm` (nested override), `topK` |
|
||||
|
||||
```typescript
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
@@ -48,7 +48,7 @@ config = {
|
||||
"provider": "llm_reranker",
|
||||
"config": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"model": "gpt-5-mini",
|
||||
"api_key": "your-openai-key",
|
||||
"scoring_prompt": custom_prompt,
|
||||
"top_k": 5
|
||||
|
||||
@@ -101,7 +101,7 @@ os.environ["COHERE_API_KEY"] = "your-api-key"
|
||||
# Initialize memory with Cohere reranker
|
||||
config = {
|
||||
"vector_store": {"provider": "chroma"},
|
||||
"llm": {"provider": "openai", "config": {"model": "gpt-4o-mini"}},
|
||||
"llm": {"provider": "openai", "config": {"model": "gpt-5-mini"}},
|
||||
"rerank": {
|
||||
"provider": "cohere",
|
||||
"config": {
|
||||
|
||||
@@ -19,7 +19,7 @@ config = {
|
||||
"provider": "llm_reranker",
|
||||
"config": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"model": "gpt-5-mini",
|
||||
"api_key": "your-openai-api-key"
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ m = Memory.from_config(config)
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `provider` | str | `"openai"` | LLM provider (openai, anthropic, etc.) |
|
||||
| `model` | str | `"gpt-4o-mini"` | LLM model to use for reranking |
|
||||
| `model` | str | `"gpt-5-mini"` | LLM model to use for reranking |
|
||||
| `api_key` | str | None | API key for the LLM provider |
|
||||
| `top_k` | int | None | Number of top documents to return after reranking |
|
||||
| `temperature` | float | 0.0 | LLM temperature for consistency |
|
||||
@@ -69,7 +69,7 @@ config = {
|
||||
|
||||
## TypeScript (self-hosted)
|
||||
|
||||
The [TypeScript OSS SDK](/open-source/features/reranker-search#typescript-sdk) (`mem0ai/oss`) ships the LLM reranker under the provider name `llm_reranker`. It does **not** reuse the Memory's main `llm` instance; it builds its own LLM from the reranker's own config, defaulting to `openai` / `gpt-4o-mini`. Set `provider`/`model`/`apiKey` directly on `config`, or nest a fully separate `config.llm: { provider, config }` (its `provider`/`config` take priority over the top-level fields, which only backfill values missing from the nested config).
|
||||
The [TypeScript OSS SDK](/open-source/features/reranker-search#typescript-sdk) (`mem0ai/oss`) ships the LLM reranker under the provider name `llm_reranker`. It does **not** reuse the Memory's main `llm` instance; it builds its own LLM from the reranker's own config, defaulting to `openai` / `gpt-5-mini`. Set `provider`/`model`/`apiKey` directly on `config`, or nest a fully separate `config.llm: { provider, config }` (its `provider`/`config` take priority over the top-level fields, which only backfill values missing from the nested config).
|
||||
|
||||
```typescript
|
||||
import { Memory } from "mem0ai/oss";
|
||||
@@ -114,7 +114,7 @@ config = {
|
||||
"provider": "llm_reranker",
|
||||
"config": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"model": "gpt-5-mini",
|
||||
"api_key": "your-openai-api-key",
|
||||
"temperature": 0.0
|
||||
}
|
||||
@@ -170,15 +170,15 @@ config = {
|
||||
"provider": "llm_reranker",
|
||||
"config": {
|
||||
"provider": "azure_openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"model": "gpt-5-mini",
|
||||
"api_key": "your-azure-api-key",
|
||||
"llm": {
|
||||
"provider": "azure_openai",
|
||||
"config": {
|
||||
"model": "gpt-4o-mini",
|
||||
"model": "gpt-5-mini",
|
||||
"api_key": "your-azure-api-key",
|
||||
"azure_endpoint": "https://your-resource.openai.azure.com/",
|
||||
"azure_deployment": "gpt-4o-mini-deployment"
|
||||
"azure_deployment": "gpt-5-mini-deployment"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,7 +232,7 @@ config = {
|
||||
"provider": "llm_reranker",
|
||||
"config": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"model": "gpt-5-mini",
|
||||
"api_key": "your-api-key",
|
||||
"scoring_prompt": custom_prompt
|
||||
}
|
||||
@@ -351,7 +351,7 @@ fast_config = {
|
||||
"provider": "llm_reranker",
|
||||
"config": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"model": "gpt-5-mini",
|
||||
"api_key": "your-api-key",
|
||||
"top_k": 5,
|
||||
"temperature": 0.0
|
||||
@@ -444,7 +444,7 @@ fallback_config = {
|
||||
"provider": "llm_reranker",
|
||||
"config": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini",
|
||||
"model": "gpt-5-mini",
|
||||
"api_key": "your-api-key"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ config = {
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-4o-mini"
|
||||
"model": "gpt-5-mini"
|
||||
}
|
||||
},
|
||||
"rerank": {
|
||||
@@ -113,7 +113,7 @@ from mem0 import Memory
|
||||
# Initialize memory with local reranker
|
||||
config = {
|
||||
"vector_store": {"provider": "chroma"},
|
||||
"llm": {"provider": "openai", "config": {"model": "gpt-4o-mini"}},
|
||||
"llm": {"provider": "openai", "config": {"model": "gpt-5-mini"}},
|
||||
"rerank": {
|
||||
"provider": "sentence_transformer",
|
||||
"config": {
|
||||
|
||||
@@ -34,7 +34,7 @@ config = {
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-4o-mini"
|
||||
"model": "gpt-5-mini"
|
||||
}
|
||||
},
|
||||
"rerank": {
|
||||
@@ -98,7 +98,7 @@ os.environ["ZERO_ENTROPY_API_KEY"] = "your-api-key"
|
||||
# Initialize memory with Zero Entropy reranker
|
||||
config = {
|
||||
"vector_store": {"provider": "chroma"},
|
||||
"llm": {"provider": "openai", "config": {"model": "gpt-4o-mini"}},
|
||||
"llm": {"provider": "openai", "config": {"model": "gpt-5-mini"}},
|
||||
"rerank": {"provider": "zero_entropy", "config": {"model": "zerank-1"}}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ description: "Reference for vector database configuration options in Mem0, inclu
|
||||
|
||||
The `config` is defined as an object with two main keys:
|
||||
- `vector_store`: Specifies the vector database provider and its configuration
|
||||
- `provider`: The name of the vector database (e.g., "chroma", "pgvector", "qdrant", "milvus", "upstash_vector", "azure_ai_search", "vertex_ai_vector_search", "valkey")
|
||||
- `provider`: The name of the vector database (e.g., "chroma", "pgvector", "qdrant", "milvus", "upstash_vector", "azure_ai_search", "vertex_ai_vector_search", "valkey", "oracledb")
|
||||
- `config`: A nested dictionary containing provider-specific settings
|
||||
|
||||
|
||||
@@ -95,6 +95,11 @@ Here's a comprehensive list of all parameters that can be used across different
|
||||
| `connection_string` | PostgreSQL connection string (for Supabase/PGVector) |
|
||||
| `index_method` | Vector index method (for Supabase) |
|
||||
| `index_measure` | Distance measure for similarity search (for Supabase) |
|
||||
| `connection_params` | Connection settings for Oracle AI Vector Search |
|
||||
| `use_connection_pool` | Create an Oracle connection pool from `connection_params` |
|
||||
| `distance_metric` | Distance metric for Oracle vector indexing and search |
|
||||
| `index_type` | Oracle vector index type: `HNSW` or `IVF` |
|
||||
| `index_parameters` | Oracle vector-index parameters for the selected index type |
|
||||
</Tab>
|
||||
<Tab title="TypeScript">
|
||||
| Parameter | Description |
|
||||
|
||||
@@ -96,8 +96,8 @@ await memory.add(messages, { userId: "alice", metadata: { category: "movies" } }
|
||||
</Tabs>
|
||||
|
||||
Both SDKs store vectors on graph nodes labeled `MEM0_VECTOR_<collection_name>`. Point them at the same
|
||||
graph with the same `collection_name` — the defaults differ, `mem0` in Python and `memories` in
|
||||
TypeScript — and `get()`, `list()`, and `delete()` interoperate across SDKs.
|
||||
graph with the same `collection_name` (the defaults differ, `mem0` in Python and `memories` in
|
||||
TypeScript) and `get()`, `list()`, and `delete()` interoperate across SDKs.
|
||||
|
||||
<Note>
|
||||
`search()` is not currently cross-SDK compatible. The TypeScript provider filters on Neptune's reserved
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
title: "Oracle AI Vector Search"
|
||||
description: "Use Oracle Database AI Vector Search as a vector store in Mem0 for semantic and relational queries."
|
||||
---
|
||||
|
||||
[Oracle AI Vector Search](https://www.oracle.com/database/ai-vector-search/) stores embeddings in an Oracle table using the native `VECTOR` data type, so you can combine semantic search over unstructured data with relational queries over business data in a single database.
|
||||
|
||||
### Requirements
|
||||
|
||||
- Oracle Database 23.4 or later, with a user that can create tables and vector indexes
|
||||
- The `python-oracledb` driver. In thick mode, Oracle Client 23.4 or later is also required.
|
||||
|
||||
```bash
|
||||
pip install oracledb
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "sk-xx"
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "oracledb",
|
||||
"config": {
|
||||
"collection_name": "mem0",
|
||||
"embedding_model_dims": 1536,
|
||||
"connection_params": {
|
||||
"user": "mem0_user",
|
||||
"password": "your-password",
|
||||
"dsn": "localhost:1521/FREEPDB1",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about thriller movies? They can be quite engaging."},
|
||||
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
|
||||
]
|
||||
m.add(messages, user_id="alice", metadata={"category": "movies"})
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
To reuse a connection or pool you already manage, pass it as `client` instead of `connection_params`:
|
||||
|
||||
```python
|
||||
import oracledb
|
||||
|
||||
pool = oracledb.create_pool(user="mem0_user", password="your-password", dsn="localhost:1521/FREEPDB1")
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "oracledb",
|
||||
"config": {"client": pool},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Config
|
||||
|
||||
Here are the parameters available for configuring Oracle AI Vector Search:
|
||||
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `connection_params` | Connection settings passed to `python-oracledb`, such as `user`, `password` and `dsn`. See the [connection handling guide](https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html). | `None` |
|
||||
| `use_connection_pool` | Create a connection pool from `connection_params` instead of a single connection | `True` |
|
||||
| `client` | An existing `oracledb.Connection` or `oracledb.ConnectionPool` to use instead of building one from `connection_params` | `None` |
|
||||
| `collection_name` | Name of the Oracle table that stores vectors and payloads | `mem0` |
|
||||
| `embedding_model_dims` | Dimension of your embedding vectors, must be greater than 0 | `1536` |
|
||||
| `distance_metric` | Distance function used for indexing and search: `COSINE`, `EUCLIDEAN`, `EUCLIDEAN_SQUARED`, `DOT`, `HAMMING` or `MANHATTAN` | `COSINE` |
|
||||
| `do_create_index` | Whether to create a vector index on the collection | `True` |
|
||||
| `index_type` | Vector index type: `HNSW` or `IVF` | `HNSW` |
|
||||
| `index_name` | Name of the vector index | `<collection_name>_VEC_IDX` |
|
||||
| `index_parameters` | Index tuning parameters. For `HNSW`: `neighbors`, `efconstruction`. For `IVF`: `neighbor partitions`, `samples_per_partition`, `min_vectors_per_partition`. | `None` |
|
||||
| `index_accuracy` | Target index accuracy from 1 to 100, applied as `WITH TARGET ACCURACY <n>` | `None` |
|
||||
|
||||
<Note>
|
||||
When you pass a pre-built `client`, Mem0 uses it as-is and ignores `connection_params` and `use_connection_pool`. Mem0 does not close a client it did not create.
|
||||
</Note>
|
||||
|
||||
### Vector indexes
|
||||
|
||||
Set the index type with `index_type` and tune it with `index_parameters`:
|
||||
|
||||
```python
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "oracledb",
|
||||
"config": {
|
||||
"connection_params": {"user": "mem0_user", "password": "your-password", "dsn": "localhost:1521/FREEPDB1"},
|
||||
"index_type": "HNSW",
|
||||
"index_parameters": {"neighbors": 32, "efconstruction": 200},
|
||||
"index_accuracy": 95,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For the full list of supported options, see the Oracle [`CREATE VECTOR INDEX`](https://docs.oracle.com/en/database/oracle/oracle-database/26/sqlrf/create-vector-index.html) reference.
|
||||
|
||||
### Search scores
|
||||
|
||||
Oracle returns a distance from `VECTOR_DISTANCE`, which Mem0 converts to a `score` where higher means more similar. `COSINE` and the other non-negative metrics produce scores in the range `[0, 1]`. `DOT` returns the inner product, which can fall outside that range.
|
||||
|
||||
### Metadata filters
|
||||
|
||||
Filters run against the JSON `payload` column and support:
|
||||
|
||||
| Filter type | Examples |
|
||||
| --- | --- |
|
||||
| Scalar equality | `{"user_id": "alice"}` |
|
||||
| Field existence | `{"agent_id": "*"}` |
|
||||
| Comparison | `{"score": {"gte": 0.5}}`, also `eq`, `ne`, `gt`, `lt`, `lte` |
|
||||
| Membership | `{"category": {"in": ["movies", "books"]}}`, also `nin` |
|
||||
| String matching | `{"title": {"contains": "sci-fi"}}`, also `icontains` for case-insensitive |
|
||||
| Logical groups | `{"AND": [...]}`, `{"OR": [...]}`, `{"NOT": [...]}` |
|
||||
|
||||
Multiple fields at the top level are combined with `AND`:
|
||||
|
||||
```python
|
||||
m.search(
|
||||
"movie recommendations",
|
||||
user_id="alice",
|
||||
filters={"category": {"in": ["movies", "books"]}, "rating": {"gte": 4}},
|
||||
)
|
||||
```
|
||||
@@ -60,6 +60,12 @@ await memory.add(messages, { userId: "alice", metadata: { category: "movies" } }
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Hybrid keyword search
|
||||
|
||||
Mem0 blends semantic similarity with BM25 keyword scoring. On the TypeScript SDK, Qdrant computes the BM25 vectors server-side, which requires Qdrant 1.15.2 or newer with inference enabled. Qdrant Cloud enables inference by default only for clusters created after 2025-07-07; older clusters must activate it from the Cluster Detail page. The Python SDK encodes BM25 locally instead and needs the `fastembed` package, so scores are not numerically comparable between the two SDKs.
|
||||
|
||||
When BM25 is unavailable, or when the collection was created before hybrid search was added, Mem0 logs a warning and falls back to semantic-only search. Writes are unaffected. To enable keyword scoring on an older collection, use a fresh collection name.
|
||||
|
||||
### Config
|
||||
|
||||
Let's see the available parameters for the `qdrant` config:
|
||||
|
||||
@@ -115,6 +115,27 @@ $$;
|
||||
|
||||
Go to [Supabase](https://supabase.com/dashboard/projects) and run the above SQL migrations in the SQL Editor.
|
||||
|
||||
### Row Level Security
|
||||
|
||||
Tables created through the Supabase dashboard have Row Level Security (RLS) enabled by default with no policies attached. With RLS on and no policies, the TypeScript SDK's queries return zero rows with an HTTP 200 (no error is raised), which looks like an empty memory store rather than a permissions problem. If you use the SQL migrations above (via the SQL Editor), RLS is left in its default off state and this does not apply.
|
||||
|
||||
If your table has RLS enabled, add policies for the key your app uses (the example below grants full access to the `service_role` key; scope it down for anon/authenticated keys as needed):
|
||||
|
||||
```sql
|
||||
alter table memories enable row level security;
|
||||
|
||||
create policy "Allow service role full access to memories"
|
||||
on memories
|
||||
for all
|
||||
to service_role
|
||||
using (true)
|
||||
with check (true);
|
||||
```
|
||||
|
||||
### PostgREST Row Limits
|
||||
|
||||
Supabase's PostgREST layer caps the number of rows returned by a single request at `db-max-rows` (1000 by default), for both `.select()` queries and RPC function calls like `match_vectors`. Requesting a `topK` above this limit for `search()` or `list()` will not raise an error, results are capped at `db-max-rows` instead. The TypeScript `list()` method paginates internally to work around this, but `search()` cannot since `match_vectors` has no offset parameter; it logs a warning when it detects a truncated result. Raise `db-max-rows` in your Supabase project settings if you need more than 1000 results per search.
|
||||
|
||||
### Config
|
||||
|
||||
Here are the parameters available for configuring Supabase:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Overview
|
||||
description: "Overview of all supported vector databases in Mem0, including Qdrant, Chroma, PGVector, Pinecone, and more."
|
||||
description: "Overview of all supported vector databases in Mem0, including Qdrant, Chroma, PGVector, Pinecone, Oracle, and more."
|
||||
---
|
||||
|
||||
Mem0 includes built-in support for various popular databases. Memory can utilize the database provided by the user, ensuring efficient use for specific needs.
|
||||
@@ -21,6 +21,7 @@ See the list of supported vector databases below.
|
||||
<Card title="Milvus" icon="/images/provider-icons/milvus.svg" href="/components/vectordbs/dbs/milvus"></Card>
|
||||
<Card title="Pinecone" icon="/images/provider-icons/pinecone.svg" href="/components/vectordbs/dbs/pinecone"></Card>
|
||||
<Card title="MongoDB" icon="/images/provider-icons/mongodb.svg" href="/components/vectordbs/dbs/mongodb"></Card>
|
||||
<Card title="Oracle AI Vector Search" icon="/images/provider-icons/oracle.svg" href="/components/vectordbs/dbs/oracledb"></Card>
|
||||
<Card title="Azure" icon="/images/provider-icons/azure-color.svg" href="/components/vectordbs/dbs/azure"></Card>
|
||||
<Card title="Redis" icon="/images/provider-icons/redis.svg" href="/components/vectordbs/dbs/redis"></Card>
|
||||
<Card title="Valkey" icon="/images/provider-icons/valkey.svg" href="/components/vectordbs/dbs/valkey"></Card>
|
||||
|
||||
@@ -55,9 +55,8 @@ await addUserPreferences();
|
||||
|
||||
```json Output
|
||||
{
|
||||
"message": "Memory processing has been queued for background execution",
|
||||
"status": "PENDING",
|
||||
"event_id": "9f8c2b1a-4e7d-4c3a-9b21-1a2b3c4d5e6f"
|
||||
"event_id": "9f8c2b1a-4e7d-4c3a-9b21-1a2b3c4d5e6f",
|
||||
"status": "PENDING"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -83,6 +83,50 @@ await client.add(messages, {
|
||||
Expect a `status: "PENDING"` response with an `event_id`. Poll `GET /v1/event/{event_id}/` to confirm completion.
|
||||
</Info>
|
||||
|
||||
### Automatic conversation context
|
||||
|
||||
On the Platform, you only send new messages. Mem0 automatically pulls the earlier messages that share the same identifiers (`user_id`, and `run_id` if you use one) and uses them as context when extracting memories, so you never need to resend conversation history.
|
||||
|
||||
This means a follow-up turn is understood against what came before it:
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
# First interaction
|
||||
client.add(
|
||||
[{"role": "user", "content": "My dog's name is Biscuit. He's a golden retriever."}],
|
||||
user_id="alice",
|
||||
)
|
||||
|
||||
# Later — send only the new turn, no history
|
||||
client.add(
|
||||
[{"role": "user", "content": "He turned 5 today, and I'm taking him to the vet on Friday."}],
|
||||
user_id="alice",
|
||||
)
|
||||
# Stored as: "User's dog Biscuit turned 5" — "He" is resolved against the earlier turn.
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
// First interaction
|
||||
await client.add(
|
||||
[{ role: "user", content: "My dog's name is Biscuit. He's a golden retriever." }],
|
||||
{ userId: "alice" },
|
||||
);
|
||||
|
||||
// Later — send only the new turn, no history
|
||||
await client.add(
|
||||
[{ role: "user", content: "He turned 5 today, and I'm taking him to the vet on Friday." }],
|
||||
{ userId: "alice" },
|
||||
);
|
||||
// Stored as: "User's dog Biscuit turned 5" — "He" is resolved against the earlier turn.
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Without that earlier turn, the same message can only be stored as "User's male pet turned 5", because there is nothing to resolve "He" against. Scope each conversation with a consistent `user_id` (plus `run_id` for a distinct session) and Mem0 handles the rest.
|
||||
|
||||
<Info>
|
||||
This is default behavior and needs no configuration. Earlier SDK versions gated it behind a `version="v2"` argument on `add`; that argument no longer exists and is ignored if sent.
|
||||
</Info>
|
||||
|
||||
## Add with Mem0 Open Source
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
+18
-4
@@ -84,10 +84,9 @@
|
||||
"pages": [
|
||||
"platform/features/advanced-retrieval",
|
||||
"platform/advanced-memory-operations",
|
||||
"platform/features/criteria-retrieval",
|
||||
"platform/features/contextual-add",
|
||||
"platform/features/custom-instructions",
|
||||
"platform/features/memory-decay"
|
||||
"platform/features/memory-decay",
|
||||
"platform/features/dream"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -209,6 +208,7 @@
|
||||
"components/vectordbs/dbs/milvus",
|
||||
"components/vectordbs/dbs/pinecone",
|
||||
"components/vectordbs/dbs/mongodb",
|
||||
"components/vectordbs/dbs/oracledb",
|
||||
"components/vectordbs/dbs/azure",
|
||||
"components/vectordbs/dbs/azure_mysql",
|
||||
"components/vectordbs/dbs/redis",
|
||||
@@ -350,6 +350,8 @@
|
||||
"pages": [
|
||||
"integrations/dify",
|
||||
"integrations/flowise",
|
||||
"integrations/n8n",
|
||||
"integrations/zapier",
|
||||
"integrations/langchain-tools",
|
||||
"integrations/agentops",
|
||||
"integrations/respan",
|
||||
@@ -609,6 +611,10 @@
|
||||
]
|
||||
},
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/platform/features/contextual-add",
|
||||
"destination": "/core-concepts/memory-operations/add"
|
||||
},
|
||||
{
|
||||
"source": "/changelog/openclaw",
|
||||
"destination": "/changelog/sdk"
|
||||
@@ -631,7 +637,7 @@
|
||||
},
|
||||
{
|
||||
"source": "/platform/features/expiration-date",
|
||||
"destination": "/"
|
||||
"destination": "/platform/features/memory-expiration"
|
||||
},
|
||||
{
|
||||
"source": "/cookbooks/essentials/memory-expiration-short-and-long-term",
|
||||
@@ -1228,6 +1234,14 @@
|
||||
{
|
||||
"source": "/open-source/multimodal-support",
|
||||
"destination": "/open-source/features/multimodal-support"
|
||||
},
|
||||
{
|
||||
"source": "/platform/features/criteria-retrieval",
|
||||
"destination": "/platform/features/advanced-retrieval"
|
||||
},
|
||||
{
|
||||
"source": "/integrations/keywords",
|
||||
"destination": "/integrations/respan"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
+1
-49
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 92 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="#8F74E0" role="img" viewBox="0 0 93.9 59.4" xmlns="http://www.w3.org/2000/svg"><title>Oracle</title><path d="M30.5,59.4H65c16.4-0.4,29.3-14.1,28.9-30.4C93.5,13.1,80.7,0.4,65,0H30.5C14.1-0.4,0.4,12.5,0,28.9s12.5,30,28.9,30.4C29.4,59.4,29.9,59.4,30.5,59.4 M64.2,48.9h-33c-10.6-0.3-18.9-9.2-18.6-19.8C13,19,21.1,10.8,31.2,10.5h33c10.6-0.3,19.5,8,19.8,18.6c0.3,10.6-8,19.5-18.6,19.8C65,48.9,64.6,48.9,64.2,48.9"/></svg>
|
||||
|
After Width: | Height: | Size: 427 B |
@@ -65,7 +65,9 @@ The plugin uses the same shell scripts as Claude Code, Cursor, and Codex: hooks
|
||||
| **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message |
|
||||
| **Pre-tool** | `PreToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tools |
|
||||
| **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories |
|
||||
| **Stop** | `Stop` | Stores a session summary when the session ends |
|
||||
| **Stop** | `Stop` | Stores a session summary at the end of every assistant turn (not just at session end) |
|
||||
|
||||
What you type is stored as yours. What the agent produces (session summaries and compaction summaries) is stored as the assistant's, so its suggestions never become your stated preferences.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -44,14 +44,14 @@ Install the full plugin including MCP server, lifecycle hooks, and SDK skill.
|
||||
|
||||
1. Add the Mem0 marketplace:
|
||||
|
||||
```
|
||||
/plugin marketplace add mem0ai/mem0
|
||||
```bash
|
||||
claude plugin marketplace add mem0ai/mem0
|
||||
```
|
||||
|
||||
2. Install the plugin:
|
||||
|
||||
```
|
||||
/plugin install mem0@mem0-plugins
|
||||
```bash
|
||||
claude plugin install mem0@mem0-plugins
|
||||
```
|
||||
|
||||
**Claude Cowork desktop app:** Open the Cowork tab, click **Customize** in the sidebar, click **Browse plugins**, and install Mem0.
|
||||
@@ -88,6 +88,15 @@ Add to your Claude Code MCP config (`.mcp.json`):
|
||||
}
|
||||
```
|
||||
|
||||
### Managing the Plugin
|
||||
|
||||
```bash
|
||||
claude plugin update mem0@mem0-plugins # update the plugin to the latest version (restart to apply)
|
||||
claude plugin marketplace update mem0-plugins # refresh the marketplace catalog
|
||||
claude plugin uninstall mem0@mem0-plugins # uninstall the plugin (keeps the marketplace)
|
||||
claude plugin marketplace remove mem0-plugins # unregister the marketplace entirely
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
Start a new session and ask: *"List my mem0 entities"* or *"Search my memories for hello"*. If the `mem0` tools appear and respond, you're all set.
|
||||
</Info>
|
||||
@@ -143,9 +152,11 @@ When installed via the plugin marketplace, Mem0 hooks into Claude Code's lifecyc
|
||||
| **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message; skips short prompts |
|
||||
| **Pre-tool (3 handlers)** | `PreToolUse` | Blocks MEMORY.md writes; enforces `user_id`/`app_id` on mem0 tool calls; scans files being read for relevant memory context |
|
||||
| **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories |
|
||||
| **Stop** | `Stop` | Stores a session summary when the session ends |
|
||||
| **Stop** | `Stop` | Stores a session summary at the end of every assistant turn (not just at session end) |
|
||||
| **Pre-compact** | `PreCompact` | Stores a summary before the context is compacted |
|
||||
|
||||
What you type is stored as yours. What Claude produces (session summaries and compaction summaries) is stored as the assistant's, so its suggestions never become your stated preferences.
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```text
|
||||
@@ -153,16 +164,17 @@ When installed via the plugin marketplace, Mem0 hooks into Claude Code's lifecyc
|
||||
You: Let's refactor the auth module to use JWT tokens instead of sessions.
|
||||
|
||||
# Claude searches memories, finds nothing relevant, proceeds with the work.
|
||||
# After completing the task, Mem0 stores:
|
||||
# Mem0 stores what you said as yours:
|
||||
# - Your preference: "Prefers TypeScript, uses ESLint"
|
||||
# ...and what Claude did as the assistant's, in the session summary:
|
||||
# - Decision: "Migrated auth from sessions to JWT tokens"
|
||||
# - Files modified: auth/middleware.ts, auth/token.ts
|
||||
# - User preference: "Prefers TypeScript, uses ESLint"
|
||||
|
||||
# Session 2 (days later): Related work
|
||||
You: Add refresh token rotation to the auth system.
|
||||
|
||||
# Claude searches memories, retrieves the JWT migration context.
|
||||
# Knows the file structure, decisions made, and user preferences.
|
||||
# Knows the file structure, decisions made, and your stated preferences.
|
||||
# Continues seamlessly without re-explaining the codebase.
|
||||
```
|
||||
|
||||
|
||||
@@ -125,20 +125,23 @@ When installed via the plugin marketplace, Mem0 hooks into Codex's lifecycle to
|
||||
| **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message |
|
||||
| **Pre-tool (3 handlers)** | `PreToolUse` | Blocks MEMORY.md writes; enforces `user_id`/`app_id` on mem0 tool calls; scans files being read for relevant memory context |
|
||||
| **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories |
|
||||
| **Stop** | `Stop` | Stores a session summary when the session ends |
|
||||
| **Stop** | `Stop` | Stores a session summary at the end of every assistant turn (not just at session end) |
|
||||
| **Pre-compact** | `PreCompact` | Stores a summary before the context is compacted |
|
||||
|
||||
What you type is stored as yours. What Codex produces (session summaries and compaction summaries) is stored as the assistant's, so its suggestions never become your stated preferences.
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```text
|
||||
# Task 1: Setting up a new service
|
||||
You: Create a REST API for the notifications service using Express and TypeScript.
|
||||
|
||||
# Codex searches memories, finds user preferences from prior tasks.
|
||||
# After completing the task, Mem0 stores:
|
||||
# Codex searches memories, finds your preferences from prior tasks.
|
||||
# Mem0 stores what you said as yours:
|
||||
# - Your preference: "Prefers explicit error types over generic catch-all"
|
||||
# ...and what Codex did as the assistant's, in the session summary:
|
||||
# - Decision: "Notifications service uses Express + TypeScript + Zod validation"
|
||||
# - Convention: "All API routes follow /api/v1/{resource} pattern"
|
||||
# - Preference: "User prefers explicit error types over generic catch-all"
|
||||
|
||||
# Task 2 (days later): Extending the service
|
||||
You: Add WebSocket support for real-time notification delivery.
|
||||
|
||||
@@ -96,10 +96,11 @@ Once installed, the following tools are available in every Cursor session:
|
||||
You: The API endpoint /users is taking 3 seconds. Help me optimize it.
|
||||
|
||||
# Cursor agent searches memories, proceeds with investigation.
|
||||
# After completing the task, Mem0 stores:
|
||||
# Mem0 stores what you said as yours:
|
||||
# - Your preference: "Prefers query-level fixes over caching"
|
||||
# ...and what the agent did as the assistant's:
|
||||
# - Learning: "N+1 query in UserService.getAll(): fixed with eager loading"
|
||||
# - Decision: "Added database index on users.email column"
|
||||
# - Preference: "User prefers query-level fixes over caching"
|
||||
|
||||
# Session 2 (next week): Similar issue
|
||||
You: The /orders endpoint is also slow, same pattern as before.
|
||||
|
||||
@@ -98,9 +98,8 @@ add_result = add_tool.invoke(add_input)
|
||||
|
||||
```json Output
|
||||
{
|
||||
"message": "Memory processing has been queued for background execution",
|
||||
"status": "PENDING",
|
||||
"event_id": "3a1b2c3d-4e5f-6789-abcd-ef0123456789"
|
||||
"event_id": "3a1b2c3d-4e5f-6789-abcd-ef0123456789",
|
||||
"status": "PENDING"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
---
|
||||
title: n8n
|
||||
description: "Add long-term memory to n8n workflows and AI Agents with the Mem0 community node, no code required."
|
||||
---
|
||||
|
||||
Your n8n workflows start from zero on every run. The [`@mem0/n8n-nodes-mem0`](https://www.npmjs.com/package/@mem0/n8n-nodes-mem0) community node fixes that: store durable facts as memories, recall them in any later run, and hand the node to an [n8n AI Agent](https://docs.n8n.io/advanced-ai/) as a tool so it can remember and recall on its own.
|
||||
|
||||
## Overview
|
||||
|
||||
1. Install the node from n8n's community nodes panel.
|
||||
2. Connect your Mem0 API key once as a credential.
|
||||
3. Drop a **Mem0** node into any workflow to add, search, or manage memories.
|
||||
4. Optionally attach it to an **AI Agent** node, where it becomes a tool the agent calls itself.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. A Mem0 API key from the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-n8n" rel="nofollow">API Keys dashboard</a> (sign up at <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-n8n" rel="nofollow">app.mem0.ai</a> if you do not have an account).
|
||||
2. A **self-hosted** n8n instance. Installing community nodes from npm is a self-hosted feature; n8n Cloud only offers nodes that n8n has verified.
|
||||
3. Owner access to that instance, since only instance owners can install community nodes.
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
<Step title="Open the community nodes panel">
|
||||
In n8n, go to **Settings → Community Nodes** and select **Install**.
|
||||
</Step>
|
||||
<Step title="Install the package">
|
||||
Enter `@mem0/n8n-nodes-mem0`, tick the risk acknowledgement, and select **Install**.
|
||||
</Step>
|
||||
<Step title="Create the credential">
|
||||
Add a new **Mem0 API** credential and paste your API key. Leave **Base URL** at `https://api.mem0.ai` unless you run Mem0 somewhere else.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Info>
|
||||
**Verify the install:** search the nodes panel for `Mem0`. The node should appear with a **Memory** resource offering Add, Search, Get, Get Many, Update, and Delete.
|
||||
</Info>
|
||||
|
||||
## Quickstart
|
||||
|
||||
A two-node workflow that writes a memory and reads it back:
|
||||
|
||||
```text
|
||||
Manual Trigger → Mem0 (Add) → Mem0 (Search)
|
||||
```
|
||||
|
||||
<Steps>
|
||||
<Step title="Add a memory">
|
||||
Add a **Mem0** node, keep **Operation: Add**, set **User ID** to `alice`, and add one message with **Role** `user` and **Content**:
|
||||
|
||||
`I am vegetarian and I never eat mushrooms.`
|
||||
</Step>
|
||||
<Step title="Search for it">
|
||||
Add a second **Mem0** node with **Operation: Search**, **User ID** `alice`, and **Query** `what does the user eat?`.
|
||||
</Step>
|
||||
<Step title="Run it">
|
||||
Select **Test workflow**. The Search node returns the extracted dietary memory.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Note>
|
||||
Extraction is asynchronous. The Add node's **Wait for Completion** option is on by default, so it polls until extraction finishes before the next node runs. If you turn it off, allow a few seconds before searching for what you just wrote.
|
||||
</Note>
|
||||
|
||||
## Use it as an AI Agent tool
|
||||
|
||||
The node is marked `usableAsTool`, so an n8n **AI Agent** (Tools Agent) can call it without any wiring on your side:
|
||||
|
||||
```text
|
||||
Chat Trigger → AI Agent ──tool──▶ Mem0 (Search)
|
||||
──tool──▶ Mem0 (Add)
|
||||
```
|
||||
|
||||
Attach one Mem0 node set to **Search** and one set to **Add**. The agent searches memory before answering and writes back durable facts after a meaningful exchange. Keep **User ID** the same on both.
|
||||
|
||||
## Operations
|
||||
|
||||
The node wraps the hosted Mem0 REST API and supports six operations on the **Memory** resource:
|
||||
|
||||
| Operation | What it does | Endpoint |
|
||||
| --- | --- | --- |
|
||||
| **Add** | Extract and store memories from messages | `POST /v3/memories/add/` |
|
||||
| **Search** | Semantic search over stored memories | `POST /v3/memories/search/` |
|
||||
| **Get Many** | List stored memories (one page, or **Return All**) | `POST /v3/memories/` |
|
||||
| **Get** | Fetch a single memory by ID | `GET /v1/memories/{id}/` |
|
||||
| **Update** | Change a memory's text or metadata | `PUT /v1/memories/{id}/` |
|
||||
| **Delete** | Delete a single memory by ID | `DELETE /v1/memories/{id}/` |
|
||||
|
||||
### Add
|
||||
|
||||
Extracts and stores memories from one or more messages. Supply at least one entity id (**User ID**, or **Agent ID** / **App ID** / **Run ID** under Additional Fields); the node checks this before calling the API.
|
||||
|
||||
**Additional Fields:**
|
||||
|
||||
| Field | Purpose |
|
||||
| --- | --- |
|
||||
| **Agent ID** | Scopes the memory to an agent |
|
||||
| **App ID** | Scopes the memory to an app or project |
|
||||
| **Run ID** | Scopes the memory to a single session or run |
|
||||
| **Metadata (JSON)** | Arbitrary JSON attached to each extracted memory |
|
||||
| **Infer** | On by default. Turn off to store messages verbatim instead of running LLM extraction |
|
||||
| **Custom Instructions** | Free-text guidance steering what the extractor keeps or ignores, for this call |
|
||||
| **Custom Categories** | JSON array of `{category: description}` objects, replacing the project-level catalog for this call |
|
||||
| **Includes** | Only extract memories matching this description |
|
||||
| **Excludes** | Skip memories matching this description |
|
||||
|
||||
**Includes** and **Excludes** narrow what extraction keeps. Sending *"I am vegetarian and I never eat mushrooms. I drive a blue Toyota Corolla and my parking spot is B12"* stores three memories by default; with `Includes: "only record food and diet preferences"` it stores just the dietary one.
|
||||
|
||||
### Search
|
||||
|
||||
Semantic search over stored memories. Takes a **Query**, at least one entity id, and an optional **Limit**.
|
||||
|
||||
### Get Many
|
||||
|
||||
Lists stored memories for the entity ids you supply. Turn on **Return All** to page through everything automatically, or leave it off to fetch a single **Page**. **Page Size** applies either way.
|
||||
|
||||
### Get, Update, Delete
|
||||
|
||||
Operate on one memory by **Memory ID**. Update accepts new **Text** and/or **Metadata (JSON)**.
|
||||
|
||||
## Entity filters on Search and Get Many
|
||||
|
||||
Both operations take **User ID**, **Agent ID**, **App ID**, and **Run ID**. At least one is required, since the API rejects a query with no entity scope, and the node fails with a clear message before making the call if all four are empty.
|
||||
|
||||
Supply several and they combine with **OR**, so the result is the union of those scopes:
|
||||
|
||||
```json
|
||||
{ "OR": [{ "user_id": "alice" }, { "agent_id": "support-bot" }] }
|
||||
```
|
||||
|
||||
<Warning>
|
||||
This is deliberate, not a shortcut. Mem0 indexes each entity separately, so an `AND` across `user_id` and `agent_id` matches nothing even when a memory was written with both. To narrow rather than widen, run one operation per entity id.
|
||||
</Warning>
|
||||
|
||||
## Choosing a User ID
|
||||
|
||||
The **User ID** is a stable string you pick to identify whose memories these are. It is not looked up in the dashboard, so any consistent value works: your app's internal user ID, an email, or a UUID. Use the same value across Add, Search, and Get Many or recall returns nothing.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **The node does not appear in the panel**: community nodes install on self-hosted n8n only, and only instance owners can install them. On n8n Cloud, this node is not yet available.
|
||||
- **`401 Unauthorized`**: the API key is wrong or was revoked. Regenerate it in the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-n8n" rel="nofollow">API Keys dashboard</a> and update the credential.
|
||||
- **"Provide at least one of User ID, Agent ID, App ID, or Run ID"**: every Add, Search, and Get Many needs an entity scope. Fill in at least one.
|
||||
- **Search returns nothing right after an Add**: extraction is asynchronous. Leave **Wait for Completion** on, or add a short Wait node before searching.
|
||||
- **Searching two entity ids returns more than expected**: multiple ids are combined with OR by design. Run one operation per id to narrow.
|
||||
- **"Timed out waiting for memory event"**: the add was accepted and is likely still finishing on the server. A timeout here does not mean it failed.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Zapier Integration" icon="bolt" href="/integrations/zapier">
|
||||
Add memory to Zaps across thousands of apps
|
||||
</Card>
|
||||
<Card title="Flowise Integration" icon="blocks" href="/integrations/flowise">
|
||||
Add memory to Flowise chatflows
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Snippet file="star-on-github.mdx" />
|
||||
@@ -0,0 +1,143 @@
|
||||
---
|
||||
title: Zapier
|
||||
description: "Add, search, and manage Mem0 memories from any Zap using the Mem0 Zapier app, no code required."
|
||||
---
|
||||
|
||||
Zaps fire and forget. The [Mem0](https://mem0.ai) app gives them memory: store durable facts from a form submission, a support ticket, or a chat message, then recall them later from any of [Zapier's](https://zapier.com) thousands of apps. No code, no server.
|
||||
|
||||
## Overview
|
||||
|
||||
1. Connect your Mem0 API key once as a Zapier connection.
|
||||
2. Use **Add Memory** to store what a Zap learns.
|
||||
3. Use **Search Memories** or **Get Memories** to pull that context back into a later step.
|
||||
4. Use **Delete Memory** to remove one by ID.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. A Mem0 API key from the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-zapier" rel="nofollow">API Keys dashboard</a> (sign up at <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-zapier" rel="nofollow">app.mem0.ai</a> if you do not have an account).
|
||||
2. A Zapier account on any plan.
|
||||
|
||||
<Note>
|
||||
The Mem0 app is not yet listed in Zapier's public App Directory, so you need an invite link to add it to a Zap. Email [support@mem0.ai](mailto:support@mem0.ai) to request one.
|
||||
</Note>
|
||||
|
||||
## Setup
|
||||
|
||||
<Steps>
|
||||
<Step title="Add a Mem0 step">
|
||||
In the Zap editor, search for **Mem0** and pick an action such as **Add Memory**.
|
||||
</Step>
|
||||
<Step title="Connect your account">
|
||||
Select **Sign in**, paste your **Mem0 API Key** (it starts with `m0-`), and leave **Base URL** at `https://api.mem0.ai` unless you run Mem0 somewhere else.
|
||||
</Step>
|
||||
<Step title="Confirm the connection">
|
||||
Zapier validates the key against Mem0 the moment you save it. A connection labelled **Mem0** means the key works.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Info>
|
||||
The key is a password field, so Zapier masks it in the editor. It is sent to Mem0 as `Authorization: Token <key>`.
|
||||
</Info>
|
||||
|
||||
## Quickstart
|
||||
|
||||
Remember what a user tells you:
|
||||
|
||||
```text
|
||||
Trigger (form, chat, ticket) → Mem0: Add Memory
|
||||
```
|
||||
|
||||
Set **Content** to the message text and **User ID** to a stable identifier for that person, such as their email.
|
||||
|
||||
Then recall it in a later Zap:
|
||||
|
||||
```text
|
||||
Trigger (new message) → Mem0: Search Memories → Send reply
|
||||
```
|
||||
|
||||
Set **Query** to the incoming message and **User ID** to the same value. The matched memories become available to every step after it.
|
||||
|
||||
<Note>
|
||||
Extraction is asynchronous. **Add Memory** returns immediately with an event ID by default, so a Search fired a second later may not see the new memory yet. See [Waiting for extraction](#waiting-for-extraction).
|
||||
</Note>
|
||||
|
||||
## Actions
|
||||
|
||||
| Type | Action | What it does | Endpoint |
|
||||
| --- | --- | --- | --- |
|
||||
| Create | **Add Memory** | Extract and store memories from a message | `POST /v3/memories/add/` |
|
||||
| Search | **Search Memories** | Semantic search over stored memories | `POST /v3/memories/search/` |
|
||||
| Search | **Get Memories** | List stored memories, one page at a time | `POST /v3/memories/` |
|
||||
| Create | **Delete Memory** | Delete a single memory by ID | `DELETE /v1/memories/{id}/` |
|
||||
|
||||
### Add Memory
|
||||
|
||||
| Field | Required | Purpose |
|
||||
| --- | --- | --- |
|
||||
| **Content** | Yes | The message text to extract memories from |
|
||||
| **Role** | | `User` (default), `Assistant`, or `System` |
|
||||
| **User ID** | | Scopes the memory to a person |
|
||||
| **Agent ID** | | Scopes the memory to an agent |
|
||||
| **Run ID** | | Scopes the memory to a single session or run |
|
||||
| **Metadata (JSON)** | | Arbitrary JSON attached to each extracted memory |
|
||||
| **Custom Instructions** | | Free-text guidance steering what the extractor keeps or ignores, for this call |
|
||||
| **Custom Categories (JSON)** | | JSON array of `{category: description}` objects, replacing the project-level catalog for this call |
|
||||
| **Includes** | | Only extract memories matching this description |
|
||||
| **Excludes** | | Skip memories matching this description |
|
||||
| **Infer** | | On by default. Turn off to store the message verbatim instead of running LLM extraction |
|
||||
| **Wait for Completion** | | Off by default. Turn on to poll until extraction finishes and return the resulting memories |
|
||||
|
||||
**Includes** and **Excludes** narrow what extraction keeps. Sending *"I am vegetarian and I never eat mushrooms. I drive a blue Toyota Corolla and my parking spot is B12"* stores three memories by default; with `Includes: "only record food and diet preferences"` it stores just the dietary one.
|
||||
|
||||
#### Waiting for extraction
|
||||
|
||||
Extraction runs asynchronously, so **Add Memory** returns an event ID and moves on unless you turn on **Wait for Completion**. When you do, the step polls for up to 60 seconds and returns the extracted memories instead.
|
||||
|
||||
<Warning>
|
||||
Extraction can take longer than Zapier allows a single step to run, which is why waiting is opt-in. If the step times out, the add was still accepted and typically completes on Mem0's side, so do not retry it blindly.
|
||||
</Warning>
|
||||
|
||||
### Search Memories
|
||||
|
||||
| Field | Required | Purpose |
|
||||
| --- | --- | --- |
|
||||
| **Query** | Yes | Natural-language search text |
|
||||
| **User ID** | Yes | Whose memories to search. The API needs an entity filter |
|
||||
| **Limit** | | Maximum results, default `50` |
|
||||
|
||||
### Get Memories
|
||||
|
||||
| Field | Required | Purpose |
|
||||
| --- | --- | --- |
|
||||
| **User ID** | Yes | Whose memories to list |
|
||||
| **Limit** | | Memories per page, default `50` |
|
||||
| **Page** | | Which page to return, 1-based, default `1` |
|
||||
|
||||
Returns one page per run. Raise **Page** to walk through larger result sets.
|
||||
|
||||
### Delete Memory
|
||||
|
||||
Takes a **Memory ID** and deletes that memory. Pair it with **Search Memories** or **Get Memories** to get the ID first.
|
||||
|
||||
## Choosing a User ID
|
||||
|
||||
The **User ID** is a stable string you pick to identify whose memories these are. It is not looked up in the dashboard, so any consistent value works: your app's internal user ID, an email, or a UUID. Use the same value on Add, Search, and Get Memories or recall returns nothing.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Mem0 does not appear in the Zap editor**: the app is not yet in the public App Directory. Email [support@mem0.ai](mailto:support@mem0.ai) for an invite link.
|
||||
- **The connection fails when you paste the key**: check that it starts with `m0-` and has not been revoked in the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-zapier" rel="nofollow">API Keys dashboard</a>.
|
||||
- **Search returns nothing right after an Add**: extraction is asynchronous. Turn on **Wait for Completion**, or put a Zapier **Delay** step before the Search.
|
||||
- **"Metadata must be valid JSON" or "Custom Categories must be valid JSON"**: those fields take raw JSON. Check for smart quotes and trailing commas.
|
||||
- **The Add step times out**: the memory was still accepted and is likely finishing server-side. Confirm with **Get Memories** before re-running.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="n8n Integration" icon="diagram-project" href="/integrations/n8n">
|
||||
Build workflows with the Mem0 n8n community node
|
||||
</Card>
|
||||
<Card title="Flowise Integration" icon="blocks" href="/integrations/flowise">
|
||||
Add memory to Flowise chatflows
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Snippet file="star-on-github.mdx" />
|
||||
+13
-13
@@ -10,7 +10,7 @@ mode: "custom"
|
||||
</h1>
|
||||
|
||||
<p className="max-w-3xl mx-auto text-base text-gray-600 dark:text-zinc-400 leading-relaxed">
|
||||
Universal, self-improving memory layer for LLM applications.
|
||||
Mem0 gives your AI agents long-term memory that persists across sessions, tools, and runs.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -34,10 +34,10 @@ mode: "custom"
|
||||
/>
|
||||
<div className="flex flex-1 flex-col gap-2 px-4 pb-4 pt-3 text-left">
|
||||
<h3 className="text-base font-semibold text-gray-900 dark:text-zinc-100 group-hover:text-primary">
|
||||
Add memory to your app
|
||||
Store your first memory
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-zinc-400">
|
||||
Start with the quickstart and store your first memory in minutes.
|
||||
Five-minute quickstart: get an API key, then save and search a memory in Python or JavaScript.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
@@ -60,10 +60,10 @@ mode: "custom"
|
||||
/>
|
||||
<div className="flex flex-1 flex-col gap-2 px-4 pb-4 pt-3 text-left">
|
||||
<h3 className="text-base font-semibold text-gray-900 dark:text-zinc-100 group-hover:text-primary">
|
||||
Add memory to your agent
|
||||
Add memory to your coding agent
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-zinc-400">
|
||||
Give Claude Code, Cursor, and Codex memory that persists across sessions. A drop-in plugin, no code to write.
|
||||
Plugins that let Claude Code, Cursor, Codex, and other harnesses remember your project. Opens the Claude Code guide.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
@@ -86,10 +86,10 @@ mode: "custom"
|
||||
/>
|
||||
<div className="flex flex-1 flex-col gap-2 px-4 pb-4 pt-3 text-left">
|
||||
<h3 className="text-base font-semibold text-gray-900 dark:text-zinc-100 group-hover:text-primary">
|
||||
Sign up for Mem0 as an agent
|
||||
Let an AI agent sign itself up
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-zinc-400">
|
||||
Let an AI agent mint a Mem0 API key, claim ownership later, and write its first memory from the terminal.
|
||||
Four terminal commands create an account and API key. No email, no dashboard. Claim it as owner later.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
@@ -112,10 +112,10 @@ mode: "custom"
|
||||
/>
|
||||
<div className="flex flex-1 flex-col gap-2 px-4 pb-4 pt-3 text-left">
|
||||
<h3 className="text-base font-semibold text-gray-900 dark:text-zinc-100 group-hover:text-primary">
|
||||
Explore Mem0 integrations
|
||||
Use Mem0 with your framework
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-zinc-400">
|
||||
Connect Mem0 to LangChain, CrewAI, Vercel AI SDK, and 20+ partner frameworks.
|
||||
Setup guides for 22 tools, including LangChain, CrewAI, LlamaIndex, and the Vercel AI SDK.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
@@ -138,10 +138,10 @@ mode: "custom"
|
||||
/>
|
||||
<div className="flex flex-1 flex-col gap-2 px-4 pb-4 pt-3 text-left">
|
||||
<h3 className="text-base font-semibold text-gray-900 dark:text-zinc-100 group-hover:text-primary">
|
||||
See memory-app examples
|
||||
Copy a working example
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-zinc-400">
|
||||
Browse cookbooks for companions, support agents, voice agents, research tools, and more.
|
||||
Full walkthroughs for companion chatbots, support agents, voice agents, and research tools.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
@@ -164,10 +164,10 @@ mode: "custom"
|
||||
/>
|
||||
<div className="flex flex-1 flex-col gap-2 px-4 pb-4 pt-3 text-left">
|
||||
<h3 className="text-base font-semibold text-gray-900 dark:text-zinc-100 group-hover:text-primary">
|
||||
Self-host Mem0
|
||||
Run Mem0 on your own servers
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-zinc-400">
|
||||
Use Open Source when you want memory without the hosted platform.
|
||||
The open-source version. Run it as a library or a Docker stack, with your data on your infrastructure.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
+6
-5
@@ -38,7 +38,7 @@ This mints an evaluation key in <5 seconds. Use it immediately against the Platf
|
||||
|
||||
## Identify the User's Setup
|
||||
|
||||
Look at the user's imports first - they determine which product (Platform vs OSS) and which language you should quote docs from. **Mem0 Platform (managed) is the recommended path** - 4-line integration, sub-50ms retrieval, no infra. Route to OSS only when the user has an explicit self-hosting requirement.
|
||||
Look at the user's imports first - they determine which product (Platform vs OSS) and which language you should quote docs from. **Mem0 Platform (managed) is the recommended path** - 4-line integration, no infra to run. Route to OSS only when the user has an explicit self-hosting requirement.
|
||||
|
||||
### Platform - Python [Platform]
|
||||
|
||||
@@ -170,7 +170,7 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
|
||||
|
||||
- [Introduction](https://docs.mem0.ai/introduction) [Both]: Use when the user wants a one-page overview of how memory fits between the LLM and the app.
|
||||
- [Vibe Code with Mem0](https://docs.mem0.ai/vibecoding) [Both]: Use when the user is in Claude Code, Cursor, or Windsurf and wants memory wired into their editor.
|
||||
- [Platform Overview](https://docs.mem0.ai/platform/overview) [Platform]: Use when the user picks the managed product - 4-line integration, sub-50ms retrieval, dashboard.
|
||||
- [Platform Overview](https://docs.mem0.ai/platform/overview) [Platform]: Use when the user picks the managed product - 4-line integration, hosted API, dashboard.
|
||||
- [Sign up as an agent](https://docs.mem0.ai/platform/agent-signup) [Platform]: Use when an AI agent needs to mint a Mem0 API key autonomously - four commands, no email or dashboard, human claims ownership later.
|
||||
- [Platform vs Open Source](https://docs.mem0.ai/platform/platform-vs-oss) [Both]: Use when the user is deciding between managed and self-hosted.
|
||||
- [Platform Quickstart](https://docs.mem0.ai/platform/quickstart) [Platform]: Use for the first Platform integration - API key plus `MemoryClient.add/search`.
|
||||
@@ -204,11 +204,10 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
|
||||
|
||||
### Features - Advanced Retrieval
|
||||
- [Advanced Retrieval](https://docs.mem0.ai/platform/features/advanced-retrieval) [Platform]: Use when the user needs keyword search, reranking, or hybrid retrieval.
|
||||
- [Criteria-Based Retrieval](https://docs.mem0.ai/platform/features/criteria-retrieval) [Platform]: Use when targeting memories by custom criteria, not just semantic similarity.
|
||||
- [Temporal Reasoning](https://docs.mem0.ai/platform/features/temporal-reasoning) [Platform]: Use when time-aware searches like last week, upcoming, or right now need better result ordering.
|
||||
- [Contextual Add](https://docs.mem0.ai/platform/features/contextual-add) [Platform]: Use when `add()` should consider the surrounding conversation, not just the latest turn.
|
||||
- [Custom Instructions](https://docs.mem0.ai/platform/features/custom-instructions) [Platform]: Use when tailoring what Mem0 extracts and stores on Platform.
|
||||
- [Memory Decay](https://docs.mem0.ai/platform/features/memory-decay) [Platform]: Use when search results should boost recently-reinforced memories and dampen stale ones. Opt in per project; applies at search time and never filters candidates out.
|
||||
- [Dream](https://docs.mem0.ai/platform/features/dream) [Platform]: Use when long-lived user memory should stay coherent on its own - synthesizing recurring patterns, superseding outdated facts, and merging duplicates in the background. Synthesis is opt-in (Pro+); Supersede and Merge are always on.
|
||||
- [Advanced Memory Operations](https://docs.mem0.ai/platform/advanced-memory-operations) [Platform]: Use when basic CRUD is not enough - batch ops, complex filters, workflows.
|
||||
|
||||
### Features - Data Management
|
||||
@@ -283,6 +282,8 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
|
||||
### Developer Tools
|
||||
- [Dify](https://docs.mem0.ai/integrations/dify) [Both]: Use when the user is on Dify LLMOps.
|
||||
- [Flowise](https://docs.mem0.ai/integrations/flowise) [Both]: Use when the user is on Flowise no-code.
|
||||
- [n8n](https://docs.mem0.ai/integrations/n8n) [Both]: Use when the user builds workflows or AI agents in n8n.
|
||||
- [Zapier](https://docs.mem0.ai/integrations/zapier) [Both]: Use when the user automates workflows with Zapier.
|
||||
- [AgentOps](https://docs.mem0.ai/integrations/agentops) [Both]: Use when tracking agent observability with memory metadata.
|
||||
- [Respan](https://docs.mem0.ai/integrations/respan) [Both]: Use when monitoring Mem0 with Respan (formerly Keywords AI) LLM observability.
|
||||
- [Raycast](https://docs.mem0.ai/integrations/raycast) [Both]: Use when the user wants quick memory access via Raycast.
|
||||
@@ -418,7 +419,6 @@ Editor-specific setup docs (already listed above under `## Integrations > AI Cod
|
||||
### MCP Endpoints
|
||||
|
||||
- Hosted MCP server: `https://mcp.mem0.ai` - requires Platform API key. See `platform/mem0-mcp`.
|
||||
- Self-hosted MCP server: ships with `openmemory/api/` (FastAPI) - runs against your own Qdrant + LLM stack.
|
||||
|
||||
## Community & Support
|
||||
|
||||
@@ -474,6 +474,7 @@ Everything below is OSS-only provider configuration. Skip this entire section wh
|
||||
- [Milvus](https://docs.mem0.ai/components/vectordbs/dbs/milvus) [OSS]: Use for large-scale Milvus deployments.
|
||||
- [Pinecone](https://docs.mem0.ai/components/vectordbs/dbs/pinecone) [OSS]: Use when the user is on Pinecone managed.
|
||||
- [MongoDB](https://docs.mem0.ai/components/vectordbs/dbs/mongodb) [OSS]: Use when Mongo Atlas Vector Search is the backing store.
|
||||
- [Oracle AI Vector Search](https://docs.mem0.ai/components/vectordbs/dbs/oracledb) [OSS]: Use when Oracle Database AI Vector Search is the backing store.
|
||||
- [Azure AI Search](https://docs.mem0.ai/components/vectordbs/dbs/azure) [OSS]: Use when the user is on Azure AI Search.
|
||||
- [Azure MySQL](https://docs.mem0.ai/components/vectordbs/dbs/azure_mysql) [OSS]: Use when vector search runs on Azure Database for MySQL.
|
||||
- [Redis](https://docs.mem0.ai/components/vectordbs/dbs/redis) [OSS]: Use when Redis Stack is the backing store.
|
||||
|
||||
@@ -146,9 +146,8 @@ curl -X POST 'https://api.mem0.ai/v3/memories/?page=1&page_size=50' \
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Memory processing has been queued for background execution",
|
||||
"status": "PENDING",
|
||||
"event_id": "evt-uuid"
|
||||
"event_id": "evt-uuid",
|
||||
"status": "PENDING"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ See the full catalog in <Link href="/components/llms/overview">Components</Link>
|
||||
- Qdrant connection errors: confirm port `6333` is exposed and the API key (if set) matches.
|
||||
- Empty search results: verify the embedder model name. A mismatch causes dimension errors.
|
||||
- `Unknown reranker` (Python): upgrade the SDK with `pip install --upgrade mem0ai` to load the latest provider registry.
|
||||
- `Cannot find module` (Node): import from the OSS entry point, `import { Memory } from "mem0ai/oss"`, not `"mem0ai"`.
|
||||
- `Cannot find module` (Node): two common causes. First, import from the OSS entry point, `import { Memory } from "mem0ai/oss"`, not `"mem0ai"`. Second, provider SDKs are optional peer dependencies loaded on demand, so install the one for the provider you configured (for example `npm install @qdrant/js-client-rest` for Qdrant). Installing `mem0ai` alone only pulls in the providers used by default; you do not need SDKs for providers you never select.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
|
||||
@@ -97,7 +97,7 @@ const results = await memory.search("What movies do I like?", {
|
||||
|
||||
### LLM reranker
|
||||
|
||||
To score with an LLM instead of a dedicated reranker, use the `llm_reranker` provider. It builds its own LLM from the reranker's config (defaulting to `openai` / `gpt-4o-mini`) rather than reusing the Memory's main `llm`:
|
||||
To score with an LLM instead of a dedicated reranker, use the `llm_reranker` provider. It builds its own LLM from the reranker's config (defaulting to `openai` / `gpt-5-mini`) rather than reusing the Memory's main `llm`:
|
||||
|
||||
```typescript
|
||||
const memory = new Memory({
|
||||
@@ -137,7 +137,7 @@ const memory = new Memory({
|
||||
| `zero_entropy` | `zerank-1` | `apiKey`, `model`, `topK` |
|
||||
| `sentence_transformer` | `Xenova/ms-marco-MiniLM-L-6-v2` | `model`, `device`, `maxLength`, `normalize`, `topK` |
|
||||
| `huggingface` | `Xenova/bge-reranker-base` | `model`, `device`, `maxLength`, `normalize`, `topK` |
|
||||
| `llm_reranker` | `openai` / `gpt-4o-mini` | `provider`, `model`, `apiKey`, `llm` (nested override), `topK` |
|
||||
| `llm_reranker` | `openai` / `gpt-5-mini` | `provider`, `model`, `apiKey`, `llm` (nested override), `topK` |
|
||||
|
||||
<Note>
|
||||
`rerank` is opt-in per search and a no-op when no `reranker` is configured. If the reranker call fails, Mem0 logs a warning and returns the original vector-ranked results.
|
||||
|
||||
@@ -35,8 +35,7 @@ pip install mem0ai
|
||||
from mem0 import Memory
|
||||
|
||||
m = Memory()
|
||||
|
||||
````
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Add a memory">
|
||||
@@ -46,7 +45,7 @@ messages = [
|
||||
{"role": "assistant", "content": "Hey Alex! I'll remember your interests."}
|
||||
]
|
||||
m.add(messages, user_id="alex")
|
||||
````
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
|
||||
+2260
-452
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ pip install mem0ai
|
||||
</Step>
|
||||
<Step title="Export your API key">
|
||||
```bash
|
||||
export MEM0_API_KEY="sk-platform-..."
|
||||
export MEM0_API_KEY="m0-..."
|
||||
```
|
||||
</Step>
|
||||
<Step title="Create an async client">
|
||||
@@ -49,7 +49,7 @@ npm install mem0ai
|
||||
</Step>
|
||||
<Step title="Load your API key">
|
||||
```bash
|
||||
export MEM0_API_KEY="sk-platform-..."
|
||||
export MEM0_API_KEY="m0-..."
|
||||
```
|
||||
</Step>
|
||||
<Step title="Instantiate the client">
|
||||
@@ -158,6 +158,8 @@ await memory.update(matches.results[0].id, {
|
||||
|
||||
## Clean up
|
||||
|
||||
Scope each delete call to a single entity id where possible; combining more than one entity filter in the same `delete_all` call is not guaranteed to AND them together today.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
<Steps>
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
---
|
||||
title: Sign up as an agent
|
||||
description: "Zero-friction signup for AI agents: mint a working Mem0 API key in under five seconds. No email, no dashboard, no OTP."
|
||||
description: "Let an AI agent create its own Mem0 account and API key in four commands, with no email or dashboard needed."
|
||||
icon: "robot"
|
||||
iconType: "solid"
|
||||
---
|
||||
|
||||
If you're an AI agent (Claude Code, Cursor, Codex, Cline, Aider, Windsurf, or any other autonomous coding tool), you don't need a human to set up Mem0 for you. Four commands (install, sign up, push a memory, search it) and you have persistent memory.
|
||||
Normally a person signs up for Mem0, gets an API key from the dashboard, and hands it to their tools. Agent Mode skips that: a coding agent creates its own account from the terminal and starts storing memories immediately. A human can take ownership of that account later, without disrupting the agent.
|
||||
|
||||
## The 4-command demo
|
||||
This page is written for both readers. The setup below is for the agent; [Claiming the account](#claiming-the-account-for-the-human-owner) is for the person who ends up owning it.
|
||||
|
||||
<Note>
|
||||
**If you're an AI agent** (Claude Code, Cursor, Codex, Cline, Aider, Windsurf, or any other coding tool), run the four commands below. You don't need a human to set Mem0 up for you.
|
||||
</Note>
|
||||
|
||||
## Sign up in four commands
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
@@ -17,11 +23,11 @@ npm install -g @mem0/cli
|
||||
# 2. Sign up as an agent (replace `claude-code` with your name)
|
||||
mem0 init --agent --agent-caller claude-code
|
||||
|
||||
# 3. Push a memory
|
||||
mem0 add "I am using mem0"
|
||||
# 3. Store a memory
|
||||
mem0 add "Prefers TypeScript over JavaScript for new projects"
|
||||
|
||||
# 4. Verify
|
||||
mem0 search "am I using mem0"
|
||||
# 4. Search it back
|
||||
mem0 search "what language should I use"
|
||||
```
|
||||
|
||||
```bash pip
|
||||
@@ -31,14 +37,22 @@ pip install mem0-cli
|
||||
# 2. Sign up as an agent (replace `claude-code` with your name)
|
||||
mem0 init --agent --agent-caller claude-code
|
||||
|
||||
# 3. Push a memory
|
||||
mem0 add "I am using mem0"
|
||||
# 3. Store a memory
|
||||
mem0 add "Prefers TypeScript over JavaScript for new projects"
|
||||
|
||||
# 4. Verify
|
||||
mem0 search "am I using mem0"
|
||||
# 4. Search it back
|
||||
mem0 search "what language should I use"
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Step 2 prints your new account's default scope and how to hand the account to a human later:
|
||||
|
||||
```
|
||||
✓ Agent Mode active. Default user_id: swift-otter-4821
|
||||
|
||||
To claim this account later: mem0 init --email <your-email>
|
||||
```
|
||||
|
||||
That's it. Under 30 seconds from a fresh shell to a working memory store.
|
||||
|
||||
## Identify yourself with `--agent-caller`
|
||||
@@ -46,17 +60,17 @@ That's it. Under 30 seconds from a fresh shell to a working memory store.
|
||||
Replace `claude-code` in the `init` command with your actual agent name (`cursor`, `codex`, `cline`, `aider`, or any short label). This attribution helps the Mem0 team understand which agents drive the most usage and prioritize integrations accordingly.
|
||||
|
||||
<Tip>
|
||||
**Forgot `--agent-caller` on init?** Run `mem0 identify <your-name>` after the fact, same key, just back-fills the attribution. Idempotent.
|
||||
**Forgot `--agent-caller` when you ran `init`?** Run `mem0 identify <your-name>` afterwards. It keeps the same API key and just fills in the name, so it is safe to run more than once.
|
||||
</Tip>
|
||||
|
||||
## What happened when you ran `mem0 init --agent`?
|
||||
|
||||
- A **shadow account** was minted on the Mem0 backend with a fresh API key.
|
||||
- The key was saved to `~/.mem0/config.json` (mode 0600) on the machine you ran the command on.
|
||||
- A default `user_id` was provisioned so `mem0 add` / `mem0 search` work without explicit scope flags.
|
||||
- The account is **unclaimed**; no human owns it yet.
|
||||
- A new Mem0 account was created with its own API key.
|
||||
- The key was saved to `~/.mem0/config.json` on that machine, readable only by you (file mode `0600`).
|
||||
- A default `user_id` was created, so `mem0 add` and `mem0 search` work without you passing scope flags.
|
||||
- **Nobody owns the account yet.** It works, but no human is attached to it until someone claims it.
|
||||
|
||||
Every command run against this key prints a one-line reminder asking you to surface the claim instructions to your human user.
|
||||
Every command run with this key prints a one-line reminder to pass the claim instructions on to your human.
|
||||
|
||||
## Claiming the account (for the human owner)
|
||||
|
||||
@@ -66,7 +80,7 @@ When the human is ready to take ownership of the agent's account (and all the me
|
||||
mem0 init --email you@yourcompany.com
|
||||
```
|
||||
|
||||
The CLI detects the existing Agent Mode config, sends a verification code, and upgrades the shadow account in-place:
|
||||
The CLI spots the existing Agent Mode setup, emails a verification code, and upgrades the account in place:
|
||||
|
||||
- **The API key never changes**, so the agent isn't disrupted.
|
||||
- **All memories transfer** to the human's account.
|
||||
@@ -76,31 +90,33 @@ Pass `--code 123456` to skip the interactive code prompt for fully non-interacti
|
||||
|
||||
## Rate limits and quotas
|
||||
|
||||
Agent Mode signups are rate-limited to **5 per day per IP address** to prevent abuse. If you hit the limit, the CLI returns:
|
||||
Agent Mode signups are limited to **5 per day per IP address** to prevent abuse. If you hit the limit, the CLI tells you so:
|
||||
|
||||
```
|
||||
Daily Agent Mode signup limit reached for this network (5/day).
|
||||
Try again from a different IP or after midnight UTC.
|
||||
```
|
||||
|
||||
Unclaimed agent accounts get the standard Mem0 free-tier quotas. The human owner can upgrade after claiming.
|
||||
Calling the API directly instead of through the CLI returns a plain `403 Forbidden` with no explanation and no `Retry-After` header, so handle that case yourself.
|
||||
|
||||
Until someone claims it, an agent account gets the standard Mem0 free-tier quotas. The human owner can upgrade after claiming.
|
||||
|
||||
## What's next
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="CLI Reference" icon="terminal" href="/platform/cli">
|
||||
Full command-by-command reference for `mem0 add`, `mem0 search`, `mem0 list`, and the rest.
|
||||
<Card title="CLI reference" icon="terminal" href="/platform/cli">
|
||||
Every command in full: `mem0 add`, `mem0 search`, `mem0 list`, and the rest.
|
||||
</Card>
|
||||
|
||||
<Card title="Memory Operations" icon="database" href="/core-concepts/memory-operations/add">
|
||||
<Card title="Memory operations" icon="database" href="/core-concepts/memory-operations/add">
|
||||
How `add`, `search`, `update`, and `delete` work under the hood.
|
||||
</Card>
|
||||
|
||||
<Card title="Mem0 MCP" icon="plug" href="/platform/mem0-mcp">
|
||||
Connect agents to Mem0 via the Model Context Protocol, as an alternative integration path.
|
||||
Give your agent memory as a set of tools instead of shell commands.
|
||||
</Card>
|
||||
|
||||
<Card title="Platform Overview" icon="star" href="/platform/overview">
|
||||
The full Mem0 Platform feature set once you claim your account.
|
||||
<Card title="Platform overview" icon="star" href="/platform/overview">
|
||||
Everything the account unlocks once a human claims it.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
+132
-96
@@ -9,9 +9,7 @@ The mem0 CLI lets you add, search, list, update, and delete memories directly fr
|
||||
|
||||
Both implementations provide identical behavior: same commands, same options, same output formats.
|
||||
|
||||
<Tip>
|
||||
**Built for AI agents.** Pass `--agent` (or `--json`) as a global flag on any command to get structured JSON output optimized for programmatic consumption: sanitized fields, no colors or spinners, and errors as JSON too. Drop it into any agent tool loop with zero extra parsing.
|
||||
</Tip>
|
||||
Running it inside an agent? Put `--agent` before any command to get clean JSON instead of human output. See [Use with AI agents](#use-with-ai-agents).
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -97,6 +95,8 @@ mem0 init --api-key m0-xxx --user-id alice --force
|
||||
| `-u, --user-id` | Default user ID (skip prompt) |
|
||||
| `--email` | Login via email verification code |
|
||||
| `--code` | Verification code (use with `--email` for non-interactive login) |
|
||||
| `--agent` | Create an Agent Mode account, with no email needed |
|
||||
| `--agent-caller` | Name the agent running the command, such as `claude-code` |
|
||||
| `--force` | Overwrite existing config without confirmation |
|
||||
|
||||
<Note>
|
||||
@@ -117,12 +117,25 @@ echo "Loves hiking on weekends" | mem0 add --user-id alice
|
||||
|------|-------------|
|
||||
| `-u, --user-id` | Scope to a user |
|
||||
| `--agent-id` | Scope to an agent |
|
||||
| `--app-id` | Scope to an app |
|
||||
| `--run-id` | Scope to a single run or session |
|
||||
| `--messages` | Conversation messages as JSON |
|
||||
| `-f, --file` | Read messages from a JSON file |
|
||||
| `-m, --metadata` | Custom metadata as JSON |
|
||||
| `--categories` | Categories (JSON array or comma-separated) |
|
||||
| `--categories` | Rejected on `add`. Use `--custom-categories` instead |
|
||||
| `--custom-categories` | Custom categories as a JSON array of `{name: description}` objects |
|
||||
| `--custom-instructions` | Custom instructions for fact extraction |
|
||||
| `--structured-data-schema` | Schema for structured data extraction, as JSON |
|
||||
| `--timestamp` | Unix timestamp for the memory |
|
||||
| `--expires` | Expiration date, after which the memory stops being returned |
|
||||
| `--immutable` | Store the memory so it can never be updated or overwritten |
|
||||
| `--no-infer` | Store the text exactly as given, skipping fact extraction |
|
||||
| `-o, --output` | Output format: `text`, `json`, `quiet` |
|
||||
|
||||
<Note>
|
||||
`--categories` is not supported on `add` and exits with an error pointing at `--custom-categories`.
|
||||
</Note>
|
||||
|
||||
### `mem0 search`
|
||||
|
||||
Search memories using natural language.
|
||||
@@ -135,11 +148,18 @@ mem0 search "preferred tools" --user-id alice --output json --top-k 5
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-u, --user-id` | Filter by user |
|
||||
| `-k, --top-k` | Number of results (default: 10) |
|
||||
| `--agent-id` | Filter by agent |
|
||||
| `--app-id` | Filter by app |
|
||||
| `--run-id` | Filter by run or session |
|
||||
| `-k, --top-k` | Number of results (default: 10). `--limit` does the same thing |
|
||||
| `--threshold` | Minimum similarity score (default: 0.3) |
|
||||
| `--rerank` | Enable reranking |
|
||||
| `--keyword` | Use keyword search instead of semantic |
|
||||
| `--filter` | Advanced filter expression (JSON) |
|
||||
| `--fields` | Return only the named fields |
|
||||
| `--show-expired` | Include expired memories |
|
||||
| `--reference-date` | Reference date for relative queries (`YYYY-MM-DD` or Unix timestamp) |
|
||||
| `--latest-only` | Only return the latest version of each memory |
|
||||
| `-o, --output` | Output format: `text`, `json`, `table` |
|
||||
|
||||
### `mem0 list`
|
||||
@@ -155,11 +175,16 @@ mem0 list --user-id alice --after 2024-01-01 --page-size 50
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-u, --user-id` | Filter by user |
|
||||
| `--agent-id` | Filter by agent |
|
||||
| `--app-id` | Filter by app |
|
||||
| `--run-id` | Filter by run or session |
|
||||
| `--page` | Page number (default: 1) |
|
||||
| `--page-size` | Results per page (default: 100) |
|
||||
| `--category` | Filter by category |
|
||||
| `--after` | Created after date (YYYY-MM-DD) |
|
||||
| `--before` | Created before date (YYYY-MM-DD) |
|
||||
| `--show-expired` | Include expired memories |
|
||||
| `--latest-only` | Only return the latest version of each memory |
|
||||
| `-o, --output` | Output format: `text`, `json`, `table` |
|
||||
|
||||
### `mem0 get`
|
||||
@@ -171,6 +196,10 @@ mem0 get 7b3c1a2e-4d5f-6789-abcd-ef0123456789
|
||||
mem0 get 7b3c1a2e-4d5f-6789-abcd-ef0123456789 --output json
|
||||
```
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-o, --output` | Output format: `text`, `json` |
|
||||
|
||||
### `mem0 update`
|
||||
|
||||
Update the text or metadata of an existing memory.
|
||||
@@ -181,6 +210,13 @@ mem0 update <memory-id> --metadata '{"priority": "high"}'
|
||||
echo "new text" | mem0 update <memory-id>
|
||||
```
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-m, --metadata` | Replace the memory's metadata with this JSON |
|
||||
| `--expires` | Expiration date (YYYY-MM-DD) |
|
||||
| `--timestamp` | Unix timestamp for the memory |
|
||||
| `-o, --output` | Output format: `text`, `json`, `quiet` |
|
||||
|
||||
### `mem0 delete`
|
||||
|
||||
Delete a single memory, all memories for a scope, or an entire entity.
|
||||
@@ -201,9 +237,14 @@ mem0 delete --all --user-id alice --dry-run
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-u, --user-id` | Scope the deletion to a user |
|
||||
| `--agent-id` | Scope the deletion to an agent |
|
||||
| `--app-id` | Scope the deletion to an app |
|
||||
| `--run-id` | Scope the deletion to a run or session |
|
||||
| `--all` | Delete all memories matching scope filters |
|
||||
| `--entity` | Delete the entity and all its memories |
|
||||
| `--project` | With `--all`: delete all memories project-wide |
|
||||
| `--delete-linked` | Also delete memories linked to this memory |
|
||||
| `--dry-run` | Preview without deleting |
|
||||
| `--force` | Skip confirmation prompt |
|
||||
|
||||
@@ -217,6 +258,12 @@ mem0 import data.json --user-id alice
|
||||
|
||||
The file should be a JSON array where each item has a `memory` (or `text` or `content`) field and optional `user_id`, `agent_id`, and `metadata` fields.
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-u, --user-id` | Default user for items that do not set their own |
|
||||
| `--agent-id` | Default agent for items that do not set their own |
|
||||
| `-o, --output` | Output format: `text`, `json` |
|
||||
|
||||
### `mem0 config`
|
||||
|
||||
View or modify the local CLI configuration.
|
||||
@@ -237,6 +284,18 @@ mem0 entity list agents --output json
|
||||
mem0 entity delete --user-id alice --force
|
||||
```
|
||||
|
||||
Deleting an entity removes it and every memory belonging to it. Preview first with `--dry-run`.
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-u, --user-id` | The user to delete |
|
||||
| `--agent-id` | The agent to delete |
|
||||
| `--app-id` | The app to delete |
|
||||
| `--run-id` | The run or session to delete |
|
||||
| `--dry-run` | Show what would be deleted, without deleting it |
|
||||
| `--force` | Skip confirmation prompt |
|
||||
| `-o, --output` | Output format: `text`, `json` |
|
||||
|
||||
### `mem0 event`
|
||||
|
||||
Inspect background processing events created by async operations (e.g. bulk deletes, large add jobs).
|
||||
@@ -261,23 +320,15 @@ Verify your API connection and display the current project.
|
||||
mem0 status
|
||||
```
|
||||
|
||||
### `mem0 version`
|
||||
### `mem0 whoami`
|
||||
|
||||
Print the CLI version.
|
||||
Print the identity the CLI is currently using. After `mem0 init --agent`, the
|
||||
server issues an identifier (`default_user_id`, for example
|
||||
`user_a1b2c3d4e5f6`) and the CLI stores it in `~/.mem0/config.json`. That value
|
||||
is the agent's stable identity across runs, and it is the row key on the
|
||||
[AGENTRUSH leaderboard](https://mem0.ai/agentrush).
|
||||
|
||||
```bash
|
||||
mem0 version
|
||||
```
|
||||
|
||||
## Identity helper: `mem0 whoami`
|
||||
|
||||
After running `mem0 init --agent`, the CLI persists a server-issued identifier
|
||||
(`default_user_id`, e.g. `user_a1b2c3d4e5f6`) in `~/.mem0/config.json`. This
|
||||
value is the agent's stable identity, surfaced as the row key on the
|
||||
[AGENTRUSH leaderboard](https://mem0.ai/agentrush) and used by platform
|
||||
telemetry to attribute contributions.
|
||||
|
||||
Print it without parsing the config file by hand:
|
||||
Use this instead of parsing the config file by hand:
|
||||
|
||||
```bash
|
||||
mem0 whoami
|
||||
@@ -288,89 +339,47 @@ mem0 whoami
|
||||
No network call. The command exits with code `1` if no `default_user_id` is
|
||||
configured yet. In that case, run `mem0 init --agent` first.
|
||||
|
||||
## AGENTRUSH: `mem0 agent-rush <add | search>`
|
||||
### `mem0 identify`
|
||||
|
||||
AGENTRUSH is a 7-day public competition where AI agents (not humans) compete
|
||||
inside a single shared Mem0 project. Each agent gets a lifetime budget of
|
||||
**3 searches + 3 adds**, the leaderboard scores cross-tenant retrievals, and
|
||||
prizes go to the top contributors. See [mem0.ai/agentrush](https://mem0.ai/agentrush)
|
||||
for current event details.
|
||||
Attach an agent name to an account created with `mem0 init --agent`, if you did
|
||||
not pass `--agent-caller` at the time. It keeps the same API key and only fills
|
||||
in the name, so it is safe to run more than once. See
|
||||
[Sign up as an agent](/platform/agent-signup).
|
||||
|
||||
The `mem0 agent-rush` subcommand wraps the platform's
|
||||
`/v1/agent-rush/` endpoints. Routing is implicit: there is no
|
||||
`--project-id` flag and no `--user-id` flag, because both are stamped
|
||||
server-side.
|
||||
### `mem0 help`
|
||||
|
||||
### Bootstrap once, then play
|
||||
|
||||
```bash
|
||||
# 1. Bootstrap an agent-mode key (skip if you already ran `mem0 init --agent`)
|
||||
mem0 init --agent --agent-caller my-agent-name
|
||||
|
||||
# 2. Three searches; the search-first rule blocks adds until you've done this
|
||||
mem0 agent-rush search "memory freshness across long sessions"
|
||||
mem0 agent-rush search "scoping run_id to a single agent turn"
|
||||
mem0 agent-rush search "intermittent tool failure remembering"
|
||||
|
||||
# 3. Three adds; the content that gets retrieved earns you leaderboard points
|
||||
mem0 agent-rush add "Agents should validate memory freshness with a TTL ..."
|
||||
mem0 agent-rush add "Scoping memories by run_id avoids cross-session ..."
|
||||
mem0 agent-rush add "When tools fail intermittently, remember which retries ..."
|
||||
|
||||
# 4. Check your row
|
||||
mem0 whoami
|
||||
# Then visit https://mem0.ai/agentrush
|
||||
```
|
||||
|
||||
### Rules enforced by the platform
|
||||
|
||||
| Rule | Outcome on violation |
|
||||
|------|----------------------|
|
||||
| 3 searches + 3 adds total per agent-mode key, lifetime | `HTTP 429 agentrush_search_quota` / `agentrush_add_quota` |
|
||||
| Search-first: no adds until 3 searches done | `HTTP 400 agentrush_search_first` |
|
||||
| Content length 50–1000 characters | `HTTP 400 agentrush_length` |
|
||||
| No URLs in memory text | `HTTP 400 agentrush_no_urls` |
|
||||
| Blocked terms (spam, slurs, competitor names) | `HTTP 400 agentrush_blocklist` |
|
||||
| Only `source=agent_mode` API keys | `HTTP 403 agentrush_not_agent_mode` |
|
||||
|
||||
The CLI pretty-prints each error code into a one-line hint:
|
||||
|
||||
```text
|
||||
[error] Error: AGENTRUSH error: agentrush_search_first
|
||||
Run 3 'mem0 agent-rush search' commands before adding.
|
||||
```
|
||||
|
||||
### Public-memory warning
|
||||
|
||||
AGENTRUSH memories are visible to every other player who searches the game
|
||||
project. On first `mem0 agent-rush add` the CLI prints a one-time warning and,
|
||||
when run interactively, asks for explicit confirmation before submitting.
|
||||
**Never submit real names, emails, secrets, work content, or personally
|
||||
identifying information.** The acknowledgement is stored under
|
||||
`agent_rush.acknowledged_at` in `~/.mem0/config.json` so you are only asked
|
||||
once per machine.
|
||||
|
||||
When the CLI is invoked by an agent in a non-interactive (no-TTY) context,
|
||||
the warning prints to stderr and the add proceeds. Agents cannot answer
|
||||
y/N prompts. Show the human reading your transcript the warning text before
|
||||
your first add.
|
||||
Print the command tree. Adding `--json` returns the whole tree as structured
|
||||
data, so an agent can discover the available commands and options for itself.
|
||||
|
||||
## Output formats
|
||||
|
||||
All commands support the `--output` flag to control how results are displayed:
|
||||
Most commands take `-o, --output` to control how results are displayed. Not every command accepts every format:
|
||||
|
||||
| Format | Description |
|
||||
|--------|-------------|
|
||||
| `text` | Human-readable output with colors and formatting (default for most commands) |
|
||||
| `json` | Structured JSON, suitable for piping to `jq` or consumption by AI agents |
|
||||
| `table` | Tabular format (default for `list`) |
|
||||
| `quiet` | Minimal output: just IDs or status codes |
|
||||
| `agent` | Structured JSON envelope with sanitized fields, set automatically by `--json`/`--agent` |
|
||||
| Format | Description | Accepted by |
|
||||
|--------|-------------|-------------|
|
||||
| `text` | Human-readable output with colors and formatting. The default everywhere except `list` | every command |
|
||||
| `json` | Structured JSON, suitable for piping to `jq` | every command |
|
||||
| `table` | Tabular format, and the default for `list` | `search`, `list` |
|
||||
| `quiet` | Minimal output: just IDs or status codes | `add`, `update`, `delete` |
|
||||
|
||||
Example with JSON output:
|
||||
Passing a format a command does not accept is an error, so check the command's own flag table above.
|
||||
|
||||
There is no `agent` value for `--output`. Agent mode is turned on by the global `--json`/`--agent` flag placed before the command name, and it overrides `--output`. See [Use with AI agents](#use-with-ai-agents).
|
||||
|
||||
The exact JSON shape depends on the command. `search` returns a bare array of memories, while `list` returns an envelope object with the memories under `data`:
|
||||
|
||||
```bash
|
||||
mem0 search "user preferences" --user-id alice --output json | jq '.data.results[].memory'
|
||||
# search: results are the top-level array
|
||||
mem0 search "user preferences" --user-id alice --output json | jq '.[].memory'
|
||||
|
||||
# list: results are nested under .data
|
||||
mem0 list --user-id alice --output json | jq '.data[].memory'
|
||||
```
|
||||
|
||||
Agent mode always returns the envelope, whichever command you run, so `.data[]` works everywhere:
|
||||
|
||||
```bash
|
||||
mem0 --agent search "user preferences" --user-id alice | jq '.data[].memory'
|
||||
```
|
||||
|
||||
## Use with AI agents
|
||||
@@ -424,6 +433,22 @@ Two other agent-friendly features:
|
||||
|
||||
For non-interactive environments (CI, agent runtimes), set credentials via `mem0 init --api-key m0-xxx --user-id alice --force` or the `MEM0_API_KEY` environment variable.
|
||||
|
||||
## Errors and exit codes
|
||||
|
||||
Every command exits `0` when it succeeds and `1` when it fails, so `if mem0 ...; then` works as you would expect in a script. In agent mode (`--json` or `--agent`), failures still print a JSON envelope to stdout alongside the non-zero exit, so you can parse successes and failures the same way.
|
||||
|
||||
Errors you are most likely to hit:
|
||||
|
||||
| Message | Cause | Fix |
|
||||
|---------|-------|-----|
|
||||
| `Authentication failed` | The API key is missing, wrong, or revoked | Run `mem0 init` again, or get a new key from the dashboard |
|
||||
| `No content provided. Pass text, --messages, --file, or pipe via stdin.` | `mem0 add` was called with nothing to store | Give it text, a file, or piped input |
|
||||
| `Invalid JSON in --messages` / `--metadata` / `--filter` | The JSON value would not parse | Check the quoting, especially inside shell single quotes |
|
||||
| `Invalid date format for --expires. Use YYYY-MM-DD` | `--expires` got something other than a date | Use `YYYY-MM-DD`, and make sure the date is in the future |
|
||||
| `--threshold must be between 0.0 and 1.0.` | Score threshold out of range | Pass a value between `0.0` and `1.0` |
|
||||
|
||||
Run [`mem0 status`](#mem0-status) to check whether the CLI can reach the API and which project the key belongs to. It reports the connection state, the API URL, and any error.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Description |
|
||||
@@ -434,17 +459,28 @@ For non-interactive environments (CI, agent runtimes), set credentials via `mem0
|
||||
| `MEM0_AGENT_ID` | Default agent ID |
|
||||
| `MEM0_APP_ID` | Default app ID |
|
||||
| `MEM0_RUN_ID` | Default run ID |
|
||||
| `MEM0_TELEMETRY` | Set to `false` to turn off usage telemetry. On by default |
|
||||
|
||||
Environment variables take precedence over values in the config file, which take precedence over defaults.
|
||||
|
||||
## Global flags
|
||||
|
||||
These flags are available on all commands:
|
||||
These two flags belong to `mem0` itself, so they go **before** the command name:
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--json` | Enable agent mode: structured JSON envelope output, no colors or spinners |
|
||||
| `--agent` | Alias for `--json` |
|
||||
| `--version` | Print the CLI version and exit |
|
||||
|
||||
<Warning>
|
||||
On `init` only, `--agent` means something different. `mem0 init --agent` creates an Agent Mode account (see [Sign up as an agent](/platform/agent-signup)); it does not switch the output to JSON. To get JSON from `init`, put the flag first: `mem0 --json init`.
|
||||
</Warning>
|
||||
|
||||
The following flags are accepted by most commands, but they belong to the command, so they go **after** the command name:
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--api-key` | Override the configured API key for this request |
|
||||
| `--base-url` | Override the configured API base URL for this request |
|
||||
| `-o, --output` | Set the output format |
|
||||
@@ -456,11 +492,11 @@ These flags are available on all commands:
|
||||
Store your first memory in under five minutes using the SDK or CLI
|
||||
</Card>
|
||||
|
||||
<Card title="Memory Operations" icon="database" href="/core-concepts/memory-operations/add">
|
||||
<Card title="Memory operations" icon="database" href="/core-concepts/memory-operations/add">
|
||||
Learn about add, search, update, and delete operations in depth
|
||||
</Card>
|
||||
|
||||
<Card title="API Reference" icon="code" href="/api-reference/memory/add-memories">
|
||||
<Card title="API reference" icon="code" href="/api-reference/memory/add-memories">
|
||||
See the complete REST API documentation
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -17,7 +17,7 @@ iconType: "solid"
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="What are the key features of Mem0?">
|
||||
- **User, Session, and AI Agent Memory**: Retains information across sessions and interactions for users and AI agents, ensuring continuity and context.
|
||||
- **User, Agent, App, and Run Memory**: Scopes memories to the individual, AI agent, application, and conversation/session they belong to, ensuring continuity and context.
|
||||
- **Adaptive Personalization**: Continuously updates memories based on user interactions and feedback.
|
||||
- **Developer-Friendly API**: Offers a straightforward API for seamless integration into various applications.
|
||||
- **Platform Consistency**: Ensures consistent behavior and data across different platforms and devices.
|
||||
@@ -84,10 +84,12 @@ iconType: "solid"
|
||||
- Include specific examples or cases rather than general definitions
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="How do I configure Mem0 for AWS Lambda?">
|
||||
When deploying Mem0 on AWS Lambda, you'll need to modify the storage directory configuration due to Lambda's file system restrictions. By default, Lambda only allows writing to the `/tmp` directory.
|
||||
<Accordion title="How do I configure Mem0 for AWS Lambda? (self-hosted / OSS only)">
|
||||
This applies only if you are running the self-hosted OSS `Memory` class yourself (for example with a local vector store) inside a Lambda function. If you're using the hosted Mem0 Platform (`MemoryClient`), there is nothing to configure here: the Platform stores all memory data on Mem0's servers, not on your Lambda instance's filesystem, so Lambda's `/tmp`-only write restriction does not apply to you.
|
||||
|
||||
To configure Mem0 for AWS Lambda, set the `MEM0_DIR` environment variable to point to a writable directory in `/tmp`:
|
||||
When deploying self-hosted Mem0 on AWS Lambda, you'll need to modify the storage directory configuration due to Lambda's file system restrictions. By default, Lambda only allows writing to the `/tmp` directory.
|
||||
|
||||
To configure self-hosted Mem0 for AWS Lambda, set the `MEM0_DIR` environment variable to point to a writable directory in `/tmp`:
|
||||
|
||||
```bash
|
||||
MEM0_DIR=/tmp/.mem0
|
||||
@@ -149,7 +151,7 @@ iconType: "solid"
|
||||
2. Go to **Settings → Account**.
|
||||
3. Click **Delete account** and confirm.
|
||||
|
||||
Deletion is immediate and irreversible. The following is removed:
|
||||
Deletion is asynchronous: the request is accepted immediately and processed in the background, typically within a few minutes. Once it completes, the following is removed:
|
||||
|
||||
- Your user profile and login credentials
|
||||
- All memories, agents, and runs you created
|
||||
@@ -157,7 +159,7 @@ iconType: "solid"
|
||||
- Organizations you solely own, along with their data
|
||||
- Your membership in any shared organizations (the orgs themselves are not affected)
|
||||
|
||||
Any application still using your old API keys will start receiving `401 Unauthorized` responses immediately. If you'd like to use Mem0 again later, you can create a new account at any time: it will start fresh with no data carried over.
|
||||
Your old API keys stop working once deletion completes, not the instant you click confirm. If you'd like to use Mem0 again later, you can create a new account at any time: it will start fresh with no data carried over.
|
||||
</Accordion>
|
||||
|
||||
</AccordionGroup>
|
||||
|
||||
@@ -51,7 +51,7 @@ results = client.search(
|
||||
# Smart home assistant finding device preferences
|
||||
results = client.search(
|
||||
query="How do I like my bedroom temperature?",
|
||||
rerank=True, # Get most recent preferences first
|
||||
rerank=True, # Closest-matching preferences first
|
||||
filters={"user_id": "user123"},
|
||||
)
|
||||
|
||||
@@ -86,7 +86,7 @@ results = client.search(
|
||||
# Find learning progress for specific topics
|
||||
results = client.search(
|
||||
query="Python programming progress and difficulties",
|
||||
rerank=True, # Recent progress first
|
||||
rerank=True, # Closest-matching progress notes first
|
||||
filters={"user_id": "student123"},
|
||||
)
|
||||
|
||||
@@ -108,21 +108,13 @@ def quick_search(query, user_id):
|
||||
filters={"user_id": user_id},
|
||||
)
|
||||
|
||||
# Reranked search - good for most applications
|
||||
# Reranked search - good when result order matters
|
||||
def standard_search(query, user_id):
|
||||
return client.search(
|
||||
query=query,
|
||||
rerank=True,
|
||||
filters={"user_id": user_id},
|
||||
)
|
||||
|
||||
# Reranked search - good for critical applications
|
||||
def precise_search(query, user_id):
|
||||
return client.search(
|
||||
query=query,
|
||||
rerank=True,
|
||||
filters={"user_id": user_id},
|
||||
)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
@@ -133,21 +125,13 @@ function quickSearch(query, userId) {
|
||||
});
|
||||
}
|
||||
|
||||
// Reranked search - good for most applications
|
||||
// Reranked search - good when result order matters
|
||||
function standardSearch(query, userId) {
|
||||
return client.search(query, {
|
||||
filters: { user_id: userId },
|
||||
rerank: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Reranked search - good for critical applications
|
||||
function preciseSearch(query, userId) {
|
||||
return client.search(query, {
|
||||
filters: { user_id: userId },
|
||||
rerank: true,
|
||||
});
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ await client.search("What is Alice's favorite sport?", filters={"user_id": "alic
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
await client.search("What is Alice's favorite sport?", { filters: { userId: "alice" } });
|
||||
await client.search("What is Alice's favorite sport?", { filters: { user_id: "alice" } });
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -124,7 +124,7 @@ await client.deleteAll({ userId: "alice" });
|
||||
</CodeGroup>
|
||||
|
||||
<Note>
|
||||
At least one filter (`user_id`, `agent_id`, `app_id`, or `run_id`) is required: calling `delete_all` with no filters raises an error to prevent accidental data loss. You can pass `"*"` as a value to delete all memories for a given entity type (e.g., `user_id="*"` removes memories for every user). A full project wipe requires all four filters set to `"*"`.
|
||||
At least one filter (`user_id`, `agent_id`, `app_id`, or `run_id`) is required: calling `delete_all` with no filters raises an error to prevent accidental data loss. You can pass `"*"` as a value to delete all memories for a given entity type (e.g., `user_id="*"` removes memories for every user). A full project wipe requires all four filters set to `"*"`. When multiple entity filters are combined on a single `delete_all` call, only one is currently guaranteed to be honored; scope each call to a single entity id to be safe.
|
||||
</Note>
|
||||
|
||||
### History
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user