Compare commits
359 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 42cf18c4e6 | |||
| d89793b666 | |||
| 17836748d7 | |||
| c9af55986e | |||
| f69f8dcc7b | |||
| 28e4d819f8 | |||
| 1d383c4ee2 | |||
| 8488abe603 | |||
| 49863e9a7a | |||
| 770ce97bd9 | |||
| 44bcfbe1f3 | |||
| 33a0ed7559 | |||
| ba9054e0c8 | |||
| df9d5cc4b1 | |||
| 3b2357bfe0 | |||
| 573b20cec8 | |||
| a781800d3f | |||
| 4470803fe5 | |||
| 6a801bfe2f | |||
| 2a4aa232b2 | |||
| 99206f0c64 | |||
| 5dbf071356 | |||
| b26469e006 | |||
| e72ae96ad4 | |||
| fbdbab805d | |||
| 22f70d50e1 | |||
| f89edb45dc | |||
| 846f25bd39 | |||
| 9b04509433 | |||
| 803ff13bb8 | |||
| 94e46526bc | |||
| d122479687 | |||
| cc52f0e367 | |||
| 87276ef968 | |||
| 002fe46ab3 | |||
| 9d36b2c94d | |||
| c944bed460 | |||
| 2cc060fd76 | |||
| 2bc2f763d9 | |||
| 7fb3feb5cd | |||
| fec7cdf118 | |||
| 03b41ab00f | |||
| 4c974c8fa8 | |||
| b0bee551cb | |||
| b8141aaea8 | |||
| a7ecf781cd | |||
| 6dc4606dcf | |||
| 580d390e4d | |||
| 2bd3ff1eff | |||
| cd79fa8914 | |||
| 59484f066f | |||
| 8a5c0729e5 | |||
| 3b9aed866a | |||
| fb2593e10d | |||
| 207f65deda | |||
| f2532f072f | |||
| 41c8f00851 | |||
| a36a392cd3 | |||
| 152d1e66f7 | |||
| bc05fd9623 | |||
| ad7e09851c | |||
| c325bd3b8e | |||
| 2add7fd57d | |||
| b2ff3aeda5 | |||
| 754034abbc | |||
| bedf862d64 | |||
| cc59d122db | |||
| 3619fd77ae | |||
| 2dcb3542f8 | |||
| 31cec11a79 | |||
| 4c0ea22d31 | |||
| f59320df65 | |||
| ad57cbb8d6 | |||
| ee0c38e081 | |||
| d5b64ccec9 | |||
| 8d6b7c1d67 | |||
| b44ce4dcc3 | |||
| e9c930c430 | |||
| 4b39d01ccb | |||
| 49061718bd | |||
| 7e7682a06d | |||
| f38608fb50 | |||
| fb11cdffbb | |||
| ee600705c2 | |||
| f4ccef5157 | |||
| 08da741a31 | |||
| e4efdd2e29 | |||
| a87c9ce367 | |||
| d258b638ef | |||
| bbbfcfea07 | |||
| fbef369b91 | |||
| a7ed68e697 | |||
| 8a92cf0306 | |||
| 0fbbb2f525 | |||
| 818c2981b7 | |||
| 1f66aadfa3 | |||
| b91c745fbc | |||
| af70668308 | |||
| 890473f891 | |||
| d2ff83cf72 | |||
| 0e02effaf7 | |||
| 9269a0ad6e | |||
| 3d06006f36 | |||
| 6bb1d328ad | |||
| ac296f7534 | |||
| ac8f862ff7 | |||
| b33fa5427c | |||
| 5d573dd2ae | |||
| 98dbf90864 | |||
| 6ddf1669f4 | |||
| d3d2e89fd5 | |||
| 43175d85f2 | |||
| 661ecb9f0f | |||
| 09f181c577 | |||
| 3497f26a00 | |||
| e9c0547423 | |||
| 25bc1b7426 | |||
| fee344db85 | |||
| b611f69381 | |||
| c2862831db | |||
| 1678e682ee | |||
| ced4af681f | |||
| 565db27121 | |||
| c0ac9f81fa | |||
| 879c68555c | |||
| c2e723352e | |||
| 716f021df8 | |||
| 7fa996261d | |||
| 87bd2d91e0 | |||
| 15a930dac2 | |||
| fa9abc77a6 | |||
| 4e448269bc | |||
| 29d131f7aa | |||
| 42fe129330 | |||
| bd5996f41e | |||
| 299c423213 | |||
| ce0531a13e | |||
| 513b56159f | |||
| 1676b3168d | |||
| 8a786bf72d | |||
| a48f34cf77 | |||
| 650b734b1b | |||
| 871a1de7d2 | |||
| e615cc66de | |||
| ca86a164bd | |||
| 2ac3f3956a | |||
| 7a9f03af3f | |||
| 48f1d6f010 | |||
| f0ccd99924 | |||
| c5971193a2 | |||
| ff53fd60b7 | |||
| ffa334537a | |||
| bd7ce2c13c | |||
| 5d767219ff | |||
| 6b744845c3 | |||
| 5b4478458b | |||
| 1751e7bff9 | |||
| 7ae6a8c36a | |||
| 1dcee153b9 | |||
| 4065e846f6 | |||
| 466249113c | |||
| 3e2ae734e7 | |||
| 96b31c4bc0 | |||
| 0117d5838b | |||
| 42a3b4043c | |||
| 158e9111cb | |||
| 9ed1983b85 | |||
| 703e8a035d | |||
| 7ed2faab84 | |||
| 0d66d3d127 | |||
| 137b7519f7 | |||
| e34f5835bd | |||
| f122eb7c65 | |||
| a5123b8a5e | |||
| 6aa9bffa55 | |||
| d772f9a961 | |||
| 7c841a2bce | |||
| 6a6dfb4935 | |||
| 8b370def80 | |||
| d46464282c | |||
| bb4a239cb1 | |||
| e30f0d91fe | |||
| 9f34e858c7 | |||
| 94bbc13de0 | |||
| a2f01a8fcc | |||
| 30d172e826 | |||
| bb69b036b5 | |||
| b55c51e004 | |||
| 4492e75d04 | |||
| 4d949022f2 | |||
| 3ef034a9e4 | |||
| b90e3c0b76 | |||
| a8eeddde64 | |||
| 09a9e34382 | |||
| 66c4394b40 | |||
| 32575a65fc | |||
| 66901d7393 | |||
| a1eefc31bc | |||
| de471799d1 | |||
| 3951ad4705 | |||
| 9315e3036f | |||
| b3ede5b7c0 | |||
| 3553fc79dd | |||
| f322cf82b9 | |||
| 73c975ba68 | |||
| 931d579ba5 | |||
| f4773a0baf | |||
| 06d33f6cc4 | |||
| 8f3b60f3e1 | |||
| a6e27dcc9c | |||
| 4f10c986b5 | |||
| 821152bd14 | |||
| f48b133101 | |||
| 1d56f85705 | |||
| ced852033b | |||
| b9ad8fa8b2 | |||
| e3f5ce7b41 | |||
| f681889b14 | |||
| b5ec46be5b | |||
| 168ad358d5 | |||
| 2c796d144f | |||
| c676c2c458 | |||
| b36847622d | |||
| 32c8849044 | |||
| 2dd2872c08 | |||
| cf268da19d | |||
| 7a5df64746 | |||
| 4c41f6deeb | |||
| f84aa1eb31 | |||
| 9226ee2229 | |||
| 8399b088a5 | |||
| 437f0b5495 | |||
| 0ffaffa88c | |||
| 433ff494f1 | |||
| de03c52ed3 | |||
| b4a50e3dc8 | |||
| b819d95d18 | |||
| 3ac1c9452c | |||
| d6347f6660 | |||
| e769502baa | |||
| 652193d599 | |||
| a86c87236d | |||
| 2274b5acad | |||
| 9b0705c345 | |||
| d31fa168eb | |||
| f32eb4406b | |||
| 366945965d | |||
| 6702fa3e3e | |||
| a44855af9e | |||
| d817aa9c12 | |||
| 7ac8ab154b | |||
| b00a1a1065 | |||
| 2e90ed4f78 | |||
| 069ea0887c | |||
| ae7f406265 | |||
| 90f2d24e83 | |||
| 64b9646e7d | |||
| 866888df41 | |||
| 95b6f95f7b | |||
| 74771b4e76 | |||
| 8e65ce915d | |||
| a3154d59e5 | |||
| 1019f0e17c | |||
| 9328c36a46 | |||
| eb4afc6ef7 | |||
| fea748d7e6 | |||
| e83297f150 | |||
| eaca45dcdb | |||
| add6aad40b | |||
| 116c439b1d | |||
| 49b7953c44 | |||
| 3e6ab39429 | |||
| 75a37ec93d | |||
| 88934304c6 | |||
| 098a599579 | |||
| 5a2201d76b | |||
| ad736d9a06 | |||
| 7f6d46050e | |||
| f9c52baf21 | |||
| 0da3359a1a | |||
| 6b9707fee9 | |||
| 99beb007ab | |||
| 16a7702d09 | |||
| 53a3998873 | |||
| 08aa143db3 | |||
| ac141fdafe | |||
| b1188d6044 | |||
| 0d61af60c2 | |||
| 58696e4bd4 | |||
| 8b11e0787a | |||
| 09dc74d61a | |||
| 606ede7c0a | |||
| edd1b3e2f2 | |||
| 74d043731b | |||
| 843ab82905 | |||
| 79793b0d2e | |||
| 5f7ace2aef | |||
| 219b1a6f3d | |||
| 57c8468ce6 | |||
| ddee5f8671 |
@@ -8,7 +8,7 @@
|
||||
"name": "mem0",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./mem0-plugin"
|
||||
"path": "./integrations/mem0-plugin"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
"plugins": [
|
||||
{
|
||||
"name": "mem0",
|
||||
"source": "./mem0-plugin",
|
||||
"source": "./integrations/mem0-plugin",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.",
|
||||
"version": "0.1.3"
|
||||
"version": "0.2.13"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "mem0-plugins",
|
||||
"interface": {
|
||||
"displayName": "Mem0 Plugins"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "mem0",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./integrations/mem0-plugin"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "Productivity"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -10,9 +10,9 @@
|
||||
"plugins": [
|
||||
{
|
||||
"name": "mem0",
|
||||
"source": "./mem0-plugin",
|
||||
"source": "./integrations/mem0-plugin",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.",
|
||||
"version": "0.1.1"
|
||||
"version": "0.2.13"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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.`);
|
||||
@@ -1,18 +1,33 @@
|
||||
name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged v* is
|
||||
# published. Can also be dispatched manually to re-publish a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. v1.2.3)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Unused for PyPI (pre-releases are expressed in the version itself); accepted for router uniformity'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
|
||||
if: startsWith(github.event.release.tag_name, 'v')
|
||||
# Pure SDK version tags only (v1.2.3) — excludes package-prefixed tags
|
||||
# like vercel-ai-v* that also start with 'v'
|
||||
if: startsWith(inputs.tag, 'v') && !contains(inputs.tag, '-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
@@ -39,7 +54,6 @@ jobs:
|
||||
# packages_dir: dist/
|
||||
|
||||
- name: Publish distribution 📦 to PyPI
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
packages_dir: dist/
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
name: CI Gate
|
||||
|
||||
# Single required status check for all PRs.
|
||||
#
|
||||
# Path-filtered CI workflows can't be marked as required in branch
|
||||
# protection: on a PR that doesn't touch their paths they never report, and
|
||||
# the required check hangs at "Expected" forever. This gate solves that. It
|
||||
# runs on every PR, detects which packages changed, calls only the relevant
|
||||
# package CI workflows (as reusable workflows), and the final "CI Gate" job
|
||||
# reports the aggregate result — success when every invoked pipeline passed
|
||||
# (skipped pipelines are fine), failure when any failed.
|
||||
#
|
||||
# Branch protection should require exactly one status check: "CI Gate".
|
||||
#
|
||||
# Package CI workflows keep their own push-to-main and workflow_dispatch
|
||||
# triggers; only their pull_request triggers moved here. To wire in a new
|
||||
# package: add a filter under the `changes` job, a call job that `uses:` the
|
||||
# package workflow, and list the call job in the gate's `needs`.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ci-gate-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
name: Detect changed packages
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
python_sdk: ${{ steps.filter.outputs.python_sdk }}
|
||||
ts_sdk: ${{ steps.filter.outputs.ts_sdk }}
|
||||
cli_python: ${{ steps.filter.outputs.cli_python }}
|
||||
cli_node: ${{ steps.filter.outputs.cli_node }}
|
||||
openclaw: ${{ steps.filter.outputs.openclaw }}
|
||||
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
|
||||
id: filter
|
||||
with:
|
||||
# Each filter mirrors the package workflow's old pull_request
|
||||
# paths, plus the package workflow file itself and this gate file
|
||||
# (changing either must re-exercise the pipeline).
|
||||
filters: |
|
||||
python_sdk:
|
||||
- 'mem0/**'
|
||||
- 'tests/**'
|
||||
- 'pyproject.toml'
|
||||
- '.github/workflows/ci.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
ts_sdk:
|
||||
- 'mem0-ts/**'
|
||||
- '.github/workflows/ts-sdk-ci.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
cli_python:
|
||||
- 'cli/python/**'
|
||||
- '.github/workflows/cli-python-ci.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
cli_node:
|
||||
- 'cli/node/**'
|
||||
- '.github/workflows/cli-node-ci.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
openclaw:
|
||||
- 'integrations/openclaw/**'
|
||||
- '.github/workflows/openclaw-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
opencode_plugin:
|
||||
- 'integrations/mem0-plugin/.opencode-plugin/**'
|
||||
- '.github/workflows/opencode-plugin-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
pi_agent_plugin:
|
||||
- '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'
|
||||
- 'scripts/check-llms-txt-coverage.py'
|
||||
- 'scripts/llms-txt-ignore.txt'
|
||||
- '.github/workflows/docs-llms-txt-check.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
|
||||
python-sdk:
|
||||
name: Python SDK
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python_sdk == 'true'
|
||||
uses: ./.github/workflows/ci.yml
|
||||
secrets: inherit
|
||||
|
||||
ts-sdk:
|
||||
name: TypeScript SDK
|
||||
needs: changes
|
||||
if: needs.changes.outputs.ts_sdk == 'true'
|
||||
uses: ./.github/workflows/ts-sdk-ci.yml
|
||||
secrets: inherit
|
||||
|
||||
cli-python:
|
||||
name: Python CLI
|
||||
needs: changes
|
||||
if: needs.changes.outputs.cli_python == 'true'
|
||||
uses: ./.github/workflows/cli-python-ci.yml
|
||||
secrets: inherit
|
||||
|
||||
cli-node:
|
||||
name: Node CLI
|
||||
needs: changes
|
||||
if: needs.changes.outputs.cli_node == 'true'
|
||||
uses: ./.github/workflows/cli-node-ci.yml
|
||||
secrets: inherit
|
||||
|
||||
openclaw:
|
||||
name: OpenClaw
|
||||
needs: changes
|
||||
if: needs.changes.outputs.openclaw == 'true'
|
||||
uses: ./.github/workflows/openclaw-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
opencode-plugin:
|
||||
name: OpenCode Plugin
|
||||
needs: changes
|
||||
if: needs.changes.outputs.opencode_plugin == 'true'
|
||||
uses: ./.github/workflows/opencode-plugin-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
pi-agent-plugin:
|
||||
name: Pi Agent Plugin
|
||||
needs: changes
|
||||
if: needs.changes.outputs.pi_agent_plugin == 'true'
|
||||
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
|
||||
if: needs.changes.outputs.docs_llms_txt == 'true'
|
||||
uses: ./.github/workflows/docs-llms-txt-check.yml
|
||||
secrets: inherit
|
||||
|
||||
gate:
|
||||
name: CI Gate
|
||||
needs:
|
||||
- changes
|
||||
- python-sdk
|
||||
- ts-sdk
|
||||
- cli-python
|
||||
- cli-node
|
||||
- openclaw
|
||||
- opencode-plugin
|
||||
- pi-agent-plugin
|
||||
- n8n-nodes-mem0
|
||||
- zapier-mem0
|
||||
- docs-llms-txt
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Evaluate pipeline results
|
||||
env:
|
||||
NEEDS: ${{ toJSON(needs) }}
|
||||
run: |
|
||||
echo "$NEEDS" | jq -r 'to_entries[] | "\(.key): \(.value.result)"'
|
||||
failed=$(echo "$NEEDS" | jq -r '[to_entries[] | select(.value.result == "failure" or .value.result == "cancelled") | .key] | join(", ")')
|
||||
if [ -n "$failed" ]; then
|
||||
echo "::error::Failing pipelines: $failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "All pipelines relevant to this change passed."
|
||||
+19
-60
@@ -1,20 +1,11 @@
|
||||
name: ci
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main runs remain standalone.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'mem0/**'
|
||||
- 'tests/**'
|
||||
- 'embedchain/**'
|
||||
- '.github/workflows/**'
|
||||
- 'pyproject.toml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'mem0/**'
|
||||
- 'tests/**'
|
||||
- 'embedchain/**'
|
||||
- 'pyproject.toml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
changelog_check:
|
||||
@@ -60,9 +51,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
mem0_changed: ${{ steps.filter.outputs.mem0 }}
|
||||
embedchain_changed: ${{ steps.filter.outputs.embedchain }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
@@ -70,25 +60,28 @@ jobs:
|
||||
mem0:
|
||||
- 'mem0/**'
|
||||
- 'tests/**'
|
||||
- '.github/workflows/**'
|
||||
- '.github/workflows/ci.yml'
|
||||
- 'pyproject.toml'
|
||||
embedchain:
|
||||
- 'embedchain/**'
|
||||
|
||||
build_mem0:
|
||||
needs: check_changes
|
||||
if: needs.check_changes.outputs.mem0_changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Skip — no relevant changes
|
||||
if: needs.check_changes.outputs.mem0_changed != 'true'
|
||||
run: echo "No changes in mem0/, tests/, pyproject.toml, or ci.yml — skipping"
|
||||
- uses: actions/checkout@v4
|
||||
if: needs.check_changes.outputs.mem0_changed == 'true'
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
if: needs.check_changes.outputs.mem0_changed == 'true'
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Clean up disk space
|
||||
if: needs.check_changes.outputs.mem0_changed == 'true'
|
||||
run: |
|
||||
df -h
|
||||
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
|
||||
@@ -96,61 +89,27 @@ jobs:
|
||||
sudo docker builder prune -a
|
||||
df -h
|
||||
- name: Install Hatch
|
||||
if: needs.check_changes.outputs.mem0_changed == 'true'
|
||||
run: pip install hatch
|
||||
- name: Load cached venv
|
||||
if: needs.check_changes.outputs.mem0_changed == 'true'
|
||||
id: cached-hatch-dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .venv
|
||||
key: venv-mem0-${{ runner.os }}-${{ hashFiles('**/pyproject.toml') }}
|
||||
- name: Install GEOS Libraries
|
||||
if: needs.check_changes.outputs.mem0_changed == 'true'
|
||||
run: sudo apt-get update && sudo apt-get install -y libgeos-dev
|
||||
- name: Install dependencies
|
||||
if: needs.check_changes.outputs.mem0_changed == 'true' && steps.cached-hatch-dependencies.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
pip install --upgrade pip
|
||||
pip install -e ".[test,graph,vector_stores,llms,extras]"
|
||||
pip install ruff
|
||||
if: steps.cached-hatch-dependencies.outputs.cache-hit != 'true'
|
||||
pip install ruff==0.16.0
|
||||
- name: Run Linting
|
||||
if: needs.check_changes.outputs.mem0_changed == 'true'
|
||||
run: make lint
|
||||
- name: Run tests and generate coverage report
|
||||
if: needs.check_changes.outputs.mem0_changed == 'true'
|
||||
run: make test
|
||||
|
||||
build_embedchain:
|
||||
needs: check_changes
|
||||
if: needs.check_changes.outputs.embedchain_changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install Hatch
|
||||
run: pip install hatch
|
||||
- name: Load cached venv
|
||||
id: cached-hatch-dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .venv
|
||||
key: venv-embedchain-${{ runner.os }}-${{ hashFiles('**/pyproject.toml') }}
|
||||
- name: Install dependencies
|
||||
run: cd embedchain && make install_all
|
||||
if: steps.cached-hatch-dependencies.outputs.cache-hit != 'true'
|
||||
- name: Run Formatting
|
||||
run: |
|
||||
mkdir -p embedchain/.ruff_cache && chmod -R 777 embedchain/.ruff_cache
|
||||
cd embedchain && hatch run format
|
||||
- name: Lint with ruff
|
||||
run: cd embedchain && make lint
|
||||
- name: Run tests and generate coverage report
|
||||
run: cd embedchain && make coverage
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: coverage.xml
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
name: Publish @mem0/cli 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# cli-node-v* is published. Can also be dispatched manually to re-publish
|
||||
# a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. cli-node-v0.2.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 @mem0/cli 📦 to npm
|
||||
if: startsWith(github.event.release.tag_name, 'cli-node-v')
|
||||
if: startsWith(inputs.tag, 'cli-node-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -16,6 +28,8 @@ jobs:
|
||||
working-directory: cli/node
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -38,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
|
||||
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
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
name: CLI Node CI
|
||||
|
||||
# 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:
|
||||
@@ -7,10 +9,7 @@ on:
|
||||
paths:
|
||||
- 'cli/node/**'
|
||||
- '.github/workflows/cli-node-ci.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'cli/node/**'
|
||||
- '.github/workflows/cli-node-ci.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
name: Publish mem0-cli 🐍 distributions 📦 to PyPI
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged cli-v* is
|
||||
# published. Can also be dispatched manually to re-publish a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. cli-v0.2.0)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Unused for PyPI (pre-releases are expressed in the version itself); accepted for router uniformity'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish mem0-cli 📦 to PyPI
|
||||
if: startsWith(github.event.release.tag_name, 'cli-v')
|
||||
if: startsWith(inputs.tag, 'cli-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -16,6 +27,8 @@ jobs:
|
||||
working-directory: cli/python
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
name: CLI Python CI
|
||||
|
||||
# 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:
|
||||
@@ -7,10 +9,7 @@ on:
|
||||
paths:
|
||||
- 'cli/python/**'
|
||||
- '.github/workflows/cli-python-ci.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'cli/python/**'
|
||||
- '.github/workflows/cli-python-ci.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
||||
@@ -6,13 +6,10 @@ name: docs - llms.txt check
|
||||
# python scripts/check-llms-txt-coverage.py # read-only
|
||||
# python scripts/check-llms-txt-coverage.py --write # scaffold placeholders
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# manual runs remain standalone.
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docs/**/*.mdx'
|
||||
- 'docs/llms.txt'
|
||||
- 'scripts/check-llms-txt-coverage.py'
|
||||
- 'scripts/llms-txt-ignore.txt'
|
||||
workflow_call:
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
|
||||
@@ -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,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)
|
||||
@@ -1,21 +1,35 @@
|
||||
name: Publish @mem0/openclaw-mem0 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# openclaw-v* is published. Can also be dispatched manually to re-publish
|
||||
# a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. openclaw-v0.5.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 @mem0/openclaw-mem0 📦 to npm
|
||||
if: startsWith(github.event.release.tag_name, 'openclaw-v')
|
||||
if: startsWith(inputs.tag, 'openclaw-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: openclaw
|
||||
working-directory: integrations/openclaw
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -28,7 +42,7 @@ jobs:
|
||||
node-version: '22'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: openclaw/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/openclaw/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -38,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
|
||||
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
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
name: openclaw 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:
|
||||
- 'openclaw/**'
|
||||
- '.github/workflows/openclaw-checks.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'openclaw/**'
|
||||
- 'integrations/openclaw/**'
|
||||
- '.github/workflows/openclaw-checks.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@@ -28,13 +27,13 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: openclaw/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/openclaw/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd openclaw && pnpm install --frozen-lockfile
|
||||
run: cd integrations/openclaw && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Type check
|
||||
run: cd openclaw && pnpm exec tsc --noEmit
|
||||
run: cd integrations/openclaw && pnpm exec tsc --noEmit
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -54,20 +53,20 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: openclaw/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/openclaw/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd openclaw && pnpm install --frozen-lockfile
|
||||
run: cd integrations/openclaw && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: cd openclaw && pnpm exec vitest run --coverage
|
||||
run: cd integrations/openclaw && pnpm exec vitest run --coverage
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
if: matrix.node-version == 20
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
flags: openclaw
|
||||
directory: openclaw/coverage
|
||||
directory: integrations/openclaw/coverage
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -86,15 +85,15 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: openclaw/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/openclaw/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd openclaw && pnpm install --frozen-lockfile
|
||||
run: cd integrations/openclaw && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: cd openclaw && pnpm build
|
||||
run: cd integrations/openclaw && pnpm build
|
||||
|
||||
- name: Verify dist output exists
|
||||
run: |
|
||||
test -f openclaw/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
|
||||
test -f openclaw/dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)
|
||||
test -f integrations/openclaw/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
|
||||
test -f integrations/openclaw/dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
name: Publish @mem0/opencode-plugin 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# opencode-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. opencode-v0.2.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 @mem0/opencode-plugin 📦 to npm
|
||||
if: startsWith(inputs.tag, 'opencode-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: integrations/mem0-plugin/.opencode-plugin
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Install Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: bun 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,39 @@
|
||||
name: opencode-plugin 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/mem0-plugin/.opencode-plugin/**'
|
||||
- '.github/workflows/opencode-plugin-checks.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: integrations/mem0-plugin/.opencode-plugin
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Type check
|
||||
run: bun run type-check
|
||||
|
||||
- name: Build
|
||||
run: bun run build
|
||||
|
||||
- name: Verify dist output exists
|
||||
run: |
|
||||
test -f dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
|
||||
@@ -0,0 +1,60 @@
|
||||
name: Publish @mem0/pi-agent-plugin 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# pi-agent-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. pi-agent-v0.1.1)'
|
||||
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 @mem0/pi-agent-plugin 📦 to npm
|
||||
if: startsWith(inputs.tag, 'pi-agent-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: integrations/pi-agent-plugin
|
||||
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: '22'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/pi-agent-plugin/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm 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,92 @@
|
||||
name: pi-agent-plugin 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/pi-agent-plugin/**'
|
||||
- '.github/workflows/pi-agent-plugin-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/pi-agent-plugin/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/pi-agent-plugin && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Type check
|
||||
run: cd integrations/pi-agent-plugin && pnpm exec tsc --noEmit
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20, 22]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/pi-agent-plugin/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/pi-agent-plugin && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run tests
|
||||
run: cd integrations/pi-agent-plugin && pnpm exec vitest run
|
||||
|
||||
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/pi-agent-plugin/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/pi-agent-plugin && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: cd integrations/pi-agent-plugin && pnpm build
|
||||
|
||||
- name: Verify dist output exists
|
||||
run: |
|
||||
test -f integrations/pi-agent-plugin/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
|
||||
test -f integrations/pi-agent-plugin/dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)
|
||||
test -f integrations/pi-agent-plugin/dist/entry.js || (echo "Build output missing: dist/entry.js" && exit 1)
|
||||
test -f integrations/pi-agent-plugin/dist/entry.d.ts || (echo "Build output missing: dist/entry.d.ts" && 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],
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
name: Release Router 🚦
|
||||
|
||||
# Single entry point for all release publishing.
|
||||
#
|
||||
# Package CD workflows no longer listen to release events themselves — this
|
||||
# router inspects the release tag and dispatches only the matching pipeline,
|
||||
# so each release produces one routed run instead of one real run plus seven
|
||||
# skipped ones.
|
||||
#
|
||||
# Re-publishing a release (e.g. after fixing registry settings) does NOT
|
||||
# require deleting and recreating it anymore — manually dispatch the
|
||||
# package's CD workflow from the tag instead:
|
||||
#
|
||||
# gh workflow run <package>-cd.yml --ref refs/tags/<tag> -f tag=<tag>
|
||||
#
|
||||
# Note: dispatching runs the workflow file as it exists at the given ref, so
|
||||
# this router can only dispatch tags created after the workflow_dispatch
|
||||
# conversion landed on main. For older tags, dispatch manually from main.
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
route:
|
||||
name: Route ${{ github.event.release.tag_name }} to its CD pipeline
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Match tag prefix to CD workflow
|
||||
id: match
|
||||
env:
|
||||
TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
# Specific package prefixes first; the bare v* (Python SDK) arm
|
||||
# must stay last so prefixed tags that also start with 'v'
|
||||
# (vercel-ai-v*) can never be routed to the Python pipeline.
|
||||
case "$TAG" in
|
||||
ts-v*) workflow="ts-sdk-cd.yml" ;;
|
||||
cli-node-v*) workflow="cli-node-cd.yml" ;;
|
||||
cli-v*) workflow="cli-python-cd.yml" ;;
|
||||
vercel-ai-v*) workflow="vercel-ai-cd.yml" ;;
|
||||
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."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo "workflow=$workflow" >> "$GITHUB_OUTPUT"
|
||||
echo ":outbox_tray: Routed \`$TAG\` → \`$workflow\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Dispatch ${{ steps.match.outputs.workflow }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
# --ref points at the tag so the dispatched run builds (and signs
|
||||
# provenance for) the exact tagged commit.
|
||||
gh workflow run "${{ steps.match.outputs.workflow }}" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--ref "refs/tags/$TAG" \
|
||||
-f tag="$TAG" \
|
||||
-f prerelease="${{ github.event.release.prerelease }}"
|
||||
@@ -1,13 +1,24 @@
|
||||
name: Publish mem0ai 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged ts-v* is
|
||||
# published. Can also be dispatched manually to re-publish a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. ts-v2.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 mem0ai 📦 to npm
|
||||
if: startsWith(github.event.release.tag_name, 'ts-v')
|
||||
if: startsWith(inputs.tag, 'ts-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -16,6 +27,8 @@ jobs:
|
||||
working-directory: mem0-ts
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -38,7 +51,7 @@ jobs:
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
|
||||
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
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
name: TypeScript SDK CI
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main runs remain standalone.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'mem0-ts/**'
|
||||
- '.github/workflows/ts-sdk-ci.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'mem0-ts/**'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
check_changes:
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
name: Publish @mem0/vercel-ai-provider 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# vercel-ai-v* is published. Can also be dispatched manually to re-publish
|
||||
# a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. vercel-ai-v2.0.7)'
|
||||
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 @mem0/vercel-ai-provider 📦 to npm
|
||||
if: startsWith(github.event.release.tag_name, 'vercel-ai-v')
|
||||
if: startsWith(inputs.tag, 'vercel-ai-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: vercel-ai-sdk
|
||||
working-directory: integrations/vercel-ai-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -28,7 +42,7 @@ jobs:
|
||||
node-version: '22'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: vercel-ai-sdk/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/vercel-ai-sdk/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -38,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
|
||||
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
|
||||
|
||||
@@ -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
|
||||
+2
-1
@@ -170,7 +170,6 @@ cython_debug/
|
||||
# Database
|
||||
db
|
||||
test-db
|
||||
!embedchain/embedchain/core/db/
|
||||
|
||||
.vscode
|
||||
.idea/
|
||||
@@ -190,3 +189,5 @@ eval/
|
||||
qdrant_storage/
|
||||
.crossnote
|
||||
testing.ipynb
|
||||
.weave/
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[submodule "evaluation"]
|
||||
path = evaluation
|
||||
url = https://github.com/mem0ai/memory-benchmarks
|
||||
branch = main
|
||||
@@ -12,7 +12,7 @@ This file provides context for AI coding assistants (Claude Code, Cursor, GitHub
|
||||
|
||||
## Repository Structure
|
||||
|
||||
This is a **polyglot monorepo** containing Python and TypeScript packages, CLIs, servers, plugins, documentation, and evaluation tooling.
|
||||
This is a **polyglot monorepo** containing Python and TypeScript packages, CLIs, servers, plugins, and documentation.
|
||||
|
||||
### Key Directories
|
||||
|
||||
@@ -22,18 +22,19 @@ This is a **polyglot monorepo** containing Python and TypeScript packages, CLIs,
|
||||
| `mem0-ts/` | TypeScript SDK (`mem0ai` on npm) — client + OSS memory |
|
||||
| `cli/python/` | Python CLI (`mem0-cli` on PyPI) — Typer-based, entry point `mem0` |
|
||||
| `cli/node/` | Node CLI (`@mem0/cli` on npm) — Commander-based, entry point `mem0` |
|
||||
| `vercel-ai-sdk/` | `@mem0/vercel-ai-provider` — Vercel AI SDK memory provider |
|
||||
| `openclaw/` | `@mem0/openclaw-mem0` — OpenClaw plugin for Claude Code / AI editors |
|
||||
| `integrations/` | **Agent & editor integrations**, one directory per integration (see "Adding a New Integration") |
|
||||
| `integrations/mem0-plugin/` | AI editor plugins (Claude Code, Cursor, Codex) — MCP server connection, lifecycle hooks, skills. Contains nested `.opencode-plugin/` (`@mem0/opencode-plugin`) |
|
||||
| `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) |
|
||||
| `mem0-plugin/` | AI editor plugins (Claude Code, Cursor, Codex) — MCP server connection, lifecycle hooks, skills |
|
||||
| `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/` |
|
||||
| `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) |
|
||||
| `evaluation/` | Benchmarking framework — LOCOMO evals, experiment runner, score generation |
|
||||
| `examples/` | Sample projects — demo apps, Chrome extension, multi-agent patterns |
|
||||
| `cookbooks/` | Jupyter notebooks — customer support chatbot, AutoGen integration |
|
||||
| `embedchain/` | Legacy Embedchain RAG framework (maintained separately, Poetry-based) |
|
||||
| `evaluation/` | Submodule → [`mem0ai/memory-benchmarks`](https://github.com/mem0ai/memory-benchmarks) — benchmarking (LOCOMO, LongMemEval, BEAM) lives in that repo |
|
||||
| `examples/` | Sample projects & runnable demos — apps, Chrome extension, multi-agent patterns, and Jupyter notebooks (`notebooks/`) |
|
||||
| `pr-reviews/` | Pull request review materials |
|
||||
| `scripts/` | Repo-wide utility scripts (e.g., `check-llms-txt-coverage.py` for docs/llms.txt sync) |
|
||||
|
||||
@@ -50,8 +51,8 @@ mem0 (Python SDK) mem0-ts (TypeScript SDK)
|
||||
|
||||
cli/python/ ──▶ mem0ai (optional, for OSS mode)
|
||||
cli/node/ ──▶ mem0ai (npm, for API calls)
|
||||
vercel-ai-sdk/ ──▶ ai, @ai-sdk/* providers
|
||||
openclaw/ ──▶ mem0ai (npm)
|
||||
integrations/vercel-ai-sdk/ ──▶ ai, @ai-sdk/* providers
|
||||
integrations/openclaw/ ──▶ mem0ai (npm)
|
||||
```
|
||||
|
||||
## Development Setup
|
||||
@@ -62,7 +63,7 @@ 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
|
||||
|
||||
@@ -74,8 +75,8 @@ pre-commit install # install git hooks
|
||||
# TypeScript packages
|
||||
cd mem0-ts && pnpm install # TS SDK
|
||||
cd cli/node && pnpm install # Node CLI
|
||||
cd vercel-ai-sdk && pnpm install # Vercel AI provider
|
||||
cd openclaw && pnpm install # OpenClaw plugin
|
||||
cd integrations/vercel-ai-sdk && pnpm install # Vercel AI provider
|
||||
cd integrations/openclaw && pnpm install # OpenClaw plugin
|
||||
```
|
||||
|
||||
## Build, Lint, and Test Commands
|
||||
@@ -163,10 +164,10 @@ pnpm run dev # tsx src/index.ts (development)
|
||||
- **Test:** vitest (not jest)
|
||||
- **Framework:** Commander + Chalk + ora + cli-table3
|
||||
|
||||
### Vercel AI SDK Provider (`vercel-ai-sdk/`)
|
||||
### Vercel AI SDK Provider (`integrations/vercel-ai-sdk/`)
|
||||
|
||||
```bash
|
||||
cd vercel-ai-sdk
|
||||
cd integrations/vercel-ai-sdk
|
||||
pnpm install
|
||||
pnpm run build # tsup
|
||||
pnpm run lint # eslint
|
||||
@@ -181,10 +182,10 @@ pnpm run test:node # vitest (node runtime)
|
||||
- **Lint:** ESLint + Prettier
|
||||
- **Test:** jest + vitest (edge/node configs)
|
||||
|
||||
### OpenClaw Plugin (`openclaw/`)
|
||||
### OpenClaw Plugin (`integrations/openclaw/`)
|
||||
|
||||
```bash
|
||||
cd openclaw
|
||||
cd integrations/openclaw
|
||||
pnpm install
|
||||
pnpm run build # tsup
|
||||
pnpm run test # vitest run
|
||||
@@ -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
|
||||
@@ -246,18 +225,19 @@ make docs # or: cd docs && mintlify dev
|
||||
- **API spec:** `docs/openapi.json`
|
||||
- **Structure:** `api-reference/`, `open-source/`, `platform/`, `integrations/`, `cookbooks/`, `core-concepts/`
|
||||
|
||||
### Evaluation (`evaluation/`)
|
||||
### Evaluation / Benchmarking
|
||||
|
||||
Benchmarking lives in the external [`mem0ai/memory-benchmarks`](https://github.com/mem0ai/memory-benchmarks) repo (LOCOMO + LongMemEval + BEAM). The in-repo `evaluation/` path is a **git submodule** pinned to that repo's `main` — populate it with `git submodule update --init evaluation` (or clone mem0 with `--recurse-submodules`), or clone the benchmarks repo standalone:
|
||||
|
||||
```bash
|
||||
cd evaluation
|
||||
make run-mem0-add # Run mem0 add experiments
|
||||
make run-mem0-search # Run mem0 search experiments
|
||||
make run-mem0-plus-add # With graph memory
|
||||
make run-mem0-plus-search # With graph memory
|
||||
make run-rag # RAG baseline
|
||||
make run-full-context # Full context baseline
|
||||
make run-langmem # LangMem comparison
|
||||
make run-openai # OpenAI comparison
|
||||
git clone https://github.com/mem0ai/memory-benchmarks.git
|
||||
cd memory-benchmarks
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Run a benchmark (Mem0 Cloud; use docker compose for OSS)
|
||||
python -m benchmarks.locomo.run --project-name my-test --backend cloud --mem0-api-key $MEM0_API_KEY
|
||||
python -m benchmarks.longmemeval.run --project-name my-test --backend cloud --mem0-api-key $MEM0_API_KEY --all-questions
|
||||
python -m benchmarks.beam.run --project-name my-test --backend cloud --mem0-api-key $MEM0_API_KEY --chat-sizes 100K --conversations 0-9
|
||||
```
|
||||
|
||||
## Core APIs
|
||||
@@ -330,7 +310,6 @@ make run-openai # OpenAI comparison
|
||||
- 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 `embedchain/` and `openmemory/` from root config.
|
||||
|
||||
### TypeScript Conventions
|
||||
|
||||
@@ -343,8 +322,8 @@ make run-openai # OpenAI comparison
|
||||
|---------|--------|-----------|---------------|
|
||||
| `mem0-ts/` | — | Prettier | jest |
|
||||
| `cli/node/` | Biome | Biome | vitest |
|
||||
| `vercel-ai-sdk/` | ESLint | Prettier | jest + vitest |
|
||||
| `openclaw/` | — | — | vitest |
|
||||
| `integrations/vercel-ai-sdk/` | ESLint | Prettier | jest + vitest |
|
||||
| `integrations/openclaw/` | — | — | vitest |
|
||||
|
||||
### Type Checking
|
||||
|
||||
@@ -381,15 +360,14 @@ 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 `mem0-plugin/` — 9 tools: `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`
|
||||
- **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
|
||||
|
||||
- `mem0-plugin/` provides integrations for Claude Code, Cursor, and Codex via MCP server connections and lifecycle hooks for automatic memory capture.
|
||||
- `integrations/mem0-plugin/` provides integrations for Claude Code, Cursor, and Codex via MCP server connections and lifecycle hooks for automatic memory capture.
|
||||
- `skills/` contains structured skill definitions for AI agents, split into two categories:
|
||||
- **Reference skills** (always-on SDK knowledge): `mem0` (Python + TS SDKs, framework integrations), `mem0-cli` (terminal workflows), `mem0-vercel-ai-sdk` (Vercel AI provider).
|
||||
- **Pipeline skills** (run on demand): `mem0-integrate` wires Mem0 into an existing repo via a TDD pipeline; `mem0-test-integration` verifies what the integrator produced on the same branch. The two are loosely coupled via `.mem0-integration/` artifacts.
|
||||
- **Pipeline skills** (run on demand): `mem0-integrate` wires Mem0 into an existing repo via a TDD pipeline; `mem0-test-integration` verifies what the integrator produced on the same branch (the two are loosely coupled via `.mem0-integration/` artifacts); `mem0-oss-to-platform` migrates an existing project from Mem0 OSS to the hosted Platform SDK (plan, then execute on approval).
|
||||
|
||||
### Adding a New Provider
|
||||
|
||||
@@ -403,38 +381,69 @@ To add a new LLM, embedding, vector store, or reranker provider:
|
||||
6. Add any new dependencies to the appropriate optional group in `pyproject.toml` (never to core `dependencies`)
|
||||
7. Follow the exact pattern of existing providers in the same category — match method signatures, error handling, and config structure
|
||||
|
||||
### Adding a New Integration
|
||||
|
||||
Agent/editor integrations live under `integrations/`. Each is a self-contained directory (its own `package.json`/lockfile, build, and tests). To add one:
|
||||
|
||||
1. Create `integrations/<name>/` and build the integration there.
|
||||
2. If it publishes to a registry, set `repository.directory: "integrations/<name>"` in its `package.json` so npm provenance links to the correct subdirectory.
|
||||
3. Add CI/CD under `.github/workflows/` (`<name>-checks.yml`, `<name>-cd.yml`). Use `integrations/<name>` in `paths:` triggers, `working-directory`, and `cache-dependency-path`. Register the release tag prefix in the `case` block in `release.yml` (keep the bare `v*` arm last). Keep workflow **filenames** stable — npm OIDC trusted publishing is pinned to repo + workflow filename.
|
||||
4. If it is a Claude Code / editor marketplace plugin, register its path in the five `marketplace.json` files (root + `.claude-plugin/`, `.cursor-plugin/`, `.codex-plugin/`, `.agents/plugins/`).
|
||||
5. Document it under `docs/integrations/` and add the page to `docs/docs.json` and `docs/llms.txt`.
|
||||
6. Add rows to the "Key Directories" table and the CI/CD tables in this file.
|
||||
|
||||
## CI/CD
|
||||
|
||||
### CI Workflows (automated testing)
|
||||
|
||||
| Workflow | File | Triggers | Tests |
|
||||
|----------|------|----------|-------|
|
||||
| Python SDK | `ci.yml` | Push to main, PRs on `mem0/`, `tests/`, `pyproject.toml` | Ruff lint + pytest on Python 3.10, 3.11, 3.12 |
|
||||
| TypeScript SDK | `ts-sdk-ci.yml` | Push to main, PRs on `mem0-ts/` | Prettier + build + jest on Node 20, 22 |
|
||||
| Python CLI | `cli-python-ci.yml` | Push to `cli/python/`, PRs, manual | Ruff lint + pytest + hatch build on Python 3.10, 3.11, 3.12 |
|
||||
| Node CLI | `cli-node-ci.yml` | Push to `cli/node/`, PRs, manual | Biome lint + tsc + vitest + tsup build on Node 20, 22 |
|
||||
| OpenClaw | `openclaw-checks.yml` | Push to `openclaw/`, PRs, manual | tsc + vitest (with Codecov) + tsup build on Node 20, 22 |
|
||||
| Embedchain | `ci.yml` (shared) | PRs on `embedchain/` | Ruff + pytest + coverage on Python 3.9–3.12 |
|
||||
PR testing is orchestrated by a single entry point: **`ci-gate.yml` (CI Gate)** runs on every PR, detects which packages changed, and invokes only the relevant package workflows below as reusable workflows (`workflow_call`). Its final **`CI Gate`** job aggregates the results (skipped pipelines pass; failed or cancelled ones fail) and is the **only status check that needs to be required** in branch protection. Package workflows keep their own push-to-main and manual triggers; their `pull_request` triggers moved into the gate's path filters.
|
||||
|
||||
| Workflow | File | Standalone Triggers | Tests |
|
||||
|----------|------|---------------------|-------|
|
||||
| CI Gate | `ci-gate.yml` | All PRs | Routes to and aggregates the workflows below |
|
||||
| Python SDK | `ci.yml` | Push to main | Ruff lint + pytest on Python 3.10, 3.11, 3.12 |
|
||||
| TypeScript SDK | `ts-sdk-ci.yml` | Push to main (on `mem0-ts/`) | Prettier + build + jest on Node 20, 22 |
|
||||
| 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 |
|
||||
| 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.
|
||||
|
||||
### CD Workflows (automated publishing)
|
||||
|
||||
Publishing is routed through a single entry point: **`release.yml` (Release Router)** is the only workflow that listens to `release: published` events. It matches the release tag prefix and dispatches the corresponding package workflow via `workflow_dispatch`, so each release produces exactly one routed run (no skipped runs from the other pipelines).
|
||||
|
||||
| Workflow | File | Tag Prefix | Target |
|
||||
|----------|------|------------|--------|
|
||||
| Release Router | `release.yml` | (all releases) | dispatches the matching workflow below |
|
||||
| Python SDK | `cd.yml` | `v*` | PyPI (`mem0ai`) |
|
||||
| TypeScript SDK | `ts-sdk-cd.yml` | `ts-v*` | npm (`mem0ai`) |
|
||||
| Python CLI | `cli-python-cd.yml` | `cli-v*` | PyPI (`mem0-cli`) |
|
||||
| Node CLI | `cli-node-cd.yml` | `cli-node-v*` | npm (`@mem0/cli`) |
|
||||
| Vercel AI SDK | `vercel-ai-cd.yml` | `vercel-ai-v*` | npm (`@mem0/vercel-ai-provider`) |
|
||||
| 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
|
||||
|
||||
| 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`. |
|
||||
|
||||
@@ -557,7 +566,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
|
||||
@@ -576,10 +585,8 @@ N/A
|
||||
- Modify CI/CD workflows without explicit approval.
|
||||
- Add new Python dependencies to the core `dependencies` list in `pyproject.toml` without discussion — use optional dependency groups instead.
|
||||
- Commit `.env` files, API keys, or credentials.
|
||||
- Modify `embedchain/` unless specifically working on that package — it has its own build system (Poetry).
|
||||
- Skip pre-commit hooks.
|
||||
- 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/`.
|
||||
|
||||
+134
-47
@@ -1,72 +1,157 @@
|
||||
# Contributing to mem0
|
||||
# Contributing to Mem0
|
||||
|
||||
Let us make contribution easy, collaborative and fun.
|
||||
First off, thank you for taking the time to contribute! 🎉 Mem0 is a
|
||||
community-driven project and we welcome contributions of all kinds — bug fixes,
|
||||
new features, documentation, examples, and integrations.
|
||||
|
||||
## Submit your Contribution through PR
|
||||
Mem0 is a polyglot monorepo, and this guide covers contributing to both the
|
||||
**Python SDK** and the **TypeScript SDK** (and the rest of the repository).
|
||||
|
||||
To make a contribution, follow these steps:
|
||||
## Before You Start
|
||||
|
||||
1. Fork and clone this repository
|
||||
2. Do the changes on your fork with dedicated feature branch `feature/f1`
|
||||
3. If you modified the code (new feature or bug-fix), please add tests for it
|
||||
4. Include proper documentation / docstring and examples to run the feature
|
||||
5. Ensure that all tests pass
|
||||
6. Submit a pull request
|
||||
### 1. Open an Issue First
|
||||
|
||||
For more details about pull requests, please read [GitHub's guides](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).
|
||||
**Always open an issue before opening a pull request.** This lets us discuss the
|
||||
change, avoid duplicate effort, and agree on the approach before you invest time
|
||||
in code.
|
||||
|
||||
- Search [existing issues](https://github.com/mem0ai/mem0/issues) first to see if
|
||||
your bug or idea already exists.
|
||||
- If it doesn't, open a
|
||||
[bug report](https://github.com/mem0ai/mem0/issues/new?template=bug_report.yml) or
|
||||
[feature request](https://github.com/mem0ai/mem0/issues/new?template=feature_request.yml).
|
||||
- For anything beyond a trivial fix, wait for a maintainer to confirm the approach
|
||||
before starting significant work.
|
||||
|
||||
### 📦 Development Environment
|
||||
Every pull request must link to an issue using `Closes #<issue-number>`.
|
||||
|
||||
We use `hatch` for managing development environments. To set up:
|
||||
### 2. Sign the Contributor License Agreement (CLA)
|
||||
|
||||
**We cannot accept or merge any pull request until you have signed our Contributor
|
||||
License Agreement (CLA).**
|
||||
|
||||
When you open your first PR, the CLA bot will automatically comment with a link to
|
||||
sign. Signing takes less than a minute and only needs to be done once. Pull
|
||||
requests from contributors who have not signed the CLA will be blocked from
|
||||
merging.
|
||||
|
||||
## Repository Layout
|
||||
|
||||
The two most common contribution targets are the SDKs:
|
||||
|
||||
| Package | Path | Language | Package manager |
|
||||
| --------------------- | ---------- | ------------ | --------------- |
|
||||
| Python SDK (`mem0ai`) | `mem0/` | Python 3.9+ | `hatch` |
|
||||
| TypeScript SDK (`mem0ai`) | `mem0-ts/` | TypeScript | `pnpm` |
|
||||
|
||||
Other packages include the CLIs (`cli/python/`, `cli/node/`), integrations
|
||||
(`integrations/`), the self-hosted `server/`, and the docs site
|
||||
(`docs/`). See [AGENTS.md](./AGENTS.md) for a full map of the repository.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Fork** the repository and **clone** your fork.
|
||||
2. Create a **feature branch** from `main` (e.g. `feature/my-new-feature` or
|
||||
`fix/issue-1234`).
|
||||
3. Make your changes — add **tests**, **documentation**, and **examples** as
|
||||
appropriate.
|
||||
4. Run **linting and tests** for every package you touched (see below).
|
||||
5. Commit using [Conventional Commits](https://www.conventionalcommits.org/)
|
||||
(e.g. `feat:`, `fix:`, `docs:`, `refactor:`, `test:`).
|
||||
6. Push and open a **pull request** against `main`, linking the issue with
|
||||
`Closes #<number>` and filling out the
|
||||
[PR template](./.github/PULL_REQUEST_TEMPLATE.md).
|
||||
|
||||
### Contributing to the Python SDK (`mem0/`)
|
||||
|
||||
We use [`hatch`](https://hatch.pypa.io/latest/install/) to manage environments.
|
||||
**Do not use `pip` or `conda` for dependency management.**
|
||||
|
||||
```bash
|
||||
# Activate environment for specific Python version:
|
||||
hatch shell dev_py_3_9 # Python 3.9
|
||||
hatch shell dev_py_3_10 # Python 3.10
|
||||
hatch shell dev_py_3_11 # Python 3.11
|
||||
hatch shell dev_py_3_12 # Python 3.12
|
||||
# Activate a dev environment (3.9 / 3.10 / 3.11 / 3.12)
|
||||
hatch shell dev_py_3_11
|
||||
|
||||
# The environment will automatically install all dev dependencies
|
||||
# Run tests within the activated shell:
|
||||
make test
|
||||
```
|
||||
|
||||
### 📌 Pre-commit
|
||||
|
||||
To ensure our standards, make sure to install pre-commit before starting to contribute.
|
||||
|
||||
```bash
|
||||
# Install pre-commit hooks (runs ruff + isort on commit)
|
||||
pre-commit install
|
||||
|
||||
# Lint, format, and sort imports
|
||||
make lint
|
||||
make format
|
||||
make sort
|
||||
|
||||
# Run the test suite (run `make install_all` first if deps are missing)
|
||||
make test
|
||||
```
|
||||
|
||||
### 🧪 Testing
|
||||
- **Linter / formatter:** Ruff (line length **120**)
|
||||
- **Import sorting:** isort (`profile = "black"`)
|
||||
- **Tests:** pytest (in `tests/`)
|
||||
|
||||
We use `pytest` to test our code across multiple Python versions. You can run tests using:
|
||||
See the full [Development guide](https://docs.mem0.ai/contributing/development) for
|
||||
environment details.
|
||||
|
||||
### Contributing to the TypeScript SDK (`mem0-ts/`)
|
||||
|
||||
We use [`pnpm`](https://pnpm.io/) (v10+) for all TypeScript packages. **Do not use
|
||||
`npm` or `yarn`.**
|
||||
|
||||
```bash
|
||||
# Run tests with default Python version
|
||||
make test
|
||||
cd mem0-ts
|
||||
pnpm install
|
||||
|
||||
# Test specific Python versions:
|
||||
make test-py-3.9 # Python 3.9 environment
|
||||
make test-py-3.10 # Python 3.10 environment
|
||||
make test-py-3.11 # Python 3.11 environment
|
||||
make test-py-3.12 # Python 3.12 environment
|
||||
|
||||
# When using hatch shells, run tests with:
|
||||
make test # After activating a shell with hatch shell test_XX
|
||||
pnpm run build # tsup (CJS + ESM)
|
||||
pnpm run test # jest (all tests)
|
||||
pnpm run test:unit # unit tests with coverage
|
||||
```
|
||||
|
||||
Make sure that all tests pass across all supported Python versions before submitting a pull request.
|
||||
- **Build:** tsup
|
||||
- **Formatter:** Prettier
|
||||
- **Tests:** jest
|
||||
- Always run type checking after changes: `pnpm run typecheck` (or `tsc --noEmit`).
|
||||
- Use ES module `import` syntax — never `require()`.
|
||||
|
||||
We look forward to your pull requests and can't wait to see your contributions!
|
||||
## Good Contribution Practices
|
||||
|
||||
### 🚀 Releasing
|
||||
- **Keep PRs small and focused.** One logical change per PR is easier to review and
|
||||
merge.
|
||||
- **Follow existing patterns.** Match the style, structure, and conventions of the
|
||||
code around you. Don't introduce new frameworks or abstractions without
|
||||
discussion.
|
||||
- **Write tests** that would fail without your change — regression tests for bugs,
|
||||
coverage for new features.
|
||||
- **Update documentation** in `docs/` for any user-facing change. New `.mdx` pages
|
||||
must be added to `docs/llms.txt` (run
|
||||
`python scripts/check-llms-txt-coverage.py --write` to scaffold entries).
|
||||
- **Add examples** when introducing new user-facing behavior.
|
||||
- **Run linters and tests locally** before pushing — CI re-runs them on every PR
|
||||
via the CI Gate.
|
||||
- **Never commit secrets** — no `.env` files, API keys, or credentials.
|
||||
- **Don't add core dependencies lightly.** New Python dependencies belong in an
|
||||
optional group in `pyproject.toml`, not the core `dependencies` list.
|
||||
- **Be responsive** to review feedback and keep your branch up to date with `main`.
|
||||
|
||||
All packages are published automatically via GitHub Actions when a GitHub Release is created with the correct tag prefix.
|
||||
## Pull Request Checklist
|
||||
|
||||
#### Tag Prefixes
|
||||
Before requesting review, make sure:
|
||||
|
||||
- [ ] An issue exists and is linked with `Closes #<number>`
|
||||
- [ ] You have signed the CLA
|
||||
- [ ] Your code follows the project's style guidelines (lint passes)
|
||||
- [ ] You performed a self-review of your changes
|
||||
- [ ] Tests are added/updated and pass locally
|
||||
- [ ] Documentation is updated if needed
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
**Do not report security vulnerabilities through public issues or pull requests.**
|
||||
Please follow our [Security Policy](./SECURITY.md) to report them privately.
|
||||
|
||||
## Releasing
|
||||
|
||||
All packages are published automatically via GitHub Actions when a GitHub Release
|
||||
is created with the correct tag prefix.
|
||||
|
||||
### Tag Prefixes
|
||||
|
||||
| Package | Registry | Tag Prefix | Example |
|
||||
|---------|----------|------------|---------|
|
||||
@@ -77,15 +162,17 @@ All packages are published automatically via GitHub Actions when a GitHub Releas
|
||||
| `@mem0/vercel-ai-provider` | npm | `vercel-ai-v*` | `vercel-ai-v2.0.6` |
|
||||
| `@mem0/openclaw-mem0` | npm | `openclaw-v*` | `openclaw-v1.0.1` |
|
||||
|
||||
#### How to Release
|
||||
### How to Release
|
||||
|
||||
1. Bump the version in `pyproject.toml` (Python) or `package.json` (Node)
|
||||
2. Create a [GitHub Release](https://github.com/mem0ai/mem0/releases/new) with the matching tag prefix
|
||||
3. The correct workflow will trigger automatically — verify in the [Actions tab](https://github.com/mem0ai/mem0/actions)
|
||||
|
||||
#### Publishing Details
|
||||
### Publishing Details
|
||||
|
||||
- **PyPI packages** use OIDC trusted publishing via `pypa/gh-action-pypi-publish`
|
||||
- **npm packages** use OIDC trusted publishing via npm CLI (>= 11.5.1) — no tokens or secrets required
|
||||
- All workflows require `permissions: id-token: write` for OIDC authentication
|
||||
- First publish of a new npm package must be done manually; OIDC works for subsequent versions
|
||||
|
||||
We look forward to your pull requests and can't wait to see your contributions!
|
||||
|
||||
@@ -1026,7 +1026,8 @@ def get_user_preferences(user_id: str):
|
||||
### AutoGen Integration
|
||||
|
||||
```python
|
||||
from cookbooks.helper.mem0_teachability import Mem0Teachability
|
||||
# Mem0Teachability lives in examples/notebooks/helper/ — see examples/notebooks/mem0-autogen.ipynb
|
||||
from helper.mem0_teachability import Mem0Teachability
|
||||
from mem0 import Memory
|
||||
|
||||
# Add memory capability to AutoGen agents
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
# Migration Guide: Upgrading to mem0 1.0.0
|
||||
|
||||
## TL;DR
|
||||
|
||||
**What changed?** We simplified the API by removing confusing version parameters. Now everything returns a consistent format: `{"results": [...]}`.
|
||||
|
||||
**What you need to do:**
|
||||
1. Upgrade: `pip install mem0ai==1.0.0`
|
||||
2. Remove `version` and `output_format` parameters from your code
|
||||
3. Update response handling to use `result["results"]` instead of treating responses as lists
|
||||
|
||||
**Time needed:** ~5-10 minutes for most projects
|
||||
|
||||
---
|
||||
|
||||
## Quick Migration Guide
|
||||
|
||||
### 1. Install the Update
|
||||
|
||||
```bash
|
||||
pip install mem0ai==1.0.0
|
||||
```
|
||||
|
||||
### 2. Update Your Code
|
||||
|
||||
**If you're using the Memory API:**
|
||||
|
||||
```python
|
||||
# Before
|
||||
memory = Memory(config=MemoryConfig(version="v1.1"))
|
||||
result = memory.add("I like pizza")
|
||||
|
||||
# After
|
||||
memory = Memory() # That's it - version is automatic now
|
||||
result = memory.add("I like pizza")
|
||||
```
|
||||
|
||||
**If you're using the Client API:**
|
||||
|
||||
```python
|
||||
# Before
|
||||
client.add(messages, output_format="v1.1")
|
||||
client.search(query, version="v2", output_format="v1.1")
|
||||
|
||||
# After
|
||||
client.add(messages) # Just remove those extra parameters
|
||||
client.search(query)
|
||||
```
|
||||
|
||||
### 3. Update How You Handle Responses
|
||||
|
||||
All responses now use the same format: a dictionary with `"results"` key.
|
||||
|
||||
```python
|
||||
# Before - you might have done this
|
||||
result = memory.add("I like pizza")
|
||||
for item in result: # Treating it as a list
|
||||
print(item)
|
||||
|
||||
# After - do this instead
|
||||
result = memory.add("I like pizza")
|
||||
for item in result["results"]: # Access the results key
|
||||
print(item)
|
||||
|
||||
# Graph relations (if you use them)
|
||||
if "relations" in result:
|
||||
for relation in result["relations"]:
|
||||
print(relation)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Enhanced Message Handling
|
||||
|
||||
The platform client (MemoryClient) now supports the same flexible message formats as the OSS version:
|
||||
|
||||
```python
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(api_key="your-key")
|
||||
|
||||
# All three formats now work:
|
||||
|
||||
# 1. Single string (automatically converted to user message)
|
||||
client.add("I like pizza", user_id="alice")
|
||||
|
||||
# 2. Single message dictionary
|
||||
client.add({"role": "user", "content": "I like pizza"}, user_id="alice")
|
||||
|
||||
# 3. List of messages (conversation)
|
||||
client.add([
|
||||
{"role": "user", "content": "I like pizza"},
|
||||
{"role": "assistant", "content": "I'll remember that!"}
|
||||
], user_id="alice")
|
||||
```
|
||||
|
||||
### Async Mode Configuration
|
||||
|
||||
The `async_mode` parameter now defaults to `True` but can be configured:
|
||||
|
||||
```python
|
||||
# Default behavior (async_mode=True)
|
||||
client.add(messages, user_id="alice")
|
||||
|
||||
# Explicitly set async mode
|
||||
client.add(messages, user_id="alice", async_mode=True)
|
||||
|
||||
# Disable async mode if needed
|
||||
client.add(messages, user_id="alice", async_mode=False)
|
||||
```
|
||||
|
||||
**Note:** `async_mode=True` provides better performance for most use cases. Only set it to `False` if you have specific synchronous processing requirements.
|
||||
|
||||
---
|
||||
|
||||
## That's It!
|
||||
|
||||
For most users, that's all you need to know. The changes are:
|
||||
- ✅ No more `version` or `output_format` parameters
|
||||
- ✅ Consistent `{"results": [...]}` response format
|
||||
- ✅ Cleaner, simpler API
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
**Getting `KeyError: 'results'`?**
|
||||
|
||||
Your code is still treating the response as a list. Update it:
|
||||
```python
|
||||
# Change this:
|
||||
for memory in response:
|
||||
|
||||
# To this:
|
||||
for memory in response["results"]:
|
||||
```
|
||||
|
||||
**Getting `TypeError: unexpected keyword argument`?**
|
||||
|
||||
You're still passing old parameters. Remove them:
|
||||
```python
|
||||
# Change this:
|
||||
client.add(messages, output_format="v1.1")
|
||||
|
||||
# To this:
|
||||
client.add(messages)
|
||||
```
|
||||
|
||||
**Seeing deprecation warnings?**
|
||||
|
||||
Remove any explicit `version="v1.0"` from your config:
|
||||
```python
|
||||
# Change this:
|
||||
memory = Memory(config=MemoryConfig(version="v1.0"))
|
||||
|
||||
# To this:
|
||||
memory = Memory()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What's New in 1.0.0
|
||||
|
||||
- **Better vector stores:** Fixed OpenSearch and improved reliability across all stores
|
||||
- **Cleaner API:** One way to do things, no more confusing options
|
||||
- **Enhanced GCP support:** Better Vertex AI configuration options
|
||||
- **Flexible message input:** Platform client now accepts strings, dicts, and lists (aligned with OSS)
|
||||
- **Configurable async_mode:** Now defaults to `True` but users can override if needed
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Check [GitHub Issues](https://github.com/mem0ai/mem0/issues)
|
||||
- Read the [documentation](https://docs.mem0.ai/)
|
||||
- Open a new issue if you're stuck
|
||||
|
||||
---
|
||||
|
||||
## Advanced: Configuration Changes
|
||||
|
||||
**If you configured vector stores with version:**
|
||||
|
||||
```python
|
||||
# Before
|
||||
config = MemoryConfig(
|
||||
version="v1.1",
|
||||
vector_store=VectorStoreConfig(...)
|
||||
)
|
||||
|
||||
# After
|
||||
config = MemoryConfig(
|
||||
vector_store=VectorStoreConfig(...)
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Migration
|
||||
|
||||
Quick sanity check:
|
||||
|
||||
```python
|
||||
from mem0 import Memory
|
||||
|
||||
memory = Memory()
|
||||
|
||||
# Add should return a dict with "results"
|
||||
result = memory.add("I like pizza", user_id="test")
|
||||
assert "results" in result
|
||||
|
||||
# Search should return a dict with "results"
|
||||
search = memory.search("food", user_id="test")
|
||||
assert "results" in search
|
||||
|
||||
# Get all should return a dict with "results"
|
||||
all_memories = memory.get_all(user_id="test")
|
||||
assert "results" in all_memories
|
||||
|
||||
print("✅ Migration successful!")
|
||||
```
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -46,12 +46,12 @@
|
||||
|
||||
| Benchmark | Old | New | Tokens | Latency p50 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **LoCoMo** | 71.4 | **91.6** | 7.0K | 0.88s |
|
||||
| **LongMemEval** | 67.8 | **94.8** | 6.8K | 1.09s |
|
||||
| **LoCoMo** | 71.4 | **92.5** | 7.0K | 0.88s |
|
||||
| **LongMemEval** | 67.8 | **94.4** | 6.8K | 1.09s |
|
||||
| **BEAM (1M)** | — | **64.1** | 6.7K | 1.00s |
|
||||
| **BEAM (10M)** | — | **48.6** | 6.9K | 1.05s |
|
||||
|
||||
All benchmarks run on the same production-representative model stack. Single-pass retrieval (one call, no agentic loops).
|
||||
All benchmarks run on the same production-representative model stack. Single-pass retrieval (one call, no agentic loops) at a top_200 retrieval budget. Scores reflect Mem0's managed platform, which includes proprietary optimizations not available in the open-source SDK; open-source users should expect directionally similar gains but not identical numbers.
|
||||
|
||||
**What changed:**
|
||||
- **Single-pass ADD-only extraction** -- one LLM call, no UPDATE/DELETE. Memories accumulate; nothing is overwritten.
|
||||
@@ -63,8 +63,8 @@ All benchmarks run on the same production-representative model stack. Single-pas
|
||||
See the [migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for upgrade instructions. The [evaluation framework](https://github.com/mem0ai/memory-benchmarks) is open-sourced so anyone can reproduce the numbers.
|
||||
|
||||
## Research Highlights
|
||||
- **91.6 on LoCoMo** -- +20 points over the previous algorithm
|
||||
- **94.8 on LongMemEval** -- +27 points, with +53.6 on assistant memory recall
|
||||
- **92.5 on LoCoMo** -- +21 points over the previous algorithm
|
||||
- **94.4 on LongMemEval** -- +27 points, with 98.2 on assistant memory recall
|
||||
- **64.1 on BEAM (1M)** -- production-scale memory evaluation at 1M tokens
|
||||
- [Read the full paper](https://mem0.ai/research)
|
||||
|
||||
@@ -86,7 +86,25 @@ See the [migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for upgra
|
||||
|
||||
## 🚀 Quickstart Guide <a name="quickstart"></a>
|
||||
|
||||
> **Agent Mode — for AI agents setting up Mem0 autonomously.** Run `mem0 init --agent --agent-caller <your-name> --json` (after `pip install mem0-cli` or `npm install -g @mem0/cli`), replacing `<your-name>` with your AI agent identity (e.g. `claude-code`, `cursor`, `codex`). This mints an evaluation Mem0 API key in <5 seconds — no email, no dashboard, no OTP. If you forgot to pass `--agent-caller`, run `mem0 identify <your-name>` after init. Use the key immediately against the Platform or the MCP server. The human owner can claim ownership later with `mem0 init --email <their-email>`: memories transfer, the same key keeps working, and the agent isn't disrupted.
|
||||
### Sign up as an agent
|
||||
|
||||
AI agents can mint a working Mem0 API key in under five seconds — no email, no dashboard, no OTP. Four commands end-to-end:
|
||||
|
||||
```bash
|
||||
# 1. Install
|
||||
npm install -g @mem0/cli # or: pip install mem0-cli
|
||||
|
||||
# 2. Sign up as an agent (replace `claude-code` with your name)
|
||||
mem0 init --agent --agent-caller claude-code
|
||||
|
||||
# 3. Add a memory
|
||||
mem0 add "I am using mem0"
|
||||
|
||||
# 4. Search
|
||||
mem0 search "am I using mem0"
|
||||
```
|
||||
|
||||
The human owner can claim the account later with `mem0 init --email <their-email>` — same key, memories preserved. Full guide: [Sign up as an agent](https://docs.mem0.ai/platform/agent-signup).
|
||||
|
||||
| | Library | Self-Hosted Server | Cloud Platform |
|
||||
|---|---------|-------------------|----------------|
|
||||
@@ -135,6 +153,7 @@ See the [self-hosted docs](https://docs.mem0.ai/open-source/overview) for config
|
||||
|
||||
1. Sign up on [Mem0 Platform](https://app.mem0.ai?utm_source=oss&utm_medium=readme)
|
||||
2. Embed the memory layer via SDK or API keys
|
||||
3. Using hosted Qdrant vectors? See the [Platform migration guide](https://docs.mem0.ai/migration/oss-to-platform) to import them into Mem0 Platform.
|
||||
|
||||
### CLI
|
||||
|
||||
@@ -167,9 +186,10 @@ npx skills add https://github.com/mem0ai/mem0 --skill mem0-vercel-ai-sdk
|
||||
```bash
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-integrate
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-test-integration
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-oss-to-platform
|
||||
```
|
||||
|
||||
Use `/mem0-integrate` to wire Mem0 into an existing repo via a test-first pipeline, then `/mem0-test-integration` to verify. See the [skills catalog](./skills/) or [Vibecoding with Mem0](https://docs.mem0.ai/vibecoding) for the full picture.
|
||||
Use `/mem0-integrate` to wire Mem0 into an existing repo via a test-first pipeline, then `/mem0-test-integration` to verify. Use `/mem0-oss-to-platform` to migrate an existing project from Mem0 OSS to the hosted Platform SDK. See the [skills catalog](./skills/) or [Vibecoding with Mem0](https://docs.mem0.ai/vibecoding) for the full picture.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# Security Policy
|
||||
|
||||
We take the security of Mem0 and our community seriously. Thank you for helping
|
||||
keep Mem0 and its users safe by disclosing vulnerabilities responsibly.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please **do not** report security vulnerabilities through public GitHub issues,
|
||||
pull requests, or discussions.
|
||||
|
||||
If you believe you have found a security vulnerability in Mem0, please report it
|
||||
privately through one of the following channels:
|
||||
|
||||
1. **GitHub Private Vulnerability Reporting** — open a
|
||||
[private security advisory](https://github.com/mem0ai/mem0/security/advisories/new)
|
||||
directly on this repository.
|
||||
2. **Email** the maintainers at **support@mem0.ai** with the subject line:
|
||||
|
||||
`SECURITY: Mem0 vulnerability report`
|
||||
|
||||
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, CLI)
|
||||
- Affected version, tag, or commit
|
||||
- Clear, step-by-step reproduction instructions
|
||||
- The security impact and a proof of concept, if available
|
||||
- Any suggested fix or mitigation
|
||||
|
||||
## Response Process
|
||||
|
||||
- We will acknowledge receipt of your report within **72 hours**.
|
||||
- We will work with you privately to confirm the issue and assess its impact.
|
||||
- Once a fix or mitigation is ready, we will coordinate a disclosure timeline
|
||||
with you and credit you for the discovery, unless you prefer to remain anonymous.
|
||||
|
||||
## Public Disclosure
|
||||
|
||||
Please avoid sharing technical details of the vulnerability publicly until the
|
||||
maintainers have reviewed the issue and a fix or mitigation has been released. We
|
||||
are committed to resolving valid reports promptly and keeping you informed
|
||||
throughout the process.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We release security fixes against the latest published version of each package.
|
||||
Whenever possible, please reproduce the issue on the most recent release before
|
||||
reporting.
|
||||
+13
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/cli",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.11",
|
||||
"description": "The official CLI for mem0 — the memory layer for AI agents",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -40,8 +40,19 @@
|
||||
"typescript": "^5.4.0",
|
||||
"tsup": "^8.0.0",
|
||||
"tsx": "^4.7.0",
|
||||
"vitest": "^1.5.0",
|
||||
"vite": "^6.0.0",
|
||||
"vitest": "^4.1.0",
|
||||
"@biomejs/biome": "^1.7.0",
|
||||
"@types/node": "^20.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"jws@4.0.0": "4.0.1",
|
||||
"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",
|
||||
"esbuild": ">=0.28.1",
|
||||
"postcss@<8.5.18": ">=8.5.18 <9.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+327
-723
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
packages:
|
||||
- '.'
|
||||
|
||||
onlyBuiltDependencies:
|
||||
- "@biomejs/biome"
|
||||
- esbuild
|
||||
|
||||
overrides:
|
||||
jws@4.0.0: 4.0.1
|
||||
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
|
||||
"esbuild": ">=0.28.1"
|
||||
"postcss@<8.5.18": ">=8.5.18 <9.0.0"
|
||||
@@ -17,6 +17,10 @@ import {
|
||||
type SearchOptions,
|
||||
} from "./base.js";
|
||||
|
||||
function encodePathSegment(value: unknown): string {
|
||||
return encodeURIComponent(String(value));
|
||||
}
|
||||
|
||||
export class PlatformBackend implements Backend {
|
||||
private baseUrl: string;
|
||||
private headers: Record<string, string>;
|
||||
@@ -218,9 +222,13 @@ export class PlatformBackend implements Backend {
|
||||
}
|
||||
|
||||
async get(memoryId: string): Promise<Record<string, unknown>> {
|
||||
return (await this._request("GET", `/v1/memories/${memoryId}/`, {
|
||||
params: { source: "CLI" },
|
||||
})) as Record<string, unknown>;
|
||||
return (await this._request(
|
||||
"GET",
|
||||
`/v1/memories/${encodePathSegment(memoryId)}/`,
|
||||
{
|
||||
params: { source: "CLI" },
|
||||
},
|
||||
)) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
async listMemories(
|
||||
@@ -277,9 +285,13 @@ export class PlatformBackend implements Backend {
|
||||
if (content) payload.text = content;
|
||||
if (metadata) payload.metadata = metadata;
|
||||
payload.source = "CLI";
|
||||
return (await this._request("PUT", `/v1/memories/${memoryId}/`, {
|
||||
json: payload,
|
||||
})) as Record<string, unknown>;
|
||||
return (await this._request(
|
||||
"PUT",
|
||||
`/v1/memories/${encodePathSegment(memoryId)}/`,
|
||||
{
|
||||
json: payload,
|
||||
},
|
||||
)) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
async delete(
|
||||
@@ -297,9 +309,13 @@ export class PlatformBackend implements Backend {
|
||||
})) as Record<string, unknown>;
|
||||
}
|
||||
if (memoryId) {
|
||||
return (await this._request("DELETE", `/v1/memories/${memoryId}/`, {
|
||||
params: { source: "CLI" },
|
||||
})) as Record<string, unknown>;
|
||||
return (await this._request(
|
||||
"DELETE",
|
||||
`/v1/memories/${encodePathSegment(memoryId)}/`,
|
||||
{
|
||||
params: { source: "CLI" },
|
||||
},
|
||||
)) as Record<string, unknown>;
|
||||
}
|
||||
throw new Error("Either memoryId or --all is required");
|
||||
}
|
||||
@@ -316,16 +332,18 @@ export class PlatformBackend implements Backend {
|
||||
if (entities.length === 0) {
|
||||
throw new Error("At least one entity ID is required for deleteEntities.");
|
||||
}
|
||||
// Delete each provided entity via the v2 path-based endpoint
|
||||
let result: Record<string, unknown> = {};
|
||||
// Delete each provided entity via the v2 path-based endpoint. Key each
|
||||
// response by entity type so a multi-entity delete (e.g. --user-id and
|
||||
// --agent-id together) doesn't discard everything but the last result.
|
||||
const results: Record<string, unknown> = {};
|
||||
for (const [entityType, entityId] of entities) {
|
||||
result = (await this._request(
|
||||
results[entityType] = (await this._request(
|
||||
"DELETE",
|
||||
`/v2/entities/${entityType}/${entityId}/`,
|
||||
`/v2/entities/${encodePathSegment(entityType)}/${encodePathSegment(entityId)}/`,
|
||||
{ params: { source: "CLI" } },
|
||||
)) as Record<string, unknown>;
|
||||
}
|
||||
return result;
|
||||
return results;
|
||||
}
|
||||
|
||||
async ping(): Promise<Record<string, unknown>> {
|
||||
@@ -384,9 +402,9 @@ export class PlatformBackend implements Backend {
|
||||
}
|
||||
|
||||
async getEvent(eventId: string): Promise<Record<string, unknown>> {
|
||||
return (await this._request("GET", `/v1/event/${eventId}/`)) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
return (await this._request(
|
||||
"GET",
|
||||
`/v1/event/${encodePathSegment(eventId)}/`,
|
||||
)) as Record<string, unknown>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ export async function claimViaOtp(
|
||||
printError(`Claim failed: ${detail}`);
|
||||
if (errCode === "email_already_claimed") {
|
||||
console.log(
|
||||
` ${dim("Tip: this email already has a Mem0 account. Sign in there and run `mem0 link <key>` to attach this agent.")}`,
|
||||
` ${dim("Tip: this email already has a Mem0 account. Sign in at app.mem0.ai with your existing credentials.")}`,
|
||||
);
|
||||
}
|
||||
process.exit(1);
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* `mem0 agent-rush <add|search> "..."` — wraps the AGENTRUSH platform endpoints.
|
||||
* Project routing is implicit (server-side); zero flags needed.
|
||||
*/
|
||||
|
||||
import readline from "node:readline";
|
||||
import { colors, printError, printSuccess } from "../branding.js";
|
||||
import { loadConfig, saveConfig } from "../config.js";
|
||||
import { CLI_VERSION } from "../version.js";
|
||||
|
||||
const PII_WARNING = [
|
||||
"",
|
||||
"⚠️ AGENTRUSH memories are PUBLIC — visible to any other player.",
|
||||
" Do not include real names, emails, secrets, work content, or PII.",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
const ERROR_HINTS: Record<string, string> = {
|
||||
agentrush_search_first:
|
||||
"Run 3 'mem0 agent-rush search' commands before adding.",
|
||||
agentrush_search_quota: "You've used your 3 lifetime searches.",
|
||||
agentrush_add_quota: "You've used your 3 lifetime adds.",
|
||||
agentrush_not_agent_mode:
|
||||
"Re-run 'mem0 init --agent' to bootstrap an agent-mode key.",
|
||||
agentrush_length: "Memory text must be 50-1000 characters.",
|
||||
agentrush_no_urls: "URLs are not allowed.",
|
||||
agentrush_blocklist: "Content contains a blocked term.",
|
||||
agentrush_global_quota: "Event-wide cap reached. Try again later.",
|
||||
agentrush_not_provisioned:
|
||||
"AGENTRUSH is not provisioned in this environment.",
|
||||
};
|
||||
|
||||
async function callEndpoint(
|
||||
path: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
const config = loadConfig();
|
||||
const baseUrl = (config.platform?.baseUrl ?? "https://api.mem0.ai").replace(
|
||||
/\/+$/,
|
||||
"",
|
||||
);
|
||||
|
||||
if (!config.platform?.apiKey) {
|
||||
printError("Not initialized. Run `mem0 init --agent` first.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const resp = await fetch(`${baseUrl}${path}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Token ${config.platform.apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
"X-Mem0-Source": "cli",
|
||||
"X-Mem0-Client-Language": "node",
|
||||
"X-Mem0-Client-Version": CLI_VERSION,
|
||||
"X-Mem0-Mode": "agent-rush",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
});
|
||||
|
||||
const json = await resp.json().catch(() => ({}));
|
||||
|
||||
if (!resp.ok) {
|
||||
const code =
|
||||
(json as { error?: { code?: string } }).error?.code ?? "unknown";
|
||||
printError(`AGENTRUSH error: ${code}`);
|
||||
if (ERROR_HINTS[code]) {
|
||||
console.log(` ${colors.dim(ERROR_HINTS[code])}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
function promptLine(question: string): Promise<string> {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
rl.question(question, (answer) => {
|
||||
rl.close();
|
||||
resolve(answer.trim());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the human has acknowledged that AGENTRUSH memories are PUBLIC.
|
||||
*
|
||||
* Interactive (TTY): show the prompt; on "y" persist `agentRush.acknowledgedAt`
|
||||
* so we never ask the same machine twice. On anything else, abort.
|
||||
*
|
||||
* Non-interactive (agent invocation, no TTY): print the warning to stderr
|
||||
* for the human reading the agent's transcript and proceed — agents can't
|
||||
* answer y/N prompts.
|
||||
*/
|
||||
async function ensureWarningAcknowledged(): Promise<void> {
|
||||
const config = loadConfig();
|
||||
if (config.agentRush?.acknowledgedAt) return;
|
||||
|
||||
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
||||
// Agent context: surface the warning to stderr, don't block.
|
||||
console.error(PII_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(PII_WARNING);
|
||||
const answer = (await promptLine(" Continue? [y/N]: ")).toLowerCase();
|
||||
if (answer !== "y" && answer !== "yes") {
|
||||
printError("Aborted.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
config.agentRush.acknowledgedAt = new Date().toISOString();
|
||||
saveConfig(config);
|
||||
}
|
||||
|
||||
export async function cmdAgentRushAdd(content: string): Promise<void> {
|
||||
await ensureWarningAcknowledged();
|
||||
const result = await callEndpoint("/v1/agent-rush/memories/", { content });
|
||||
printSuccess(
|
||||
`Memory submitted (event_id: ${(result as { event_id?: string }).event_id ?? "?"})`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function cmdAgentRushSearch(query: string): Promise<void> {
|
||||
const result = (await callEndpoint("/v1/agent-rush/memories/search/", {
|
||||
query,
|
||||
})) as {
|
||||
results?: Array<{ memory?: string }>;
|
||||
memories?: Array<{ memory?: string }>;
|
||||
};
|
||||
|
||||
const memories = result.results ?? result.memories ?? [];
|
||||
|
||||
if (memories.length === 0) {
|
||||
console.log(colors.dim("(no results)"));
|
||||
return;
|
||||
}
|
||||
|
||||
memories.slice(0, 5).forEach((m, i) => {
|
||||
console.log(` ${i + 1}. ${m.memory ?? JSON.stringify(m)}`);
|
||||
});
|
||||
}
|
||||
@@ -46,7 +46,7 @@ export async function cmdAdd(
|
||||
file?: string;
|
||||
metadata?: string;
|
||||
immutable: boolean;
|
||||
noInfer: boolean;
|
||||
infer?: boolean;
|
||||
expires?: string;
|
||||
categories?: string;
|
||||
output: string;
|
||||
@@ -136,7 +136,7 @@ export async function cmdAdd(
|
||||
runId: opts.runId,
|
||||
metadata: meta,
|
||||
immutable: opts.immutable,
|
||||
infer: !opts.noInfer,
|
||||
infer: opts.infer !== false,
|
||||
expires: opts.expires,
|
||||
categories: cats,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* `mem0 whoami` — print the active agent's default_user_id (AGENTRUSH identifier).
|
||||
* Reads from local config; no network call.
|
||||
*/
|
||||
|
||||
import { colors, printError, printInfo } from "../branding.js";
|
||||
import { loadConfig } from "../config.js";
|
||||
|
||||
export async function cmdWhoami(): Promise<void> {
|
||||
const config = loadConfig();
|
||||
const sessionId = config.platform?.defaultUserId;
|
||||
if (!sessionId) {
|
||||
printError("No default_user_id found. Run `mem0 init --agent` first.");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Your AGENTRUSH identifier: ${colors.brand(sessionId)}`);
|
||||
printInfo("Find your row at https://mem0.ai/agentrush");
|
||||
}
|
||||
@@ -40,11 +40,18 @@ export interface TelemetryConfig {
|
||||
anonymousId: string;
|
||||
}
|
||||
|
||||
export interface AgentRushConfig {
|
||||
// ISO timestamp the human acknowledged the "memories are public" warning.
|
||||
// Empty until first interactive `mem0 agent-rush add`.
|
||||
acknowledgedAt: string;
|
||||
}
|
||||
|
||||
export interface Mem0Config {
|
||||
version: number;
|
||||
defaults: DefaultsConfig;
|
||||
platform: PlatformConfig;
|
||||
telemetry: TelemetryConfig;
|
||||
agentRush: AgentRushConfig;
|
||||
}
|
||||
|
||||
export function createDefaultConfig(): Mem0Config {
|
||||
@@ -69,6 +76,9 @@ export function createDefaultConfig(): Mem0Config {
|
||||
telemetry: {
|
||||
anonymousId: "",
|
||||
},
|
||||
agentRush: {
|
||||
acknowledgedAt: "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -103,6 +113,8 @@ export function loadConfig(): Mem0Config {
|
||||
config.defaults.runId = defaults.run_id ?? "";
|
||||
const telemetry = data.telemetry ?? {};
|
||||
config.telemetry.anonymousId = telemetry.anonymous_id ?? "";
|
||||
const agentRush = data.agent_rush ?? {};
|
||||
config.agentRush.acknowledgedAt = agentRush.acknowledged_at ?? "";
|
||||
}
|
||||
|
||||
// Environment variable overrides
|
||||
@@ -143,6 +155,9 @@ export function saveConfig(config: Mem0Config): void {
|
||||
telemetry: {
|
||||
anonymous_id: config.telemetry.anonymousId,
|
||||
},
|
||||
agent_rush: {
|
||||
acknowledged_at: config.agentRush.acknowledgedAt,
|
||||
},
|
||||
};
|
||||
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2));
|
||||
|
||||
@@ -262,6 +262,48 @@ program
|
||||
await runIdentify(name);
|
||||
});
|
||||
|
||||
// ── Setup: whoami (print active agent identifier) ────────────────────────
|
||||
|
||||
program
|
||||
.command("whoami")
|
||||
.description("Print the active agent's AGENTRUSH identifier.")
|
||||
.action(async () => {
|
||||
const { cmdWhoami } = await import("./commands/whoami.js");
|
||||
await cmdWhoami();
|
||||
});
|
||||
|
||||
// ── AGENTRUSH subcommand group ────────────────────────────────────────────
|
||||
|
||||
const agentRush = program
|
||||
.command("agent-rush")
|
||||
.description("AGENTRUSH game commands.")
|
||||
.addHelpCommand(false)
|
||||
.configureHelp({ formatHelp: richFormatHelp });
|
||||
|
||||
agentRush
|
||||
.command("add <content...>")
|
||||
.description("Submit a memory to AGENTRUSH.")
|
||||
.addHelpText(
|
||||
"after",
|
||||
'\nExamples:\n $ mem0 agent-rush add "I used mem0 to build a coding agent"\n $ mem0 agent-rush add "Agents that remember are better agents"',
|
||||
)
|
||||
.action(async (parts: string[]) => {
|
||||
const { cmdAgentRushAdd } = await import("./commands/agent-rush.js");
|
||||
await cmdAgentRushAdd(parts.join(" "));
|
||||
});
|
||||
|
||||
agentRush
|
||||
.command("search <query...>")
|
||||
.description("Search AGENTRUSH memories.")
|
||||
.addHelpText(
|
||||
"after",
|
||||
'\nExamples:\n $ mem0 agent-rush search "agents and memory and tools"\n $ mem0 agent-rush search "coding assistant"',
|
||||
)
|
||||
.action(async (parts: string[]) => {
|
||||
const { cmdAgentRushSearch } = await import("./commands/agent-rush.js");
|
||||
await cmdAgentRushSearch(parts.join(" "));
|
||||
});
|
||||
|
||||
// ── Memory: add ───────────────────────────────────────────────────────────
|
||||
|
||||
program
|
||||
|
||||
@@ -145,11 +145,11 @@ export function captureEvent(
|
||||
anonDistinctIdToAlias: anonIdToAlias,
|
||||
};
|
||||
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
[SENDER_SCRIPT, JSON.stringify(context)],
|
||||
{ detached: true, stdio: "ignore" },
|
||||
);
|
||||
const child = spawn(process.execPath, [SENDER_SCRIPT], {
|
||||
detached: true,
|
||||
stdio: ["pipe", "ignore", "ignore"],
|
||||
});
|
||||
child.stdin?.end(JSON.stringify(context));
|
||||
child.unref();
|
||||
} catch {
|
||||
/* silently swallow */
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Standalone telemetry sender — runs as a detached child process.
|
||||
*
|
||||
* Usage: node telemetry-sender.cjs '<json context>'
|
||||
* Usage: node telemetry-sender.cjs (JSON context is read from stdin; a single
|
||||
* argv argument is still accepted as a legacy fallback)
|
||||
*
|
||||
* This script is spawned by telemetry.captureEvent() and runs independently
|
||||
* of the parent CLI process. It:
|
||||
@@ -19,6 +20,31 @@
|
||||
const https = require("https");
|
||||
const fs = require("fs");
|
||||
|
||||
function loadContext() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (process.argv[2]) {
|
||||
try {
|
||||
resolve(JSON.parse(process.argv[2]));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let data = "";
|
||||
process.stdin.setEncoding("utf8");
|
||||
process.stdin.on("data", (chunk) => (data += chunk));
|
||||
process.stdin.on("end", () => {
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
process.stdin.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
function httpsRequest(url, method, headers, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const u = new URL(url);
|
||||
@@ -108,7 +134,7 @@ async function sendIdentifyEvent(ctx, payload, anonId) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const ctx = JSON.parse(process.argv[2]);
|
||||
const ctx = await loadContext();
|
||||
const payload = ctx.payload;
|
||||
|
||||
if (ctx.needsEmail && ctx.mem0ApiKey) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { Command } from "commander";
|
||||
import { createMockBackend } from "./setup.js";
|
||||
import type { Backend } from "../src/backend/base.js";
|
||||
import { setAgentMode } from "../src/state.js";
|
||||
@@ -41,8 +42,6 @@ describe("cmdAdd", () => {
|
||||
await cmdAdd(mockBackend, "I prefer dark mode", {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
|
||||
output: "text",
|
||||
});
|
||||
expect(mockBackend.add).toHaveBeenCalledOnce();
|
||||
@@ -54,8 +53,6 @@ describe("cmdAdd", () => {
|
||||
userId: "alice",
|
||||
messages: JSON.stringify([{ role: "user", content: "I love Python" }]),
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
|
||||
output: "text",
|
||||
});
|
||||
expect(mockBackend.add).toHaveBeenCalledOnce();
|
||||
@@ -66,8 +63,6 @@ describe("cmdAdd", () => {
|
||||
await cmdAdd(mockBackend, "test", {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
|
||||
output: "json",
|
||||
});
|
||||
expect(output).toContain("results");
|
||||
@@ -78,14 +73,59 @@ describe("cmdAdd", () => {
|
||||
await cmdAdd(mockBackend, "test", {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
|
||||
output: "quiet",
|
||||
});
|
||||
expect(output).not.toContain("dark mode");
|
||||
});
|
||||
});
|
||||
|
||||
describe("cmdAdd forwards --no-infer (regression for #5261)", () => {
|
||||
it("forwards infer: false when --no-infer is set", async () => {
|
||||
const { cmdAdd } = await import("../src/commands/memory.js");
|
||||
// `infer: false` is the shape Commander produces for `--no-infer`.
|
||||
await cmdAdd(mockBackend, "store me verbatim", {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
infer: false,
|
||||
output: "text",
|
||||
});
|
||||
expect(mockBackend.add).toHaveBeenCalledWith(
|
||||
"store me verbatim",
|
||||
undefined,
|
||||
expect.objectContaining({ infer: false }),
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards infer: true by default (flag absent)", async () => {
|
||||
const { cmdAdd } = await import("../src/commands/memory.js");
|
||||
await cmdAdd(mockBackend, "infer me", {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
output: "text",
|
||||
});
|
||||
expect(mockBackend.add).toHaveBeenCalledWith(
|
||||
"infer me",
|
||||
undefined,
|
||||
expect.objectContaining({ infer: true }),
|
||||
);
|
||||
});
|
||||
|
||||
it("Commander stores --no-infer as opts.infer, not opts.noInfer", () => {
|
||||
// Pins the assumption the fix relies on: Commander's `--no-X` option
|
||||
// populates the positive camelCase key (`infer`), never `noInfer`.
|
||||
const withFlag = new Command();
|
||||
withFlag.option("--no-infer", "Skip inference, store raw.").action(() => {});
|
||||
withFlag.parse(["--no-infer"], { from: "user" });
|
||||
expect(withFlag.opts().infer).toBe(false);
|
||||
expect(withFlag.opts().noInfer).toBeUndefined();
|
||||
|
||||
const withoutFlag = new Command();
|
||||
withoutFlag.option("--no-infer", "Skip inference, store raw.").action(() => {});
|
||||
withoutFlag.parse([], { from: "user" });
|
||||
expect(withoutFlag.opts().infer).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cmdAdd deduplicates PENDING", () => {
|
||||
const DUPLICATE_PENDING = {
|
||||
results: [
|
||||
@@ -100,8 +140,6 @@ describe("cmdAdd deduplicates PENDING", () => {
|
||||
await cmdAdd(mockBackend, "test", {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
|
||||
output: "text",
|
||||
});
|
||||
expect(output.match(/Queued/g)?.length).toBe(1);
|
||||
@@ -113,8 +151,6 @@ describe("cmdAdd deduplicates PENDING", () => {
|
||||
await cmdAdd(mockBackend, "test", {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
|
||||
output: "json",
|
||||
});
|
||||
const data = JSON.parse(output);
|
||||
@@ -129,8 +165,6 @@ describe("cmdAdd deduplicates PENDING", () => {
|
||||
await cmdAdd(mockBackend, "test", {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
|
||||
output: "agent",
|
||||
});
|
||||
const data = JSON.parse(output);
|
||||
@@ -315,8 +349,6 @@ describe("agent mode", () => {
|
||||
await cmdAdd(mockBackend, "test preference", {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
|
||||
output: "agent",
|
||||
});
|
||||
const parsed = JSON.parse(output.trim());
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Tests for the Platform backend (mem0 Platform API client).
|
||||
*/
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
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]);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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" } });
|
||||
});
|
||||
|
||||
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 path encoding", () => {
|
||||
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");
|
||||
|
||||
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();
|
||||
|
||||
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/",
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mockLoadConfig = vi.fn();
|
||||
const mockSaveConfig = vi.fn();
|
||||
const mockSpawn = vi.fn();
|
||||
|
||||
vi.mock("../src/config.js", () => ({
|
||||
CONFIG_FILE: "/tmp/mem0-config.json",
|
||||
loadConfig: mockLoadConfig,
|
||||
saveConfig: mockSaveConfig,
|
||||
}));
|
||||
|
||||
vi.mock("node:child_process", () => ({
|
||||
spawn: mockSpawn,
|
||||
}));
|
||||
|
||||
describe("captureEvent", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
mockLoadConfig.mockReset();
|
||||
mockSaveConfig.mockReset();
|
||||
mockSpawn.mockReset();
|
||||
delete process.env.MEM0_TELEMETRY;
|
||||
});
|
||||
|
||||
it("pipes the telemetry context through stdin instead of argv", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
platform: {
|
||||
apiKey: "m0-node-secret",
|
||||
baseUrl: "https://api.mem0.ai",
|
||||
userEmail: "",
|
||||
},
|
||||
telemetry: {
|
||||
anonymousId: "cli-anon-node",
|
||||
},
|
||||
});
|
||||
|
||||
const stdin = { end: vi.fn() };
|
||||
const child = { stdin, unref: vi.fn() };
|
||||
mockSpawn.mockReturnValue(child);
|
||||
|
||||
const { captureEvent } = await import("../src/telemetry.js");
|
||||
captureEvent("node_test_event", { case: "stdin-secret" });
|
||||
|
||||
expect(mockSpawn).toHaveBeenCalledTimes(1);
|
||||
const [execPath, args, options] = mockSpawn.mock.calls[0];
|
||||
expect(execPath).toBe(process.execPath);
|
||||
expect(args).toHaveLength(1);
|
||||
expect(String(args[0])).toContain("telemetry-sender.cjs");
|
||||
expect(JSON.stringify(args)).not.toContain("m0-node-secret");
|
||||
expect(options).toMatchObject({ detached: true, stdio: ["pipe", "ignore", "ignore"] });
|
||||
|
||||
expect(stdin.end).toHaveBeenCalledTimes(1);
|
||||
const payload = JSON.parse(stdin.end.mock.calls[0][0]);
|
||||
expect(payload.mem0ApiKey).toBe("m0-node-secret");
|
||||
expect(payload.payload.event).toBe("node_test_event");
|
||||
expect(child.unref).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -8,4 +8,10 @@ export default defineConfig({
|
||||
define: {
|
||||
__CLI_VERSION__: JSON.stringify(pkg.version),
|
||||
},
|
||||
test: {
|
||||
// Integration tests spawn the CLI via `npx tsx` (15s subprocess
|
||||
// timeout); the first spawn in a file pays a cold-start cost that can
|
||||
// exceed vitest's 5s default on CI runners.
|
||||
testTimeout: 30_000,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "mem0-cli"
|
||||
version = "0.2.5"
|
||||
version = "0.2.10"
|
||||
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.4"
|
||||
__version__ = "0.2.10"
|
||||
|
||||
@@ -914,6 +914,65 @@ def identify(
|
||||
run_identify(name)
|
||||
|
||||
|
||||
@app.command(name="whoami", rich_help_panel="Setup")
|
||||
def whoami_cmd() -> None:
|
||||
"""Print your AGENTRUSH identifier (default_user_id).
|
||||
|
||||
Example:
|
||||
mem0 whoami
|
||||
"""
|
||||
from mem0_cli.commands.whoami_cmd import run_whoami
|
||||
|
||||
run_whoami()
|
||||
|
||||
|
||||
# ── AGENTRUSH sub-app ─────────────────────────────────────────────────────
|
||||
|
||||
agent_rush_app = typer.Typer(
|
||||
name="agent-rush",
|
||||
help="AGENTRUSH game commands",
|
||||
no_args_is_help=True,
|
||||
rich_markup_mode="rich",
|
||||
)
|
||||
|
||||
|
||||
@agent_rush_app.callback(invoke_without_command=True)
|
||||
def _agent_rush_callback(ctx: typer.Context) -> None:
|
||||
if ctx.invoked_subcommand:
|
||||
_fire_telemetry(f"agent-rush.{ctx.invoked_subcommand}")
|
||||
|
||||
|
||||
@agent_rush_app.command(name="add")
|
||||
def agent_rush_add(
|
||||
content: str = typer.Argument(..., help="Memory content (50-1000 characters, no URLs)."),
|
||||
) -> None:
|
||||
"""Submit a memory to AGENTRUSH.
|
||||
|
||||
Example:
|
||||
mem0 agent-rush add "I enjoy solving constraint-satisfaction problems."
|
||||
"""
|
||||
from mem0_cli.commands.agent_rush_cmd import run_agent_rush_add
|
||||
|
||||
run_agent_rush_add(content)
|
||||
|
||||
|
||||
@agent_rush_app.command(name="search")
|
||||
def agent_rush_search(
|
||||
query: str = typer.Argument(..., help="Search query."),
|
||||
) -> None:
|
||||
"""Search AGENTRUSH memories.
|
||||
|
||||
Example:
|
||||
mem0 agent-rush search "constraint satisfaction"
|
||||
"""
|
||||
from mem0_cli.commands.agent_rush_cmd import run_agent_rush_search
|
||||
|
||||
run_agent_rush_search(query)
|
||||
|
||||
|
||||
app.add_typer(agent_rush_app, name="agent-rush", rich_help_panel="Setup")
|
||||
|
||||
|
||||
# (entity_app registered at module level, below sub-group definitions)
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
|
||||
import httpx
|
||||
|
||||
@@ -11,6 +12,10 @@ from mem0_cli.backend.base import Backend
|
||||
from mem0_cli.config import PlatformConfig
|
||||
|
||||
|
||||
def _encode_path_segment(value: Any) -> str:
|
||||
return quote(str(value), safe="")
|
||||
|
||||
|
||||
class PlatformBackend(Backend):
|
||||
"""Backend that talks to the mem0 Platform API."""
|
||||
|
||||
@@ -196,7 +201,11 @@ class PlatformBackend(Backend):
|
||||
)
|
||||
|
||||
def get(self, memory_id: str) -> dict:
|
||||
return self._request("GET", f"/v1/memories/{memory_id}/", params={"source": "CLI"})
|
||||
return self._request(
|
||||
"GET",
|
||||
f"/v1/memories/{_encode_path_segment(memory_id)}/",
|
||||
params={"source": "CLI"},
|
||||
)
|
||||
|
||||
def list_memories(
|
||||
self,
|
||||
@@ -250,7 +259,11 @@ class PlatformBackend(Backend):
|
||||
if metadata:
|
||||
payload["metadata"] = metadata
|
||||
payload["source"] = "CLI"
|
||||
return self._request("PUT", f"/v1/memories/{memory_id}/", json=payload)
|
||||
return self._request(
|
||||
"PUT",
|
||||
f"/v1/memories/{_encode_path_segment(memory_id)}/",
|
||||
json=payload,
|
||||
)
|
||||
|
||||
def delete(
|
||||
self,
|
||||
@@ -274,7 +287,11 @@ class PlatformBackend(Backend):
|
||||
params["run_id"] = run_id
|
||||
return self._request("DELETE", "/v1/memories/", params=params)
|
||||
elif memory_id:
|
||||
return self._request("DELETE", f"/v1/memories/{memory_id}/", params={"source": "CLI"})
|
||||
return self._request(
|
||||
"DELETE",
|
||||
f"/v1/memories/{_encode_path_segment(memory_id)}/",
|
||||
params={"source": "CLI"},
|
||||
)
|
||||
else:
|
||||
raise ValueError("Either memory_id or --all is required")
|
||||
|
||||
@@ -296,13 +313,17 @@ class PlatformBackend(Backend):
|
||||
entities = {t: v for t, v in type_map.items() if v}
|
||||
if not entities:
|
||||
raise ValueError("At least one entity ID is required for delete_entities.")
|
||||
# Delete each provided entity via the v2 path-based endpoint
|
||||
result: dict = {}
|
||||
# Delete each provided entity via the v2 path-based endpoint. Key each
|
||||
# response by entity type so a multi-entity delete (e.g. --user-id and
|
||||
# --agent-id together) doesn't discard everything but the last result.
|
||||
results: dict = {}
|
||||
for entity_type, entity_id in entities.items():
|
||||
result = self._request(
|
||||
"DELETE", f"/v2/entities/{entity_type}/{entity_id}/", params={"source": "CLI"}
|
||||
results[entity_type] = self._request(
|
||||
"DELETE",
|
||||
f"/v2/entities/{_encode_path_segment(entity_type)}/{_encode_path_segment(entity_id)}/",
|
||||
params={"source": "CLI"},
|
||||
)
|
||||
return result
|
||||
return results
|
||||
|
||||
def ping(self, timeout: float | None = None) -> dict:
|
||||
"""Call the ping endpoint and return the raw response.
|
||||
@@ -346,7 +367,7 @@ class PlatformBackend(Backend):
|
||||
return result if isinstance(result, list) else result.get("results", [])
|
||||
|
||||
def get_event(self, event_id: str) -> dict:
|
||||
return self._request("GET", f"/v1/event/{event_id}/")
|
||||
return self._request("GET", f"/v1/event/{_encode_path_segment(event_id)}/")
|
||||
|
||||
|
||||
class AuthError(Exception):
|
||||
|
||||
@@ -218,7 +218,7 @@ def claim_via_otp(config: Mem0Config, *, email: str, code: str | None = None) ->
|
||||
print_error(err_console, f"Claim failed: {detail}")
|
||||
if code_str == "email_already_claimed":
|
||||
console.print(
|
||||
f" [{DIM_COLOR}]Tip: this email already has a Mem0 account. Sign in there and run `mem0 link <key>` to attach this agent.[/]"
|
||||
f" [{DIM_COLOR}]Tip: this email already has a Mem0 account. Sign in at app.mem0.ai with your existing credentials.[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
"""mem0 agent-rush — AGENTRUSH game commands.
|
||||
|
||||
Wraps the platform's /v1/agent-rush/{memories/, memories/search/} endpoints.
|
||||
Hardcoded routing; no flags needed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import httpx
|
||||
import typer
|
||||
from rich.console import Console
|
||||
|
||||
from mem0_cli.branding import print_error, print_success
|
||||
from mem0_cli.config import load_config, save_config
|
||||
|
||||
console = Console()
|
||||
err_console = Console(stderr=True)
|
||||
|
||||
_PII_WARNING_LINES = (
|
||||
"",
|
||||
"[yellow]⚠️ AGENTRUSH memories are PUBLIC — visible to any other player.[/yellow]",
|
||||
"[yellow] Do not include real names, emails, secrets, work content, or PII.[/yellow]",
|
||||
"",
|
||||
)
|
||||
|
||||
_SOURCE_HEADERS = {
|
||||
"X-Mem0-Source": "cli",
|
||||
"X-Mem0-Client-Language": "python",
|
||||
"X-Mem0-Mode": "agent-rush",
|
||||
}
|
||||
|
||||
_ERROR_HINTS = {
|
||||
"agentrush_search_first": "Run 3 'mem0 agent-rush search' commands before adding.",
|
||||
"agentrush_search_quota": "You've used your 3 lifetime searches.",
|
||||
"agentrush_add_quota": "You've used your 3 lifetime adds.",
|
||||
"agentrush_not_agent_mode": "Re-run 'mem0 init --agent' to bootstrap an agent-mode key.",
|
||||
"agentrush_length": "Memory text must be 50-1000 characters.",
|
||||
"agentrush_no_urls": "URLs are not allowed.",
|
||||
"agentrush_blocklist": "Content contains a blocked term.",
|
||||
"agentrush_global_quota": "Event-wide cap reached. Try again later.",
|
||||
"agentrush_not_provisioned": "AGENTRUSH is not provisioned in this environment.",
|
||||
}
|
||||
|
||||
|
||||
def _call(path: str, body: dict) -> dict:
|
||||
config = load_config()
|
||||
if not config.platform.api_key:
|
||||
print_error(err_console, "Not initialized. Run `mem0 init --agent` first.")
|
||||
raise typer.Exit(1)
|
||||
base_url = (config.platform.base_url or "https://api.mem0.ai").rstrip("/")
|
||||
try:
|
||||
with httpx.Client(timeout=30.0) as client:
|
||||
resp = client.post(
|
||||
f"{base_url}{path}",
|
||||
headers={
|
||||
**_SOURCE_HEADERS,
|
||||
"Authorization": f"Token {config.platform.api_key}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
json=body,
|
||||
)
|
||||
except httpx.HTTPError as exc:
|
||||
print_error(err_console, f"Network error: {exc}")
|
||||
raise typer.Exit(1) from exc
|
||||
try:
|
||||
data = resp.json()
|
||||
except Exception:
|
||||
data = {}
|
||||
if resp.status_code >= 400:
|
||||
code = (
|
||||
(data.get("error") or {}).get("code", "unknown")
|
||||
if isinstance(data, dict)
|
||||
else "unknown"
|
||||
)
|
||||
print_error(err_console, f"AGENTRUSH error: {code}")
|
||||
hint = _ERROR_HINTS.get(code)
|
||||
if hint:
|
||||
console.print(f" [dim]{hint}[/dim]")
|
||||
raise typer.Exit(1)
|
||||
return data
|
||||
|
||||
|
||||
def _ensure_warning_acknowledged() -> None:
|
||||
"""Block the first interactive add on the PII warning; pass-through for agents.
|
||||
|
||||
Interactive (TTY): show prompt, require explicit 'y', persist
|
||||
`agent_rush.acknowledged_at` so we never ask the same machine twice.
|
||||
|
||||
Non-interactive (no TTY — typical when an agent runs the CLI): surface
|
||||
the warning to stderr for the human reading the agent transcript and
|
||||
proceed without prompting (agents can't answer y/N).
|
||||
"""
|
||||
config = load_config()
|
||||
if config.agent_rush.acknowledged_at:
|
||||
return
|
||||
|
||||
is_tty = sys.stdin.isatty() and sys.stdout.isatty()
|
||||
if not is_tty:
|
||||
for line in _PII_WARNING_LINES:
|
||||
err_console.print(line)
|
||||
return
|
||||
|
||||
for line in _PII_WARNING_LINES:
|
||||
console.print(line)
|
||||
answer = typer.prompt(" Continue? [y/N]", default="N", show_default=False).strip().lower()
|
||||
if answer not in ("y", "yes"):
|
||||
print_error(err_console, "Aborted.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
config.agent_rush.acknowledged_at = datetime.now(timezone.utc).isoformat()
|
||||
save_config(config)
|
||||
|
||||
|
||||
def run_agent_rush_add(content: str) -> None:
|
||||
_ensure_warning_acknowledged()
|
||||
result = _call("/v1/agent-rush/memories/", {"content": content})
|
||||
event_id = result.get("event_id", "?")
|
||||
print_success(console, f"Memory submitted (event_id: {event_id})")
|
||||
|
||||
|
||||
def run_agent_rush_search(query: str) -> None:
|
||||
result = _call("/v1/agent-rush/memories/search/", {"query": query})
|
||||
memories = result.get("results") or result.get("memories") or []
|
||||
if not memories:
|
||||
console.print("[dim](no results)[/dim]")
|
||||
return
|
||||
for i, m in enumerate(memories[:5], start=1):
|
||||
text = m.get("memory") if isinstance(m, dict) else str(m)
|
||||
console.print(f" {i}. {text}")
|
||||
@@ -0,0 +1,25 @@
|
||||
"""mem0 whoami — print the active agent's default_user_id (AGENTRUSH identifier)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import typer
|
||||
from rich.console import Console
|
||||
|
||||
from mem0_cli.branding import BRAND_COLOR, print_error, print_info
|
||||
from mem0_cli.config import load_config
|
||||
|
||||
console = Console()
|
||||
err_console = Console(stderr=True)
|
||||
|
||||
|
||||
def run_whoami() -> None:
|
||||
config = load_config()
|
||||
session_id = config.platform.default_user_id if config.platform else None
|
||||
if not session_id:
|
||||
print_error(
|
||||
err_console,
|
||||
"No default_user_id found. Run `mem0 init --agent` first.",
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
console.print(f"Your AGENTRUSH identifier: [{BRAND_COLOR}]{session_id}[/{BRAND_COLOR}]")
|
||||
print_info(console, "Find your row at https://mem0.ai/agentrush")
|
||||
@@ -51,12 +51,20 @@ class TelemetryConfig:
|
||||
anonymous_id: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentRushConfig:
|
||||
# ISO timestamp the human acknowledged the "memories are public" warning.
|
||||
# Empty until first interactive `mem0 agent-rush add`.
|
||||
acknowledged_at: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Mem0Config:
|
||||
version: int = CONFIG_VERSION
|
||||
defaults: DefaultsConfig = field(default_factory=DefaultsConfig)
|
||||
platform: PlatformConfig = field(default_factory=PlatformConfig)
|
||||
telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)
|
||||
agent_rush: AgentRushConfig = field(default_factory=AgentRushConfig)
|
||||
|
||||
|
||||
SHORT_KEY_ALIASES: dict[str, str] = {
|
||||
@@ -105,6 +113,9 @@ def load_config() -> Mem0Config:
|
||||
telemetry = data.get("telemetry", {})
|
||||
config.telemetry.anonymous_id = telemetry.get("anonymous_id", "")
|
||||
|
||||
agent_rush = data.get("agent_rush", {})
|
||||
config.agent_rush.acknowledged_at = agent_rush.get("acknowledged_at", "")
|
||||
|
||||
# Environment variable overrides
|
||||
env_key = os.environ.get("MEM0_API_KEY")
|
||||
if env_key:
|
||||
@@ -158,6 +169,9 @@ def save_config(config: Mem0Config) -> None:
|
||||
"telemetry": {
|
||||
"anonymous_id": config.telemetry.anonymous_id,
|
||||
},
|
||||
"agent_rush": {
|
||||
"acknowledged_at": config.agent_rush.acknowledged_at,
|
||||
},
|
||||
}
|
||||
|
||||
with open(CONFIG_FILE, "w") as f:
|
||||
@@ -221,7 +235,10 @@ def set_nested_value(config: Mem0Config, dotted_key: str, value: str) -> bool:
|
||||
if isinstance(current, bool):
|
||||
value = value.lower() in ("true", "1", "yes") # type: ignore[assignment]
|
||||
elif isinstance(current, int):
|
||||
value = int(value) # type: ignore[assignment]
|
||||
try:
|
||||
value = int(value) # type: ignore[assignment]
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
setattr(obj, final_key, value)
|
||||
return True
|
||||
|
||||
@@ -20,8 +20,8 @@ def format_memories_text(console: Console, memories: list[dict], title: str = "m
|
||||
console.print(f"\n[{BRAND_COLOR}]Found {count} {title}:[/]\n")
|
||||
|
||||
for i, mem in enumerate(memories, 1):
|
||||
memory_text = mem.get("memory", mem.get("text", ""))
|
||||
mem_id = mem.get("id", "")[:8]
|
||||
memory_text = mem.get("memory") or mem.get("text") or ""
|
||||
mem_id = (mem.get("id") or "")[:8]
|
||||
score = mem.get("score")
|
||||
created = _format_date(mem.get("created_at"))
|
||||
category = mem.get("categories", [None])
|
||||
@@ -67,8 +67,8 @@ def format_memories_table(
|
||||
table.add_column("Created", max_width=12)
|
||||
|
||||
for mem in memories:
|
||||
mem_id = mem.get("id", "")
|
||||
memory_text = mem.get("memory", mem.get("text", ""))
|
||||
mem_id = mem.get("id") or ""
|
||||
memory_text = mem.get("memory") or mem.get("text") or ""
|
||||
if len(memory_text) > 60:
|
||||
memory_text = memory_text[:57] + "..."
|
||||
categories = mem.get("categories", [])
|
||||
@@ -104,8 +104,8 @@ def format_single_memory(console: Console, mem: dict, output: str = "text") -> N
|
||||
format_json(console, mem)
|
||||
return
|
||||
|
||||
memory_text = mem.get("memory", mem.get("text", ""))
|
||||
mem_id = mem.get("id", "")
|
||||
memory_text = mem.get("memory") or mem.get("text") or ""
|
||||
mem_id = mem.get("id") or ""
|
||||
|
||||
lines = []
|
||||
lines.append(f" [white bold]{memory_text}[/]")
|
||||
|
||||
@@ -137,12 +137,19 @@ def capture_event(
|
||||
"anon_distinct_id_to_alias": anon_id_to_alias,
|
||||
}
|
||||
|
||||
subprocess.Popen(
|
||||
[sys.executable, "-m", "mem0_cli.telemetry_sender", json.dumps(context)],
|
||||
child = subprocess.Popen(
|
||||
[sys.executable, "-m", "mem0_cli.telemetry_sender"],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=True,
|
||||
close_fds=True,
|
||||
text=True,
|
||||
)
|
||||
if child.stdin:
|
||||
with contextlib.suppress(Exception):
|
||||
child.stdin.write(json.dumps(context))
|
||||
with contextlib.suppress(Exception):
|
||||
child.stdin.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Standalone telemetry sender — runs as a detached subprocess.
|
||||
|
||||
Usage: python -m mem0_cli.telemetry_sender '<json context>'
|
||||
Usage: python -m mem0_cli.telemetry_sender (JSON context is read from stdin;
|
||||
a single argv argument is still accepted as a legacy fallback)
|
||||
|
||||
This module is spawned by telemetry.capture_event() and runs independently
|
||||
of the parent CLI process. It:
|
||||
@@ -20,8 +21,18 @@ import sys
|
||||
import urllib.request
|
||||
|
||||
|
||||
def _load_context() -> dict:
|
||||
"""Load telemetry context from stdin, falling back to argv for compatibility."""
|
||||
raw = ""
|
||||
if not sys.stdin.isatty():
|
||||
raw = sys.stdin.read().strip()
|
||||
if not raw and len(sys.argv) > 1:
|
||||
raw = sys.argv[1]
|
||||
return json.loads(raw)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ctx = json.loads(sys.argv[1])
|
||||
ctx = _load_context()
|
||||
payload = ctx["payload"]
|
||||
|
||||
if ctx.get("needs_email") and ctx.get("mem0_api_key"):
|
||||
|
||||
@@ -32,12 +32,13 @@ def _run(args: list[str], home_dir: str | None = None) -> subprocess.CompletedPr
|
||||
if key.startswith("MEM0_"):
|
||||
del env[key]
|
||||
env.pop("FORCE_COLOR", None)
|
||||
env["PYTHONIOENCODING"] = "utf-8"
|
||||
if home_dir:
|
||||
env["HOME"] = home_dir
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "mem0_cli", *args],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
env=env,
|
||||
timeout=15,
|
||||
)
|
||||
@@ -99,12 +100,13 @@ class TestArgvPreprocessing:
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "mem0_cli", "init", "--agent"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
env={
|
||||
**{k: v for k, v in os.environ.items() if not k.startswith("MEM0_")},
|
||||
"HOME": clean_home,
|
||||
"MEM0_BASE_URL": "http://127.0.0.1:1", # blackhole
|
||||
"FORCE_COLOR": "0",
|
||||
"PYTHONIOENCODING": "utf-8",
|
||||
},
|
||||
timeout=15,
|
||||
)
|
||||
@@ -133,12 +135,13 @@ class TestJsonEnvelopeParity:
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "mem0_cli", "init", "--agent", "--json"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
env={
|
||||
**{k: v for k, v in os.environ.items() if not k.startswith("MEM0_")},
|
||||
"HOME": clean_home,
|
||||
"MEM0_BASE_URL": "http://127.0.0.1:1",
|
||||
"FORCE_COLOR": "0",
|
||||
"PYTHONIOENCODING": "utf-8",
|
||||
},
|
||||
timeout=15,
|
||||
)
|
||||
|
||||
@@ -49,6 +49,7 @@ def _run(
|
||||
if key.startswith("MEM0_"):
|
||||
del env[key]
|
||||
env.pop("FORCE_COLOR", None)
|
||||
env["PYTHONIOENCODING"] = "utf-8"
|
||||
if home_dir:
|
||||
env["HOME"] = home_dir
|
||||
if env_override:
|
||||
@@ -56,7 +57,7 @@ def _run(
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "mem0_cli", *args],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
env=env,
|
||||
)
|
||||
return subprocess.CompletedProcess(
|
||||
|
||||
@@ -8,8 +8,8 @@ from io import StringIO
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from click.exceptions import Exit as ClickExit
|
||||
from rich.console import Console
|
||||
from typer import Exit as TyperExit
|
||||
|
||||
from mem0_cli.commands.config_cmd import (
|
||||
cmd_config_get,
|
||||
@@ -181,7 +181,7 @@ class TestAddCommand:
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
patch("mem0_cli.commands.memory._stdin_is_piped", return_value=False),
|
||||
pytest.raises((SystemExit, ClickExit)),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_add(
|
||||
mock_backend,
|
||||
@@ -206,7 +206,7 @@ class TestAddCommand:
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
pytest.raises((SystemExit, ClickExit)),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_add(
|
||||
mock_backend,
|
||||
@@ -764,7 +764,7 @@ class TestImportCommand:
|
||||
with (
|
||||
patch("mem0_cli.commands.utils.console", console),
|
||||
patch("mem0_cli.commands.utils.err_console", err_console),
|
||||
pytest.raises((SystemExit, ClickExit)),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_import(mock_backend, "/nonexistent/file.json", user_id=None, agent_id=None)
|
||||
|
||||
@@ -801,7 +801,7 @@ class TestEntitiesListCommand:
|
||||
with (
|
||||
patch("mem0_cli.commands.entities.console", console),
|
||||
patch("mem0_cli.commands.entities.err_console", err_console),
|
||||
pytest.raises((SystemExit, ClickExit)),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_entities_list(mock_backend, "invalid", output="table")
|
||||
|
||||
@@ -944,7 +944,7 @@ class TestEntitiesDeleteCommand:
|
||||
with (
|
||||
patch("mem0_cli.commands.entities.console", console),
|
||||
patch("mem0_cli.commands.entities.err_console", err_console),
|
||||
pytest.raises((SystemExit, ClickExit)),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_entities_delete(
|
||||
mock_backend,
|
||||
@@ -1308,7 +1308,7 @@ class TestAgentMode:
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
patch("sys.stdout", captured_stdout),
|
||||
pytest.raises((SystemExit, ClickExit)),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_get(mock_backend, "bad-id", output="text")
|
||||
|
||||
|
||||
@@ -67,7 +67,8 @@ class TestConfig:
|
||||
from mem0_cli.config import CONFIG_FILE
|
||||
|
||||
mode = os.stat(CONFIG_FILE).st_mode & 0o777
|
||||
assert mode == 0o600
|
||||
if os.name != "nt":
|
||||
assert mode == 0o600
|
||||
|
||||
def test_defaults_save_and_load(self, isolate_config):
|
||||
config = Mem0Config()
|
||||
@@ -138,6 +139,11 @@ class TestNestedAccess:
|
||||
assert set_nested_value(config, "platform.api_key", "new-key")
|
||||
assert config.platform.api_key == "new-key"
|
||||
|
||||
def test_set_int_value_rejects_invalid_input(self):
|
||||
config = Mem0Config()
|
||||
assert set_nested_value(config, "version", "abc") is False
|
||||
assert config.version == 1
|
||||
|
||||
def test_set_nonexistent_key(self):
|
||||
config = Mem0Config()
|
||||
assert set_nested_value(config, "nonexistent.key", "val") is False
|
||||
|
||||
@@ -54,6 +54,11 @@ class TestTextFormat:
|
||||
output = buf.getvalue()
|
||||
assert "Found 0" in output
|
||||
|
||||
def test_format_memories_text_handles_null_fields(self):
|
||||
console, buf = _make_console()
|
||||
format_memories_text(console, [{"id": None, "memory": None, "created_at": None}])
|
||||
assert "Found 1 memories" in buf.getvalue()
|
||||
|
||||
|
||||
class TestTableFormat:
|
||||
def test_format_memories_table(self):
|
||||
@@ -70,6 +75,13 @@ class TestTableFormat:
|
||||
# Should still render (empty table)
|
||||
assert "ID" in output
|
||||
|
||||
def test_format_memories_table_handles_null_fields(self):
|
||||
console, buf = _make_console()
|
||||
format_memories_table(console, [{"id": None, "memory": None, "created_at": None}])
|
||||
output = buf.getvalue()
|
||||
assert "ID" in output
|
||||
assert "Memory" in output
|
||||
|
||||
|
||||
class TestSingleMemory:
|
||||
def test_format_single_memory_text(self):
|
||||
@@ -87,6 +99,17 @@ class TestSingleMemory:
|
||||
output = buf.getvalue()
|
||||
assert '"memory"' in output
|
||||
|
||||
def test_format_single_memory_handles_null_fields(self):
|
||||
console, buf = _make_console()
|
||||
format_single_memory(
|
||||
console,
|
||||
{"id": None, "memory": None, "text": "Fallback memory", "created_at": None},
|
||||
"text",
|
||||
)
|
||||
output = buf.getvalue()
|
||||
assert "Fallback memory" in output
|
||||
assert "ID:" not in output
|
||||
|
||||
|
||||
class TestAddResult:
|
||||
def test_format_add_result_text(self):
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Tests for the Platform backend (mem0 Platform API client)."""
|
||||
|
||||
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:
|
||||
# api_key/base_url are only used to build the httpx client; every test here
|
||||
# patches _request, so no real network calls are made.
|
||||
return PlatformBackend(PlatformConfig(api_key="test-key", base_url="https://api.mem0.ai"))
|
||||
|
||||
|
||||
class TestDeleteEntities:
|
||||
def test_multiple_entities_returns_all_results(self):
|
||||
backend = _make_backend()
|
||||
responses = {
|
||||
"/v2/entities/user/alice/": {"message": "user deleted"},
|
||||
"/v2/entities/agent/bob/": {"message": "agent deleted"},
|
||||
}
|
||||
with patch.object(backend, "_request") as mock_request:
|
||||
mock_request.side_effect = lambda method, path, **kw: responses[path]
|
||||
result = backend.delete_entities(user_id="alice", agent_id="bob")
|
||||
|
||||
# Regression: previously only the last entity's response survived.
|
||||
assert result == {
|
||||
"user": {"message": "user deleted"},
|
||||
"agent": {"message": "agent deleted"},
|
||||
}
|
||||
assert mock_request.call_count == 2
|
||||
|
||||
def test_single_entity_keyed_by_type(self):
|
||||
backend = _make_backend()
|
||||
with patch.object(backend, "_request", return_value={"message": "user deleted"}):
|
||||
result = backend.delete_entities(user_id="alice")
|
||||
assert result == {"user": {"message": "user deleted"}}
|
||||
|
||||
def test_no_entities_raises(self):
|
||||
backend = _make_backend()
|
||||
import pytest
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
backend.delete_entities()
|
||||
@@ -0,0 +1,43 @@
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from mem0_cli.backend.platform import PlatformBackend
|
||||
|
||||
|
||||
def _backend(sample_config):
|
||||
backend = PlatformBackend(sample_config.platform)
|
||||
backend._client = MagicMock()
|
||||
backend._client.request.return_value = MagicMock(
|
||||
status_code=200,
|
||||
json=lambda: {"message": "ok"},
|
||||
headers={},
|
||||
raise_for_status=lambda: None,
|
||||
)
|
||||
return backend
|
||||
|
||||
|
||||
def test_memory_id_path_segments_are_encoded(sample_config):
|
||||
backend = _backend(sample_config)
|
||||
|
||||
backend.get("mem/a?b#c")
|
||||
backend.update("mem/a?b#c", content="updated")
|
||||
backend.delete("mem/a?b#c")
|
||||
|
||||
paths = [call.args[1] for call in backend._client.request.call_args_list]
|
||||
assert paths == [
|
||||
"/v1/memories/mem%2Fa%3Fb%23c/",
|
||||
"/v1/memories/mem%2Fa%3Fb%23c/",
|
||||
"/v1/memories/mem%2Fa%3Fb%23c/",
|
||||
]
|
||||
|
||||
|
||||
def test_entity_and_event_path_segments_are_encoded(sample_config):
|
||||
backend = _backend(sample_config)
|
||||
|
||||
backend.delete_entities(user_id="org/team?active#frag")
|
||||
backend.get_event("evt/a?b#c")
|
||||
|
||||
paths = [call.args[1] for call in backend._client.request.call_args_list]
|
||||
assert paths == [
|
||||
"/v2/entities/user/org%2Fteam%3Factive%23frag/",
|
||||
"/v1/event/evt%2Fa%3Fb%23c/",
|
||||
]
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Tests for telemetry subprocess secret handling."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from mem0_cli.config import Mem0Config, save_config
|
||||
from mem0_cli.telemetry import capture_event
|
||||
from mem0_cli.telemetry_sender import _load_context
|
||||
|
||||
|
||||
class _CaptureStdin:
|
||||
def __init__(self):
|
||||
self.buffer = ""
|
||||
self.closed = False
|
||||
|
||||
def write(self, value: str) -> None:
|
||||
self.buffer += value
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
class _DummyProcess:
|
||||
def __init__(self):
|
||||
self.stdin = _CaptureStdin()
|
||||
|
||||
|
||||
def test_capture_event_writes_context_to_stdin_not_argv(isolate_config, monkeypatch):
|
||||
config = Mem0Config()
|
||||
config.platform.api_key = "m0-test-secret"
|
||||
config.telemetry.anonymous_id = "cli-anon-test"
|
||||
save_config(config)
|
||||
|
||||
captured: dict[str, object] = {}
|
||||
proc = _DummyProcess()
|
||||
|
||||
def fake_popen(args, **kwargs):
|
||||
captured["args"] = args
|
||||
captured["kwargs"] = kwargs
|
||||
return proc
|
||||
|
||||
monkeypatch.setattr("mem0_cli.telemetry.subprocess.Popen", fake_popen)
|
||||
|
||||
capture_event("unit_test_event", {"case": "stdin-secret"})
|
||||
|
||||
argv = captured["args"]
|
||||
assert argv == [sys.executable, "-m", "mem0_cli.telemetry_sender"]
|
||||
assert all("m0-test-secret" not in arg for arg in argv)
|
||||
|
||||
kwargs = captured["kwargs"]
|
||||
assert kwargs["stdin"] == subprocess.PIPE
|
||||
assert kwargs["text"] is True
|
||||
|
||||
ctx = json.loads(proc.stdin.buffer)
|
||||
assert ctx["mem0_api_key"] == "m0-test-secret"
|
||||
assert ctx["payload"]["event"] == "unit_test_event"
|
||||
|
||||
assert proc.stdin.closed
|
||||
|
||||
|
||||
def test_load_context_reads_from_stdin(monkeypatch):
|
||||
monkeypatch.setattr("sys.argv", ["telemetry_sender"])
|
||||
monkeypatch.setattr("sys.stdin", io.StringIO('{"payload": {"event": "stdin"}}'))
|
||||
|
||||
ctx = _load_context()
|
||||
|
||||
assert ctx["payload"]["event"] == "stdin"
|
||||
|
||||
|
||||
def test_load_context_falls_back_to_argv(monkeypatch):
|
||||
monkeypatch.setattr("sys.argv", ["telemetry_sender", '{"payload": {"event": "argv"}}'])
|
||||
monkeypatch.setattr("sys.stdin", io.StringIO(""))
|
||||
|
||||
ctx = _load_context()
|
||||
|
||||
assert ctx["payload"]["event"] == "argv"
|
||||
+1
-1
@@ -24,7 +24,7 @@ mintlify dev
|
||||
|
||||
### Publishing Changes
|
||||
|
||||
Install our Github App to auto propagate changes from your repo to your deployment. Changes will be deployed to production automatically after pushing to the default branch. Find the link to install on your dashboard.
|
||||
Install our GitHub App to auto-propagate changes from your repo to your deployment. Changes will be deployed to production automatically after pushing to the default branch. Find the link to install on your dashboard.
|
||||
|
||||
#### Troubleshooting
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{/* Subtle, value-anchored nudge to star the repo. Drop in at peak-end "win" moments in the OSS docs (after a successful add/search, a server bootstrap, etc.). Keep it off the Platform/API pages. */}
|
||||
{/* Clicks are tracked via PostHog autocapture: the data-ph-capture-attribute-cta below tags each click with cta="star-on-github" so it's filterable as an event property. Metric = count of $autocapture where cta = star-on-github; break down by Current URL to see which win-moment converts. */}
|
||||
<Callout icon="star" iconType="solid" color="#FACC15">
|
||||
**Using Mem0?** <a href="https://github.com/mem0ai/mem0" data-ph-capture-attribute-cta="star-on-github">Star us on GitHub</a> to help more developers discover memory for AI apps.
|
||||
</Callout>
|
||||
@@ -97,7 +97,7 @@ Get your API key from the <a href="https://app.mem0.ai/dashboard/api-keys?utm_so
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Add Your First Memory" icon="rocket" href="/api-reference/memory/add-memories">
|
||||
Start storing memories via the REST API
|
||||
</Card>
|
||||
@@ -105,4 +105,8 @@ Get your API key from the <a href="https://app.mem0.ai/dashboard/api-keys?utm_so
|
||||
<Card title="Search with Filters" icon="filter" href="/api-reference/memory/search-memories">
|
||||
Learn advanced search and filtering techniques
|
||||
</Card>
|
||||
|
||||
<Card title="Build with cookbooks" icon="book-open" href="/cookbooks/overview">
|
||||
See the API used end to end in real projects.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@ description: "Add facts, messages, or metadata to a user memory store with async
|
||||
openapi: post /v3/memories/add/
|
||||
---
|
||||
|
||||
Extract and store memories from a conversation using the V3 additive pipeline. The endpoint uses single-pass ADD-only extraction — one LLM call, no UPDATE/DELETE. Memories accumulate over time; nothing is overwritten.
|
||||
Extract and store memories from a conversation using the V3 additive pipeline. The endpoint uses single-pass ADD-only extraction: one LLM call, no UPDATE/DELETE. Memories accumulate over time; nothing is overwritten.
|
||||
|
||||
## Endpoint
|
||||
|
||||
@@ -50,6 +50,7 @@ Provide conversation messages for Mem0 to extract memories from. At least one en
|
||||
| `app_id` | string | No* | Associates the memory with an app. |
|
||||
| `metadata` | object | Optional | Custom key/value metadata (e.g., `{"topic": "preferences"}`). |
|
||||
| `infer` | boolean (default `true`) | Optional | Set to `false` to skip inference and store the provided text as-is. |
|
||||
| `expiration_date` | string | Optional | Date in `YYYY-MM-DD` format. The memory is visible through this date and hidden by default after it passes. |
|
||||
|
||||
> \* At least one entity ID (`user_id`, `agent_id`, `app_id`, or `run_id`) is required.
|
||||
|
||||
@@ -64,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"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -83,3 +83,11 @@ The request is queued for background processing. The response contains an `event
|
||||
<Info>
|
||||
Poll the event status via `GET /v1/event/{event_id}/`. Status will be `SUCCEEDED` or `FAILED` once processing completes.
|
||||
</Info>
|
||||
|
||||
<Info>
|
||||
Memories with `expiration_date` remain stored after they expire. Search and get-all hide them by default; pass `show_expired: true` to include them.
|
||||
</Info>
|
||||
|
||||
<Info>
|
||||
Python uses `expiration_date`; TypeScript uses `expirationDate`.
|
||||
</Info>
|
||||
|
||||
@@ -4,4 +4,4 @@ description: "Submit an export job to create a structured memory export using a
|
||||
openapi: post /v1/exports/
|
||||
---
|
||||
|
||||
Submit a job to create a structured export of memories using a customizable Pydantic schema. This process may take some time to complete, especially if you're exporting a large number of memories. You can tailor the export by applying various filters (e.g., `user_id`, `agent_id`, `run_id`, or `session_id`) and by modifying the Pydantic schema to ensure the final data matches your exact needs.
|
||||
Submit a job to create a structured export of memories using a customizable Pydantic schema. This process may take some time to complete, especially if you're exporting a large number of memories. You can tailor the export by applying various filters (e.g., `user_id`, `agent_id`, `app_id`, or `run_id`) and by modifying the Pydantic schema to ensure the final data matches your exact needs.
|
||||
|
||||
@@ -4,7 +4,11 @@ description: "Retrieve memories with paginated results and advanced filtering us
|
||||
openapi: post /v3/memories/
|
||||
---
|
||||
|
||||
List memories scoped by filters with paginated results. Entity IDs (`user_id`, `agent_id`, `app_id`, `run_id`) **must** be passed inside the `filters` object — top-level entity IDs are rejected with 400.
|
||||
List memories scoped by filters with paginated results. Entity IDs (`user_id`, `agent_id`, `app_id`, `run_id`) **must** be passed inside the `filters` object: top-level entity IDs are rejected with 400.
|
||||
|
||||
Expired memories are hidden by default. Pass `show_expired: true` to include memories whose `expiration_date` has passed.
|
||||
|
||||
Python uses `show_expired`; TypeScript uses `showExpired`.
|
||||
|
||||
The `filters` object supports complex logical operations (AND, OR, NOT) and comparison operators:
|
||||
|
||||
@@ -32,6 +36,7 @@ memories = client.get_all(
|
||||
}
|
||||
]
|
||||
},
|
||||
show_expired=False,
|
||||
page=1,
|
||||
page_size=50
|
||||
)
|
||||
@@ -46,12 +51,14 @@ memories = client.get_all(
|
||||
{
|
||||
"id": "f4cbdb08-7062-4f3e-8eb2-9f5c80dfe64c",
|
||||
"memory": "Alex is planning a trip to San Francisco from July 1st to July 10th",
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-01T12:00:00Z",
|
||||
"updated_at": "2024-07-01T12:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "a2b8c3d4-5e6f-7g8h-9i0j-1k2l3m4n5o6p",
|
||||
"memory": "Alex prefers vegetarian restaurants",
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-05T15:30:00Z",
|
||||
"updated_at": "2024-07-05T15:30:00Z"
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@ description: "Retrieve the latest structured memory export after submitting an e
|
||||
openapi: post /v1/exports/get
|
||||
---
|
||||
|
||||
Retrieve the latest structured memory export after submitting an export job. You can filter the export by `user_id`, `run_id`, `session_id`, or `app_id` to get the most recent export matching your filters.
|
||||
Retrieve the latest structured memory export after submitting an export job. You can filter the export by `user_id`, `agent_id`, `app_id`, `run_id`, `created_at`, or `updated_at` to get the most recent export matching your filters.
|
||||
@@ -4,9 +4,13 @@ description: "Search memories with hybrid retrieval (semantic + BM25 + entity ma
|
||||
openapi: post /v3/memories/search/
|
||||
---
|
||||
|
||||
Relevance-ranked hybrid search across stored memories. V3 uses multi-signal retrieval — semantic, BM25 keyword, and entity matching scored in parallel and fused. The returned `score` is a combined `[0, 1]` value.
|
||||
Relevance-ranked hybrid search across stored memories. V3 uses multi-signal retrieval: semantic, BM25 keyword, and entity matching scored in parallel and fused. The returned `score` is a combined `[0, 1]` value.
|
||||
|
||||
Entity IDs (`user_id`, `agent_id`, `app_id`, `run_id`) **must** be passed inside the `filters` object — top-level entity IDs are rejected with 400. At least one entity ID is required.
|
||||
Entity IDs (`user_id`, `agent_id`, `app_id`, `run_id`) **must** be passed inside the `filters` object: top-level entity IDs are rejected with 400. At least one entity ID is required.
|
||||
|
||||
Expired memories are hidden by default. Pass `show_expired: true` to include memories whose `expiration_date` has passed.
|
||||
|
||||
Python uses `show_expired`; TypeScript uses `showExpired`.
|
||||
|
||||
The `filters` object supports complex logical operations (AND, OR, NOT) and comparison operators:
|
||||
- `in`: Matches any of the values specified
|
||||
@@ -20,16 +24,17 @@ The `filters` object supports complex logical operations (AND, OR, NOT) and comp
|
||||
|
||||
### Search parameter defaults
|
||||
|
||||
| Parameter | V1/V2 | V3 |
|
||||
| --- | --- | --- |
|
||||
| `top_k` | Supported (default 10) | Supported (1-1000, default 10) |
|
||||
| `threshold` | No default | Default `0.1` (pass `0.0` to disable) |
|
||||
| `rerank` | Default `true` | Default `false` (pass `true` to enable) |
|
||||
| Parameter | Default |
|
||||
| --- | --- |
|
||||
| `top_k` | `10` (range 1–1000) |
|
||||
| `threshold` | `0.1` (pass `0.0` to disable) |
|
||||
| `rerank` | `false` (pass `true` to enable) |
|
||||
|
||||
<CodeGroup>
|
||||
```python Platform API Example
|
||||
related_memories = client.search(
|
||||
query="What are Alice's hobbies?",
|
||||
show_expired=False,
|
||||
filters={
|
||||
"OR": [
|
||||
{
|
||||
@@ -54,6 +59,7 @@ related_memories = client.search(
|
||||
"category": "hobbies"
|
||||
},
|
||||
"score": 0.82,
|
||||
"expiration_date": null,
|
||||
"created_at": "2024-07-26T10:29:36.630547-07:00",
|
||||
"updated_at": null,
|
||||
"categories": ["hobbies"]
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
---
|
||||
title: 'Update Memory'
|
||||
description: "Update the content or metadata of a single memory by its unique ID using the PUT endpoint."
|
||||
description: "Update the content, metadata, timestamp, or expiration date of a single memory by its unique ID using the PUT endpoint."
|
||||
openapi: put /v1/memories/{memory_id}/
|
||||
---
|
||||
---
|
||||
|
||||
Use this endpoint to update mutable memory fields. To make a memory expire, set `expiration_date` to a `YYYY-MM-DD` date. To make it permanent again, send `expiration_date: null`.
|
||||
|
||||
```python
|
||||
client.update("mem_123", expiration_date="2030-01-31")
|
||||
client.update("mem_123", expiration_date=None)
|
||||
```
|
||||
|
||||
TypeScript uses `expirationDate`.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Remove Organization Member"
|
||||
description: "Remove a member from an organization to revoke their access to its projects and resources."
|
||||
openapi: "delete /api/v1/orgs/organizations/{org_id}/members/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Update Organization Member"
|
||||
description: "Update an existing member's role within an organization to change their permissions and access level."
|
||||
openapi: "put /api/v1/orgs/organizations/{org_id}/members/"
|
||||
---
|
||||
@@ -14,7 +14,7 @@ Organizations and projects are **optional** features. You can use Mem0 without t
|
||||
|
||||
## Key Capabilities
|
||||
|
||||
- **Multi-org/project Support**: Specify organization and project when initializing the Mem0 client to attribute API usage appropriately
|
||||
- **Multi-org/project Support**: Organization and project are resolved automatically from your API key via `/v1/ping/`: no org or project params are accepted by `MemoryClient.__init__`. Use a project-specific API key to target a particular project.
|
||||
- **Member Management**: Control access to data through organization and project membership
|
||||
- **Access Control**: Only members can access memories and data within their organization/project scope
|
||||
- **Team Isolation**: Maintain data separation between different teams and projects for secure collaboration
|
||||
@@ -79,7 +79,7 @@ new_project = client.project.create(
|
||||
|
||||
### Update Project Settings
|
||||
|
||||
Modify project configuration including custom instructions, categories, graph settings, and language preferences:
|
||||
Modify project configuration including custom instructions, categories, language preferences, and memory decay:
|
||||
|
||||
```python
|
||||
# Update project with custom categories
|
||||
@@ -98,6 +98,9 @@ client.project.update(
|
||||
# Use the input language for memory storage and retrieval
|
||||
client.project.update(multilingual=True)
|
||||
|
||||
# Enable Memory Decay (boosts recently-accessed memories at search time)
|
||||
client.project.update(decay=True)
|
||||
|
||||
# Update multiple settings at once
|
||||
client.project.update(
|
||||
custom_instructions="...",
|
||||
@@ -111,7 +114,7 @@ client.project.update(
|
||||
|
||||
#### 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:
|
||||
`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:
|
||||
|
||||
```bash cURL
|
||||
curl -X PATCH https://api.mem0.ai/api/v1/orgs/organizations/$ORG_ID/projects/$PROJECT_ID/ \
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Remove Project Member"
|
||||
description: "Remove a member from a project to revoke their access to its memories, configuration, and resources."
|
||||
openapi: "delete /api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Update Project Member"
|
||||
description: "Update an existing member's role within a project to change their permissions and access level."
|
||||
openapi: "put /api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Update Project"
|
||||
description: "Update a project's settings, including name, custom instructions, and other configuration options."
|
||||
openapi: "patch /api/v1/orgs/organizations/{org_id}/projects/{project_id}/"
|
||||
---
|
||||
+173
-63
@@ -4,16 +4,154 @@ 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**
|
||||
|
||||
TypeScript SDK v3.1.0 is the largest provider release for the OSS SDK so far, closing most of the remaining gap with the Python SDK. Python SDK v2.0.12 ships alongside it with fixes and security patches.
|
||||
|
||||
- **17 new vector stores:** Pinecone, Weaviate, Milvus, Chroma, MongoDB, Elasticsearch, OpenSearch, Databricks, AWS Neptune Analytics, S3 Vectors, Azure MySQL, Google Vertex AI Vector Search, Turbopuffer, Upstash Vector, Valkey, Cassandra, and Baidu Mochow.
|
||||
- **5 new LLM providers:** AWS Bedrock, xAI Grok, Together, vLLM, and Sarvam.
|
||||
- **4 new embedding providers:** Vertex AI, HuggingFace, FastEmbed, and Together.
|
||||
- **Reranking in TypeScript:** Four rerankers (Cohere, ZeroEntropy, cross-encoder, and LLM-based) with per-search rerank via a `rerank` option on `search()`.
|
||||
- **Install only what you use:** Importing `mem0ai/oss` no longer pulls in any provider SDK. Provider packages are resolved lazily on first use, so an app that configures only OpenAI and Qdrant does not need the other provider SDKs installed.
|
||||
|
||||
See [SDK & Tools](/changelog/sdk) for version details and PR links.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-27" description="SDK memory expiration">
|
||||
|
||||
**SDK Memory Expiration: Expiring Memories Across Python and TypeScript**
|
||||
|
||||
The latest SDK releases add first-class expiration controls to memory writes, updates, and reads, plus new TypeScript provider coverage for production deployments.
|
||||
|
||||
- **Python client updates:** `MemoryClient.update()` and `AsyncMemoryClient.update()` now accept `expiration_date`, including `None` to clear an existing expiration.
|
||||
- **TypeScript client updates:** `AddMemoryOptions`, `update()`, and `Memory` now support `expirationDate`; `search()` and `getAll()` can include expired memories with `showExpired`.
|
||||
- **New TypeScript LLM providers:** `MiniMaxLLM` and `LiteLLM` are now available for OpenAI-compatible MiniMax and LiteLLM proxy deployments.
|
||||
- **PGVector deployment flexibility:** TypeScript PGVector config now supports `connectionString` and `ssl`, so apps can use a managed Postgres URI instead of separate connection fields.
|
||||
|
||||
See [SDK & Tools](/changelog/sdk) for version details and PR links.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-25" description="Mem0 Plugin v0.2.11">
|
||||
|
||||
**Mem0 Plugin: Shared Memory for Claude Code, Cursor, Codex, and Antigravity**
|
||||
|
||||
The shared Mem0 editor plugin is current through v0.2.11:
|
||||
|
||||
- **Automatic context injection:** File reads, bash errors, session resume prompts, and startup timelines can retrieve relevant memories automatically.
|
||||
- **Project and global scopes:** Project-scoped memories remain the default, while `global_search` supports team-wide recall across users and app scopes.
|
||||
- **Coding categories:** A 17-category coding taxonomy installs in the background and is cached per Mem0 account.
|
||||
- **Reliable capture:** Auto-capture, compaction summaries, session summaries, and metadata defaults keep memories scoped and readable.
|
||||
- **Editor correctness:** Telemetry now reports Claude Code, Cursor, Codex, and Antigravity separately with each editor's real plugin version.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-25" description="Antigravity Plugin v0.1.3">
|
||||
|
||||
**Antigravity Plugin: Mem0 for Google Antigravity**
|
||||
|
||||
Antigravity support in the shared Mem0 editor plugin family is current through v0.1.3:
|
||||
|
||||
- **Self-contained plugin:** Includes its own plugin manifest, MCP config, hooks, shared scripts, and skills.
|
||||
- **AGENTS.md convention:** Uses Antigravity's `contextFileName: "AGENTS.md"` convention.
|
||||
- **Lifecycle hooks:** Wires session start, prompt recall, file-read context, memory-tool metadata enforcement, bash-error lookup, post-tool tracking, and stop summaries.
|
||||
- **Shared memory layer:** Reuses the Mem0 Platform MCP tools and the shared 16-skill command bundle.
|
||||
- **Better automatic recall:** v0.1.3 adds reranked injected context and clean `files_touched` metadata in session summaries.
|
||||
- **Telemetry correctness:** v0.1.2 reports Antigravity as `antigravity` with the plugin's own version.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-22" description="OpenCode Plugin v0.2.0">
|
||||
|
||||
**OpenCode Plugin: Native SDK Memory Tools for OpenCode**
|
||||
|
||||
`@mem0/opencode-plugin` adds memory to OpenCode and is current through v0.2.0:
|
||||
|
||||
- **Native SDK tools:** Memory tools register through `@opencode-ai/plugin` and call the `mem0ai` SDK directly, so the plugin no longer depends on `mcp.mem0.ai`.
|
||||
- **Memory scopes:** Operations support `project`, `session`, and `global` scope, with `/mem0-scope` to change the default.
|
||||
- **Automatic context:** File-context injection, structured compaction summaries, and a recent-activity timeline surface relevant memories without manual recall.
|
||||
- **Auto-dream consolidation:** Gated consolidation merges duplicates, drops stale or sensitive entries, and rewrites vague memories.
|
||||
- **Skills load in place:** The `config` hook adds bundled skills to `skills.paths` instead of copying them into user config directories.
|
||||
- **Safety controls:** Blocks `MEMORY.md` writes and redacts secrets before storing memories.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-12" description="Pi Agent Plugin v0.1.2">
|
||||
|
||||
**Pi Agent Plugin: Persistent Memory for Pi Agent**
|
||||
|
||||
`@mem0/pi-agent-plugin` adds semantic memory to Pi Agent and has been updated through v0.1.2:
|
||||
|
||||
- **Agent memory tool:** Registers `mem0_memory` for scoped search, add, get, delete, and delete-all operations.
|
||||
- **Slash commands:** Adds `/mem0-remember`, `/mem0-search`, `/mem0-forget`, `/mem0-tour`, `/mem0-dream`, `/mem0-pin`, `/mem0-scope`, and `/mem0-status`.
|
||||
- **Auto-capture:** Stores user and assistant memories after agent turns.
|
||||
- **Dream consolidation:** Merges duplicates, resolves contradictions, and prunes stale memories behind session, time, and memory-count gates.
|
||||
- **Project scoping:** Uses git-root detection for stable `app_id` values across monorepos.
|
||||
- **Relevant command results:** v0.1.2 adds visible command feedback and thresholded, reranked search for search, forget, and pin commands.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-12" description="OpenClaw v1.0.13">
|
||||
|
||||
**OpenClaw Plugin: Current Memory Backend for OpenClaw**
|
||||
|
||||
`@mem0/openclaw-mem0` is current through v1.0.13, with the original production-ready memory backend plus newer setup, security, and runtime work:
|
||||
|
||||
- **Skills-based memory architecture:** Triage, recall, and dream skills handle extraction, recall, consolidation, and tool guidance.
|
||||
- **Chat and CLI setup:** Supports chat-based platform setup, `openclaw mem0 init`, direct API keys, email OTP, autonomous agent setup, and OSS onboarding.
|
||||
- **Platform and OSS modes:** Works with Mem0 Platform or self-hosted OSS providers including OpenAI, Anthropic, Ollama, Qdrant, and PGVector.
|
||||
- **Agent-friendly CLI:** All 16 CLI commands support `--json` for machine-driven setup and diagnostics.
|
||||
- **Runtime integration:** Exposes OpenClaw memory capability APIs for search manager and backend config status.
|
||||
- **Security and compliance:** Added path containment checks, sensitive config metadata, dependency overrides, telemetry hashing, and metadata-only registration safety.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-10" description="Vercel AI SDK Provider v3.0.0">
|
||||
|
||||
**Vercel AI SDK Provider: Memory-Augmented Generation for AI SDK v6**
|
||||
|
||||
The Vercel AI SDK provider moved to the v6 provider contract and Mem0 v3 APIs:
|
||||
|
||||
- **AI SDK v6 support:** Migrated to `LanguageModelV3` / `ProviderV3`, including v3 stream lifecycle events and content arrays.
|
||||
- **Mem0 v3 API support:** Memory writes and searches now use `/v3/memories/add/` and `/v3/memories/search/`.
|
||||
- **Mem0 sources in responses:** `generateText` and `streamText` responses include memories as sources with `providerMetadata.mem0.memories`.
|
||||
- **Safer prompt handling:** Prompts are cloned before memory injection, avoiding caller-side mutation.
|
||||
- **Async storage fix:** `addMemories` is awaited so generated memories are not silently dropped.
|
||||
- **Raw memory utilities:** Exports `searchMemories`, `retrieveMemories`, `getMemories`, and `addMemories` for apps that need direct memory control.
|
||||
- **Deployment controls:** Per-request `mem0ApiKey` and `host` support Mem0 Platform, custom API keys, and self-hosted API endpoints.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-05-13" description="Temporal Reasoning for Mem0 Platform v3">
|
||||
|
||||
**Temporal Reasoning — Time-Aware Retrieval for Platform v3**
|
||||
**Temporal Reasoning: Time-Aware Retrieval for Platform v3**
|
||||
|
||||
Mem0 Platform v3 can now interpret time-aware memories and queries so assistants retrieve the right information for questions about the past, upcoming plans, and current state.
|
||||
|
||||
- **Time-aware search intent** — Queries like `last week`, `upcoming`, `right now`, and `as of March 2025` return contextually appropriate results automatically
|
||||
- **Enabled by default** — No per-request toggle required for v3 writes or searches
|
||||
- **Anchored relative queries** — `reference_date` anchors relative search phrases for tests, backfills, and reproducible demos
|
||||
- **Normal response shape** — Temporal reasoning affects ranking while preserving existing client response patterns
|
||||
- **Search intent parsing:** Queries like `last week`, `upcoming`, `right now`, and `as of March 2025` now resolve against memory timestamps automatically.
|
||||
- **Default v3 behavior:** No per-request toggle is needed for v3 writes or searches.
|
||||
- **Deterministic testing:** Pass `reference_date` to anchor relative phrases in tests, backfills, and demos.
|
||||
- **Stable API shape:** Temporal reasoning changes ranking, not the client response contract.
|
||||
|
||||
See [Temporal Reasoning](/platform/features/temporal-reasoning) for usage details.
|
||||
|
||||
@@ -21,11 +159,11 @@ See [Temporal Reasoning](/platform/features/temporal-reasoning) for usage detail
|
||||
|
||||
<Update label="2026-05-08" description="Memory Decay">
|
||||
|
||||
**Memory Decay — Recently-Used Memories Surface Higher, Automatically**
|
||||
**Memory Decay: Recently-Used Memories Surface Higher**
|
||||
|
||||
Per-project search-time ranking bias that boosts recently-touched memories and gently dampens stale ones. Off by default; opt in per project via the `decay` field on the project endpoint, or via `client.project.update(decay=True)` in the SDKs (Python `v2.0.2` / TypeScript `v3.0.3`).
|
||||
|
||||
- **Soft bias, never a filter.** The scaling factor stays in `0.3×–1.5×`. Decay can reorder candidates but never zeros them out — anything that surfaced before decay can still surface after.
|
||||
- **Soft bias, never a filter.** The scaling factor stays in `0.3×–1.5×`. Decay can reorder candidates but never removes them; anything that surfaced before decay can still surface after.
|
||||
- **Reinforcement loop.** Every memory returned in a search has its access history updated, so frequently-used facts naturally float to the top over time.
|
||||
- **Public score still clamped to `[0, 1]`.** Existing API contract preserved; no client-side changes needed.
|
||||
- **v3 search only**, fully reversible. See [Memory Decay docs](/platform/features/memory-decay).
|
||||
@@ -34,76 +172,48 @@ Per-project search-time ranking bias that boosts recently-touched memories and g
|
||||
|
||||
<Update label="2026-04-14" description="Mem0 SDK v2.0.0 / v3.0.0">
|
||||
|
||||
**New Memory Algorithm — State-of-the-Art Accuracy at ~3-4x Lower Cost**
|
||||
**New Memory Algorithm: State-of-the-Art Accuracy at ~3-4x Lower Cost**
|
||||
|
||||
Ground-up rewrite of the memory pipeline with 20+ point benchmark improvements:
|
||||
|
||||
- **LoCoMo:** 71.4 → **91.6** (+20) — multi-turn conversation recall
|
||||
- **LongMemEval:** 67.8 → **93.4** (+26) — long-term memory across sessions
|
||||
- **BEAM (1M tokens):** **64.1** — production-scale memory evaluation
|
||||
- **Agent memories are first-class** — Previous algorithm: 46% on assistant recall. New: **100%**
|
||||
- **Temporal reasoning works** — "Where did I live before SF?" Previous: 51%. New: **93%**
|
||||
- **~3-4x fewer tokens** — Under 7K tokens per retrieval vs 25K+ for full-context approaches
|
||||
- **ADD-only extraction** — Memories accumulate; nothing is overwritten or deleted
|
||||
- **Hybrid retrieval** — Semantic + BM25 keyword + entity boost, scored in parallel
|
||||
- **Entity linking** — Entities extracted, embedded, and linked across memories
|
||||
- **LoCoMo:** 71.4 → **91.6** (+20) for multi-turn conversation recall.
|
||||
- **LongMemEval:** 67.8 → **93.4** (+26) for long-term memory across sessions.
|
||||
- **BEAM (1M tokens):** **64.1** on production-scale memory evaluation.
|
||||
- **Agent memories:** Assistant recall moves from 46% to **100%**.
|
||||
- **Temporal reasoning:** "Where did I live before SF?" improves from 51% to **93%**.
|
||||
- **Lower token use:** Retrieval stays under 7K tokens versus 25K+ for full-context approaches.
|
||||
- **ADD-only extraction:** Memories accumulate; nothing is overwritten or deleted.
|
||||
- **Hybrid retrieval:** Semantic search, BM25 keyword search, and entity boost are scored in parallel.
|
||||
- **Graph memory (built-in)**: entities extracted, embedded, and linked across memories, with no external graph store required
|
||||
|
||||
Breaking changes: Graph memory removed from OSS, `search()` defaults changed, deprecated params removed. See [migration guide](/migration/oss-v2-to-v3).
|
||||
Breaking changes: external graph stores removed from OSS (replaced by built-in graph memory), `search()` defaults changed, deprecated params removed. See [migration guide](/migration/oss-v2-to-v3).
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-06" description="Mem0 Skill Graph">
|
||||
|
||||
**Mem0 Skill Graph — In-Context Documentation for AI Agents**
|
||||
**Mem0 Skill Graph: In-Context Documentation for AI Agents**
|
||||
|
||||
AI coding agents in Claude Code, Cursor, and Codex can now access Mem0 knowledge directly in their workflow — no doc searching required. Three interconnected skills launched:
|
||||
AI coding agents in Claude Code, Cursor, and Codex can now access Mem0 knowledge directly in their workflow without leaving the editor. Three interconnected skills launched:
|
||||
|
||||
- **mem0 Core Skill** — Complete Python and TypeScript SDK reference, REST API patterns, and integration guides for LangChain, CrewAI, Autogen, and more
|
||||
- **mem0-cli Skill** — Terminal command reference, configuration walkthroughs, and CI/CD recipes
|
||||
- **mem0-vercel-ai-sdk Skill** — Vercel AI SDK provider API, memory-augmented generation patterns, and multi-provider setup
|
||||
- **mem0 Core Skill:** Python and TypeScript SDK reference, REST API patterns, and integration guides for LangChain, CrewAI, Autogen, and more.
|
||||
- **mem0-cli Skill:** Terminal command reference, configuration walkthroughs, and CI/CD recipes.
|
||||
- **mem0-vercel-ai-sdk Skill:** Vercel AI SDK provider API, memory-augmented generation patterns, and multi-provider setup.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-06" description="Mem0 CLI v0.2.2">
|
||||
|
||||
**Official Mem0 CLI — Now on PyPI and npm**
|
||||
**Official Mem0 CLI: Now on PyPI and npm**
|
||||
|
||||
A full-featured command-line interface for Mem0, available in both Python and Node.js:
|
||||
|
||||
- **Install:** `pip install mem0-cli` or `npm install -g @mem0/cli`
|
||||
- **Full command suite** — `add`, `search`, `list`, `get`, `update`, `delete`, `import`, `config`, `init`, `status`, `entity`, `event`
|
||||
- **Interactive setup** — `mem0 init` with email verification or direct API key entry
|
||||
- **Works everywhere** — Platform (Mem0 Cloud) and self-hosted OSS modes
|
||||
- **Scriptable** — `--json` flag for CI/CD pipelines and automation
|
||||
- **Dual SDK** — Same commands, same experience across Python and Node.js
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-06" description="OpenClaw v1.0.4">
|
||||
|
||||
**OpenClaw Plugin — Production-Ready**
|
||||
|
||||
The OpenClaw Mem0 plugin went from initial release to production-ready in one week (v1.0.0 → v1.0.4):
|
||||
|
||||
- **Skills-based memory architecture** — New extraction pipeline with skill-loader, batched extraction, and domain-aware memory triage
|
||||
- **Dream gate** — Automatic memory consolidation during idle periods for higher-quality long-term recall
|
||||
- **Interactive CLI** — `openclaw mem0 init`, `status`, `config`, `import`, and `event` commands
|
||||
- **Unified tool naming** — `memory_add` and `memory_delete` replace 4 legacy tools, matching the platform API
|
||||
- **Security hardened** — Path traversal protection, pinned dependencies, 329 tests across 10 files
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-02" description="Mem0 Plugin for AI Editors">
|
||||
|
||||
**Mem0 Plugin for Claude Code, Cursor, and Codex**
|
||||
|
||||
Launched a unified Mem0 plugin across three major AI development environments — Claude Code and Cursor first (March 25), then Codex (April 2):
|
||||
|
||||
- **9 MCP memory tools** — add, search, get, update, delete, bulk delete, entity management via `mcp.mem0.ai`
|
||||
- **Lifecycle hooks** — Automatic memory capture at session start, context compaction, task completion, and session end
|
||||
- **Cloud MCP server** — Managed endpoint replaces local MCP and Smithery setup
|
||||
- **Streamable HTTP transport** — New MCP transport protocol for real-time streaming
|
||||
- **Codex-specific skill** — Dedicated skill in `mem0-plugin/skills/mem0-codex` for Codex workflows
|
||||
- **Full command suite:** `add`, `search`, `list`, `get`, `update`, `delete`, `import`, `config`, `init`, `status`, `entity`, `event`.
|
||||
- **Interactive setup:** `mem0 init` supports email verification and direct API key entry.
|
||||
- **Runtime coverage:** Works with Mem0 Platform and self-hosted OSS modes.
|
||||
- **Automation support:** Use `--json` for CI/CD pipelines and agent workflows.
|
||||
- **Dual implementation:** Same commands and behavior across Python and Node.js.
|
||||
|
||||
</Update>
|
||||
|
||||
@@ -113,11 +223,11 @@ Launched a unified Mem0 plugin across three major AI development environments
|
||||
|
||||
Major expansion of the provider ecosystem:
|
||||
|
||||
- **Apache AGE** — New graph store support, bringing the total to 4 graph store backends (Neo4j, Memgraph, Kuzu, Apache AGE)
|
||||
- **Turbopuffer** — New vector database provider for Python SDK
|
||||
- **MiniMax** — New LLM provider with dedicated AWS Bedrock support
|
||||
- **pgvector for Node.js** — PostgreSQL vector support added to the TypeScript OSS SDK
|
||||
- **Reasoning models** — `reasoning_effort` parameter for OpenAI o1/o3-style models
|
||||
- **Apache AGE:** New graph store support, bringing the total to 4 graph store backends (Neo4j, Memgraph, Kuzu, Apache AGE). **Note:** All external graph store backends (Neo4j, Memgraph, Kuzu, Apache AGE) were subsequently removed in v2.0.0 (2026-04-14). Graph memory is now built-in entity linking with no external graph store required; see the [v2.0.0 entry above](#mem0-sdk-v2-0-0-v3-0-0).
|
||||
- **Turbopuffer:** New vector database provider for Python SDK.
|
||||
- **MiniMax:** New LLM provider with dedicated AWS Bedrock support.
|
||||
- **pgvector for Node.js:** PostgreSQL vector support added to the TypeScript OSS SDK.
|
||||
- **Reasoning models:** `reasoning_effort` parameter for OpenAI o1/o3-style models.
|
||||
|
||||
</Update>
|
||||
|
||||
@@ -125,6 +235,6 @@ Major expansion of the provider ecosystem:
|
||||
|
||||
**Mem0 Platform Skill on skills.sh**
|
||||
|
||||
First skill launch — a dedicated Mem0 skill providing platform API reference, quickstart patterns, and integration examples directly inside agent sessions. Available on [skills.sh](https://skills.sh) for any compatible AI coding agent.
|
||||
First skill launch: a dedicated Mem0 skill providing platform API reference, quickstart patterns, and integration examples directly inside agent sessions. Available on [skills.sh](https://skills.sh) for any compatible AI coding agent.
|
||||
|
||||
</Update>
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
---
|
||||
title: "OpenClaw"
|
||||
description: "Release notes for the OpenClaw plugin and agent harness."
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
<Update label="2026-04-29" description="v1.0.11">
|
||||
|
||||
**New Features:**
|
||||
- **Skills-mode auto-setup:** `enableSkillsConfig()` now runs automatically after onboarding — enables triage, recall (with reranking + keyword search), and dream consolidation with `tools.profile = "full"` and disables the built-in session-memory hook to avoid conflicts
|
||||
- **Memory runtime capability:** Plugin now exposes `runtime.getMemorySearchManager()` and `resolveMemoryBackendConfig()` on the registered memory capability, enabling OpenClaw gateway to query memory status and backend config directly
|
||||
- **Dimension-aware collections:** OSS wizard detects embedder dimension changes and creates a new collection (`mem0_<dims>d`) automatically, with a warning about old memories being inaccessible under the new embedder
|
||||
- **Tool documentation in skills:** Both `memory-triage` and `memory-dream` SKILL.md files now include full tool reference sections listing all available tools with parameters
|
||||
|
||||
**Improvements:**
|
||||
- **Auto-capture and auto-recall default to enabled:** `autoCapture` and `autoRecall` now default to `true` (was `false`). Manifest descriptions updated accordingly. Ignored in skills mode
|
||||
- **`memory_update` over delete+add:** Skills now prefer `memory_update` for in-place edits — atomic and preserves edit history. Consolidation pattern updated: update best memory, delete redundant ones
|
||||
- **Search threshold lowered:** Default `searchThreshold` reduced from `0.5` to `0.1` for broader recall. Removed hardcoded `0.6` recall-specific override — all searches now use the configured threshold
|
||||
- **Embedder dimension propagation:** Vector store config auto-resolves dimensions from embedder config when not explicitly set. Syncs `dimension` and `embeddingModelDims` fields for Qdrant/PGVector compatibility
|
||||
- **Config file write safety:** `writeFullConfig()` now re-reads and deep-merges the `plugins` section before writing, preserving `installs` and `slots` written by the OpenClaw gateway
|
||||
- **Additional embedder models:** Added `mxbai-embed-large` (1024), `all-minilm` (384), and `snowflake-arctic-embed` (1024) to known embedder dimensions
|
||||
|
||||
**Security:**
|
||||
- Bumped `protobufjs` to `>=7.5.5` via pnpm overrides (GHSA-xq3m-2v4x-88gg) ([#5012](https://github.com/mem0ai/mem0/pull/5012))
|
||||
|
||||
**Fixes:**
|
||||
- Moved `bootstrapTelemetryFlag()` and removed `ensureInstallRecord()` from module-level side effects — both now run inside `register()` to avoid crashes when loaded outside OpenClaw gateway
|
||||
- Fixed OSS history DB path resolution: absolute paths no longer passed through `resolvePath()`, preventing double-prefix bugs
|
||||
- Manifest `providerAuthEnvVars` replaced with spec-compliant `setup.providers` format using `id` + `envVars`
|
||||
|
||||
**Dependencies:**
|
||||
- Bumped `mem0ai` from `3.0.1` to `3.0.2`
|
||||
- Bumped `pluginApi` and `minGatewayVersion` compat to `>=2026.4.24`
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-23" description="v1.0.10">
|
||||
|
||||
**Security:**
|
||||
- Telemetry `distinct_id` now uses SHA-256 instead of MD5 — prevents rainbow-table reversal of API key hashes
|
||||
- User email is now SHA-256 hashed before sending as `distinct_id` — no PII in telemetry payloads
|
||||
- Declared PostHog telemetry endpoint (`us.i.posthog.com`) in `providerEndpoints`
|
||||
|
||||
**Fixes:**
|
||||
- Fixed version-pinned install records preventing plugin updates. `ensureInstallRecord()` now detects semver-pinned specs (e.g. `@mem0/openclaw-mem0@1.0.7`) and rewrites them to `@latest` or `clawhub:` prefix so `openclaw plugins update` resolves to the newest release
|
||||
- Fixed `searchThreshold` default inconsistency: standardized to `0.3` across docs, README, and manifest
|
||||
- `PLUGIN_VERSION` now injected at build time via tsup `define` from `package.json` — no more hardcoded version strings
|
||||
|
||||
**Manifest Compliance:**
|
||||
- Removed non-spec fields: `requiredEnvVars`, `dataLocations`, `privacy`, `setup` (with `externalEndpoints`, `providers`, `requiresRuntime`, `postInstallHint`)
|
||||
- Replaced `setup.externalEndpoints` with spec-compliant `providerEndpoints` using `endpointClass` + `hosts` format
|
||||
- Env var declarations now rely solely on `providerAuthEnvVars` (already spec-compliant)
|
||||
|
||||
**Docs:**
|
||||
- Fixed `openclaw plugins update` command: uses plugin ID (`openclaw-mem0`), not npm package name (`@mem0/openclaw-mem0`)
|
||||
- Added update section to README
|
||||
- Removed redundant "Key Features" and "Conclusion" sections from integration docs
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-22" description="v1.0.9">
|
||||
|
||||
**Security & Compliance:**
|
||||
- Added top-level `requiredEnvVars` to plugin manifest, declaring env vars per mode (platform, OSS OpenAI, OSS Anthropic, OSS Ollama). Fixes ClaHub scanner "required env vars: none" mismatch
|
||||
- Added `sensitive: true` and descriptions to `apiKey` and `userEmail` in `configSchema` — previously only declared in `uiHints`
|
||||
- Added `default: false` with descriptions to `autoCapture` and `autoRecall` in `configSchema` so scanner can confirm opt-in defaults
|
||||
- Added `dataLocations` field to manifest declaring all persistence paths (config, vectorStore, historyDb, dreamState)
|
||||
- Added `privacy` field to manifest documenting data flow for platform vs open-source mode and credential storage guidance
|
||||
- Added `externalEndpoints` to `setup` section declaring api.mem0.ai and app.mem0.ai with purpose and requirement context
|
||||
|
||||
**Tests:**
|
||||
- Replaced direct `process.env` access in `tests/cli-commands.test.ts` and `tests/fs-safe.test.ts` with `vi.stubEnv`/`vi.unstubAllEnvs`. Fixes ClaHub static analysis flag for "environment variable access combined with network send"
|
||||
- 421 tests across 15 test files
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-21" description="v1.0.8">
|
||||
|
||||
**New Features:**
|
||||
- **OSS Onboarding Wizard:** New guided 4-step interactive setup for open-source mode — walks through LLM provider, embedding provider, vector store, and user ID selection with prefilled defaults
|
||||
- **Agent-Friendly CLI:** Added `--json` flag to all 16 CLI commands for machine-readable output. Agents can call `openclaw mem0 help --json` to discover every command and flag
|
||||
- **Non-Interactive OSS Setup:** Added `--mode open-source` with `--oss-llm`, `--oss-embedder`, `--oss-vector` flags for fully automated OSS configuration without prompts
|
||||
- **JSON Helpers Module:** New `cli/json-helpers.ts` with `jsonOut`, `jsonErr`, and `redactSecrets` utilities for consistent structured output
|
||||
|
||||
**Improvements:**
|
||||
- **Init Flow Redesigned:** Replaced 3-option flat menu with 2-level structure: Platform (email login or API key) and Open Source (guided wizard)
|
||||
- **Provider Selection:** LLM providers: OpenAI, Ollama, Anthropic. Embedding providers: OpenAI, Ollama. Vector stores: Qdrant, PGVector
|
||||
- **Input Prefill:** All prompts with defaults (base URL, user ID) now prefill the input field instead of showing defaults in brackets
|
||||
- **Smart Reuse:** When LLM and embedder use the same provider, API key and base URL are automatically reused from the LLM step
|
||||
- **Default Model:** Updated default LLM model to `gpt-5-mini`
|
||||
- **Manifest Compliance:** Removed undocumented fields, aligned env var declarations between SKILL.md and manifest, fixed `configSchema.required` for clean installs
|
||||
|
||||
**Tests:**
|
||||
- 404 tests across 15 test files (+3 new: `json-helpers.test.ts`, `oss-wizard.test.ts`, `cli-commands.test.ts`)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-20" description="v1.0.7">
|
||||
|
||||
**New Features:**
|
||||
- **Chat-Based Setup:** Added chat-based Platform setup flow — users can now configure the plugin conversationally instead of editing config files manually
|
||||
- **Installation Docs Rewrite:** Rewrote README and integration docs with chat-first setup, numbered manual steps.
|
||||
|
||||
**Improvements:**
|
||||
- **SDK Upgrade:** Bumped `mem0ai` dependency to 3.0.1 for V3 API compatibility
|
||||
- **Config Cleanup:** Dropped deprecated `orgId`, `projectId`, `enableGraph` config options; updated CLI prompts ([#4734](https://github.com/mem0ai/mem0/pull/4734), [#4764](https://github.com/mem0ai/mem0/pull/4764))
|
||||
- **Noise Filtering:** Expanded noise patterns in memory add tool; handle leading text in JSON extraction
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-11" description="v1.0.6">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Telemetry:** Replaced shared `"anonymous-openclaw"` fallback with a persistent per-machine random hash (`openclaw-anon-<uuid>`), so anonymous plugin users are counted individually in PostHog ([#4790](https://github.com/mem0ai/mem0/pull/4790))
|
||||
- **Telemetry:** Added PostHog `$identify` event on first authenticated run to stitch anonymous history onto the authenticated profile ([#4790](https://github.com/mem0ai/mem0/pull/4790))
|
||||
- **Telemetry:** Fixed event loss on short-lived CLI invocations — added `beforeExit` handler to flush queued events before the process exits ([#4790](https://github.com/mem0ai/mem0/pull/4790))
|
||||
- **Telemetry:** Added lazy `/v1/ping/` email resolution so users who configure API key outside `mem0 init` show as their email in PostHog, not an md5 hash ([#4790](https://github.com/mem0ai/mem0/pull/4790))
|
||||
- **Telemetry:** Unified CLI event prefix from `openclaw.<cmd>` to `openclaw.cli.<cmd>` on the needsSetup branch to match the authenticated branch ([#4790](https://github.com/mem0ai/mem0/pull/4790))
|
||||
|
||||
**Improvements:**
|
||||
- **API:** Added `source: "OPENCLAW"` to all provider calls (`add`, `search`, `getAll`) across tools, CLI commands, recall, and the OSS backend adapter ([#4790](https://github.com/mem0ai/mem0/pull/4790))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-07" description="v1.0.5">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Init interactive choice bug**: Fixed number selection in `openclaw mem0 init` — entering 1/2/3 now correctly selects the corresponding option (was broken by readline prefill concatenating with user input)
|
||||
- **OSS pgvector crash** ([#4727](https://github.com/mem0ai/mem0/issues/4727)): Fixed "Client has already been connected" cascade when using pgvector in OSS mode. The warmup call swallowed errors leaving a half-initialized pg client; concurrent recall/capture then all hit `client.connect()` on the same client. Fix: let warmup errors propagate (so `initPromise` resets and retries with a fresh Memory + fresh pg client) and build fresh config objects per attempt instead of mutating shared state.
|
||||
|
||||
**Removed:**
|
||||
- **`orgId` / `projectId` config parameters**: Removed from config schema, CLI (`config show/get/set`), init display, and providers. The API key is project-scoped, so separate org/project IDs are unnecessary and could cause access errors if mismatched.
|
||||
- **`enableGraph` config parameter**: Removed from all config surfaces, providers, backend, and tools. Graph memory is being deprecated — removing the flag avoids unnecessary exposure.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-04" description="v1.0.4">
|
||||
|
||||
**New Features:**
|
||||
- **Interactive init flow**: `openclaw mem0 init` with interactive menu (email verification or direct API key). Non-interactive modes: `--api-key`, `--email`, `--email --code`
|
||||
- **`memory_add` tool**: Replaces `memory_store` — name now matches `mem0` CLI and platform API
|
||||
- **`memory_delete` tool**: Unified delete — single ID, search-then-delete, bulk, entity cascade. Replaces `memory_forget` and `memory_delete_all`
|
||||
- **CLI subcommands**: `openclaw mem0 init`, `openclaw mem0 status`, `openclaw mem0 config show`, `openclaw mem0 config set`
|
||||
- **`import` CLI command**: Bulk-import memories from a JSON file with `--user-id` and `--agent-id` overrides
|
||||
- **`event list` / `event status` CLI commands**: Monitor background processing events
|
||||
- **`fs-safe.ts` module**: Isolated filesystem wrappers in a separate entry point
|
||||
- **`backend/` module**: `PlatformBackend` with direct HTTP API access for CLI commands
|
||||
- **Plugin manifest**: Added `contracts.tools`, `configSchema`, and `uiHints` to `openclaw.plugin.json`
|
||||
- **Test suite**: 329 tests across 10 test files
|
||||
|
||||
**Changes:**
|
||||
- **Modular architecture**: Extracted tools into `tools/` directory (6 files) and CLI into `cli/commands.ts`
|
||||
- **Code splitting**: tsup builds with `splitting: true` and two entry points
|
||||
- **Skills updated**: All SKILL.md files reference new tool names (`memory_add`, `memory_delete`)
|
||||
- **Auto-recall timeout**: Recall wrapped in 8-second `Promise.race`
|
||||
- **Auto-capture fire-and-forget**: `provider.add()` runs in background via `.then()/.catch()`
|
||||
- **Auto-capture minimum content gate**: Skips extraction when total user content is fewer than 50 chars
|
||||
|
||||
**Removed:**
|
||||
- `memory_store` tool — replaced by `memory_add`
|
||||
- `memory_forget` tool — replaced by `memory_delete`
|
||||
- `memory_delete_all` tool — merged into `memory_delete`
|
||||
- `memory_history` tool and `history` CLI command — deprecated
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-03" description="v1.0.3">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Security**: Added `safePath()` containment helper to `readSkillFile` and `readDomainOverlay` in `skill-loader.ts` — prevents directory traversal
|
||||
- **Noise filter**: Reverted incorrect `After-Compaction` regex rename back to `Post-Compaction`
|
||||
|
||||
**Changes:**
|
||||
- **Supply-chain hardening**: Pinned `mem0ai` dependency to exact `2.3.0` (was `^2.3.0`)
|
||||
|
||||
**Tests:**
|
||||
- 12 new tests covering `safePath`, `readSkillFile`, `readDomainOverlay`, and `loadSkill` with traversal inputs
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-02" description="v1.0.2">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Security**: Removed `resolveEnvVars()` and `resolveEnvVarsDeep()` from `config.ts` — plugin-side env resolution was redundant and triggered static analysis warnings ([#4676](https://github.com/mem0ai/mem0/pull/4676))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-02" description="v1.0.1">
|
||||
|
||||
**New Features:**
|
||||
- **CD workflow**: Added continuous deployment workflow with OIDC trusted publishing ([#4672](https://github.com/mem0ai/mem0/pull/4672))
|
||||
- **Plugin configuration manifest**: Added `compat` and `build` metadata to `package.json` ([#4667](https://github.com/mem0ai/mem0/pull/4667))
|
||||
- **LICENSE**: Added Apache-2.0 license file ([#4667](https://github.com/mem0ai/mem0/pull/4667))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Dream gate**: Fixed cheap-first ordering, session isolation, and verified completion ([#4666](https://github.com/mem0ai/mem0/pull/4666))
|
||||
- **Graceful startup**: Plugin now starts gracefully when no API key is configured ([#4669](https://github.com/mem0ai/mem0/pull/4669))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-01" description="v1.0.0">
|
||||
|
||||
**New Features:**
|
||||
- **Skills-based memory architecture**: New skill-loader and skill-based extraction pipeline with batched extraction ([#4624](https://github.com/mem0ai/mem0/pull/4624))
|
||||
- **Dream gate**: Memory consolidation and dream-cycle processing during idle periods
|
||||
- **Enhanced recall**: New `recall.ts` module with improved recall logic and skill-aware retrieval
|
||||
- **Memory triage skill**: Domain-aware memory triage with companion domain support and recall protocol
|
||||
- **Memory dream skill**: Skill for memory consolidation during idle periods
|
||||
- **Plugin configuration**: Added `openclaw.plugin.json` manifest and `scripts/configure.py` setup helper
|
||||
|
||||
**Changes:**
|
||||
- Extraction pipeline refactored to use skills-based architecture for more contextual and higher quality memory capture
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-03-26" description="v0.4.1">
|
||||
|
||||
**New Features:**
|
||||
- **Improved extraction quality**: Enhanced noise filtering, deduplication, and better extraction instructions
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Credential detection**: Improved detection of credentials, API keys, and secrets in extraction instructions (#4552)
|
||||
- **Standalone timestamps**: Prevented extraction of standalone timestamps as memories (#4550)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-03-16" description="v0.4.0">
|
||||
|
||||
**New Features:**
|
||||
- **Non-interactive trigger filtering**: Skips recall and capture for `cron`, `heartbeat`, `automation`, and `schedule` triggers
|
||||
- **Subagent hallucination prevention**: Detects ephemeral subagent sessions and routes recall to parent namespace
|
||||
- **Dynamic recall thresholding**: Memories scoring less than 50% of top result are dropped
|
||||
- **SQLite resilience**: Init error recovery with automatic retry for OSS mode
|
||||
- **`disableHistory` config option**: New `oss.disableHistory` flag
|
||||
- 78 unit tests covering filtering, isolation, trigger filtering, subagent detection, and SQLite resilience
|
||||
|
||||
**Changes:**
|
||||
- Auto-recall threshold raised from 0.5 to 0.6 for stricter precision
|
||||
- Recall candidate pool increased to `topK * 2` for better filtering headroom
|
||||
- Relaxed extraction instructions: related facts kept together to preserve context
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Concurrent session race condition**: Lifecycle hooks now use `ctx.sessionKey` directly instead of a shared mutable variable
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-03-12" description="v0.3.1">
|
||||
|
||||
**New Features:**
|
||||
- **Message filtering pipeline**: Multi-stage noise removal before extraction
|
||||
- **Broad recall for new sessions**: Short or new-session prompts trigger secondary broad search
|
||||
- **Client-side threshold filtering**: Safety net that drops low-relevance results
|
||||
- **Temporal anchoring**: Extraction instructions now include current date
|
||||
- 55 unit tests covering filtering and isolation helpers
|
||||
|
||||
**Changes:**
|
||||
- Extraction window expanded from last 10 to last 20 messages
|
||||
- Rewritten custom extraction instructions for conciseness and deduplication
|
||||
- Refactored monolithic `index.ts` (1772 lines) into 6 focused modules
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-03-10" description="v0.3.0">
|
||||
|
||||
**Bug Fixes:**
|
||||
- Updated `mem0ai` dependency with sqlite3 to better-sqlite3 migration (#4270)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-03-09" description="v0.2.0">
|
||||
|
||||
**New Features:**
|
||||
- Per-agent memory isolation for multi-agent setups via `agentId`
|
||||
- "Understanding userId" section in docs
|
||||
|
||||
**Changes:**
|
||||
- Updated config examples to use concrete `userId` values instead of placeholders
|
||||
|
||||
**Bug Fixes:**
|
||||
- Migrated platform search to Mem0 v2 API
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-02-19" description="v0.1.2">
|
||||
|
||||
**New Features:**
|
||||
- Source field for openclaw memory entries
|
||||
|
||||
**Bug Fixes:**
|
||||
- Auto-recall injection and auto-capture message drop
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-02-02" description="v0.1.0">
|
||||
|
||||
**New Features:**
|
||||
- Initial release of the OpenClaw Mem0 plugin
|
||||
- Platform mode (Mem0 Cloud) and open-source mode support
|
||||
- Auto-recall: inject relevant memories before each turn
|
||||
- Auto-capture: store facts after each turn
|
||||
- Configurable `topK`, `threshold`, and `apiVersion` options
|
||||
|
||||
</Update>
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Platform"
|
||||
description: "Release notes for the Mem0 hosted platform — backend, dashboard, billing, and infrastructure changes."
|
||||
description: "Release notes for the Mem0 hosted platform: backend, dashboard, billing, and infrastructure changes."
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
@@ -25,7 +25,7 @@ mode: "wide"
|
||||
<Update label="2026-04-16" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **UI:** Removed Graph Memory tab, page, and all references from dashboard, sidebar, project settings, playground, and billing
|
||||
- **UI:** Removed the legacy external-graph-store visualization tab, page, and its references from dashboard, sidebar, project settings, playground, and billing
|
||||
|
||||
</Update>
|
||||
|
||||
|
||||
+1325
-47
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||
@@ -59,5 +115,21 @@ Here are the parameters available for configuring AWS Bedrock embedder:
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `model` | The name of the embedding model to use | `amazon.titan-embed-text-v1` |
|
||||
| `aws_region` | AWS region for the Bedrock client | `us-west-2` |
|
||||
| `aws_access_key_id` | AWS access key ID for authentication | `None` |
|
||||
| `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>
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Azure OpenAI
|
||||
description: "Configure Azure OpenAI as an embedding provider in Mem0 with API key, deployment, and endpoint settings."
|
||||
---
|
||||
|
||||
To use Azure OpenAI embedding models, set the `EMBEDDING_AZURE_OPENAI_API_KEY`, `EMBEDDING_AZURE_DEPLOYMENT`, `EMBEDDING_AZURE_ENDPOINT` and `EMBEDDING_AZURE_API_VERSION` environment variables. You can obtain the Azure OpenAI API key from the Azure.
|
||||
To use Azure OpenAI embedding models, set the `EMBEDDING_AZURE_OPENAI_API_KEY`, `EMBEDDING_AZURE_DEPLOYMENT`, `EMBEDDING_AZURE_ENDPOINT` and `EMBEDDING_AZURE_API_VERSION` environment variables. You can obtain the Azure OpenAI API key from the Azure Portal.
|
||||
|
||||
### Usage
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user