Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3882af7450 | |||
| d39ebad09f | |||
| 9d82e2329d | |||
| 789cc9d607 | |||
| e59e3d5f0c | |||
| d926f3697c | |||
| c996b0e7fa | |||
| 78ca85a260 | |||
| 88f696a60a | |||
| 081eca6d8f | |||
| 2434b9d550 | |||
| 3ffea554bc | |||
| 1ad8a59b0c | |||
| a670333d67 | |||
| 4c2db3e68b |
@@ -0,0 +1,580 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file provides context for AI coding assistants (Claude Code, Cursor, GitHub Copilot, Codex, etc.) working with the Mem0 repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Mem0** ("mem-zero") is an intelligent memory layer for AI agents and assistants. It provides persistent, personalized memory via both a hosted platform API and self-hosted open-source SDKs.
|
||||
|
||||
- **Repository**: https://github.com/mem0ai/mem0
|
||||
- **Documentation**: https://docs.mem0.ai
|
||||
- **License**: Apache-2.0
|
||||
|
||||
## Repository Structure
|
||||
|
||||
This is a **polyglot monorepo** containing Python and TypeScript packages, CLIs, servers, plugins, documentation, and evaluation tooling.
|
||||
|
||||
### Key Directories
|
||||
|
||||
| Directory | Description |
|
||||
|-----------|-------------|
|
||||
| `mem0/` | Core Python SDK (`mem0ai` on PyPI) — memory, LLMs, embeddings, vector stores, graphs, rerankers |
|
||||
| `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 |
|
||||
| `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 — `mem0/`, `mem0-cli/`, `mem0-vercel-ai-sdk/` |
|
||||
| `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) |
|
||||
| `pr-reviews/` | Pull request review materials |
|
||||
|
||||
### Core Package Dependencies
|
||||
|
||||
```
|
||||
mem0 (Python SDK) mem0-ts (TypeScript SDK)
|
||||
├── mem0/memory/ ├── src/client/ (MemoryClient — hosted)
|
||||
├── mem0/llms/ └── src/oss/ (Memory — self-hosted)
|
||||
├── mem0/embeddings/ ├── src/llms/
|
||||
├── mem0/vector_stores/ ├── src/embeddings/
|
||||
├── mem0/graphs/ ├── src/vector_stores/
|
||||
└── mem0/reranker/ └── src/graphs/
|
||||
|
||||
cli/python/ ──▶ mem0ai (optional, for OSS mode)
|
||||
cli/node/ ──▶ mem0ai (npm, for API calls)
|
||||
vercel-ai-sdk/ ──▶ ai, @ai-sdk/* providers
|
||||
openclaw/ ──▶ mem0ai (npm)
|
||||
```
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Requirements
|
||||
|
||||
- **Python**: 3.9+ (3.10+ for CLI)
|
||||
- **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
|
||||
|
||||
### Initial Setup
|
||||
|
||||
```bash
|
||||
# Python SDK
|
||||
hatch shell dev_py_3_11 # creates environment with all deps
|
||||
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
|
||||
```
|
||||
|
||||
## Build, Lint, and Test Commands
|
||||
|
||||
### Python SDK (`mem0/`)
|
||||
|
||||
```bash
|
||||
# Environment setup (uses Hatch)
|
||||
hatch shell dev_py_3_11 # or dev_py_3_9, dev_py_3_10, dev_py_3_12
|
||||
|
||||
# Linting and formatting
|
||||
make lint # ruff check
|
||||
make format # ruff format
|
||||
make sort # isort mem0/
|
||||
|
||||
# Tests
|
||||
make test # pytest tests/
|
||||
make test-py-3.9 # test specific Python version (3.9–3.12)
|
||||
|
||||
# Build and publish
|
||||
make build # hatch build
|
||||
make publish # hatch publish
|
||||
```
|
||||
|
||||
- **Python:** 3.9, 3.10, 3.11, 3.12
|
||||
- **Linter/formatter:** Ruff (line length **120**)
|
||||
- **Import sorting:** isort (`profile = "black"`)
|
||||
- **Test framework:** pytest (with pytest-mock, pytest-asyncio)
|
||||
- **Pre-commit hooks:** ruff + isort — run `pre-commit install` before committing
|
||||
|
||||
### TypeScript SDK (`mem0-ts/`)
|
||||
|
||||
```bash
|
||||
cd mem0-ts
|
||||
pnpm install
|
||||
pnpm run build # tsup
|
||||
pnpm run test # jest (all tests)
|
||||
pnpm run test:unit # jest --coverage (unit tests only)
|
||||
pnpm run test:integration # jest (integration tests, needs MEM0_API_KEY)
|
||||
pnpm run test:ci # jest --coverage --ci (CI mode)
|
||||
pnpm run test:watch # jest watch mode
|
||||
```
|
||||
|
||||
- **Node:** 20, 22 (CI-tested)
|
||||
- **Build:** tsup (CJS + ESM)
|
||||
- **Test:** jest
|
||||
- **Formatter:** prettier
|
||||
|
||||
### Python CLI (`cli/python/`)
|
||||
|
||||
```bash
|
||||
cd cli/python
|
||||
pip install -e ".[dev]" # dev install with ruff + pytest
|
||||
ruff check . # lint
|
||||
ruff format . # format
|
||||
pytest # test
|
||||
hatch build # build
|
||||
```
|
||||
|
||||
- **Python:** 3.10+ (not 3.9)
|
||||
- **Linter/formatter:** Ruff (line length **100** — different from root SDK)
|
||||
- **Ruff rules:** E, F, I, W, UP, B, SIM, RUF (ignores E501, B008 for Typer patterns, SIM108)
|
||||
- **Framework:** Typer + Rich + httpx
|
||||
- **Entry point:** `mem0 = "mem0_cli.app:main"`
|
||||
- **Source layout:** `src/mem0_cli/`
|
||||
- **Optional dependency:** `mem0ai` (for OSS mode, via `[oss]` extra)
|
||||
|
||||
### Node CLI (`cli/node/`)
|
||||
|
||||
```bash
|
||||
cd cli/node
|
||||
pnpm install
|
||||
pnpm run build # tsup
|
||||
pnpm run lint # biome check src/
|
||||
pnpm run lint:fix # biome check --write src/
|
||||
pnpm run typecheck # tsc --noEmit
|
||||
pnpm run test # vitest run
|
||||
pnpm run test:watch # vitest (watch mode)
|
||||
pnpm run dev # tsx src/index.ts (development)
|
||||
```
|
||||
|
||||
- **Node:** 18+ required
|
||||
- **Build:** tsup (ESM)
|
||||
- **Linter:** Biome (not ESLint, not Ruff)
|
||||
- **Test:** vitest (not jest)
|
||||
- **Framework:** Commander + Chalk + ora + cli-table3
|
||||
|
||||
### Vercel AI SDK Provider (`vercel-ai-sdk/`)
|
||||
|
||||
```bash
|
||||
cd vercel-ai-sdk
|
||||
pnpm install
|
||||
pnpm run build # tsup
|
||||
pnpm run lint # eslint
|
||||
pnpm run type-check # tsc --noEmit
|
||||
pnpm run prettier-check # prettier --check
|
||||
pnpm run test # jest
|
||||
pnpm run test:edge # vitest (edge runtime)
|
||||
pnpm run test:node # vitest (node runtime)
|
||||
```
|
||||
|
||||
- **Build:** tsup (CJS + ESM)
|
||||
- **Lint:** ESLint + Prettier
|
||||
- **Test:** jest + vitest (edge/node configs)
|
||||
|
||||
### OpenClaw Plugin (`openclaw/`)
|
||||
|
||||
```bash
|
||||
cd openclaw
|
||||
pnpm install
|
||||
pnpm run build # tsup
|
||||
pnpm run test # vitest run
|
||||
```
|
||||
|
||||
- **Build:** tsup (ESM)
|
||||
- **Test:** vitest (with Codecov in CI)
|
||||
- **Plugin manifest:** `openclaw.plugin.json`
|
||||
|
||||
### Server (`server/`)
|
||||
|
||||
```bash
|
||||
# Docker production build
|
||||
cd server
|
||||
make build # docker build -t mem0-api-server .
|
||||
make run_local # docker run -p 8000:8000 with .env
|
||||
|
||||
# Docker Compose development (FastAPI + PostgreSQL/pgvector + Neo4j)
|
||||
cd server
|
||||
docker-compose up # starts all 3 services
|
||||
# mem0 API: localhost:8888
|
||||
# PostgreSQL: localhost:8432
|
||||
# Neo4j HTTP: localhost:8474, Bolt: localhost:8687
|
||||
```
|
||||
|
||||
- **Framework:** FastAPI with uvicorn (auto-reload in dev)
|
||||
- **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
|
||||
make docs # or: cd docs && mintlify dev
|
||||
```
|
||||
|
||||
- **Framework:** Mintlify
|
||||
- **API spec:** `docs/openapi.json`
|
||||
- **Structure:** `api-reference/`, `open-source/`, `platform/`, `integrations/`, `cookbooks/`, `core-concepts/`
|
||||
|
||||
### Evaluation (`evaluation/`)
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Core APIs
|
||||
|
||||
### Python
|
||||
|
||||
| Function / Class | Purpose | Import |
|
||||
|-----------------|---------|--------|
|
||||
| `Memory` | Self-hosted memory (sync) | `from mem0 import Memory` |
|
||||
| `AsyncMemory` | Self-hosted memory (async) | `from mem0 import AsyncMemory` |
|
||||
| `MemoryClient` | Hosted platform client (sync) | `from mem0 import MemoryClient` |
|
||||
| `AsyncMemoryClient` | Hosted platform client (async) | `from mem0 import AsyncMemoryClient` |
|
||||
|
||||
**Key `Memory` / `MemoryClient` methods:**
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `add(messages, *, user_id, agent_id, run_id, metadata)` | Store a new memory |
|
||||
| `search(query, *, user_id, agent_id, run_id, limit, filters)` | Search memories |
|
||||
| `get(memory_id)` | Retrieve a single memory by ID |
|
||||
| `get_all(*, user_id, agent_id, run_id, limit)` | List all memories |
|
||||
| `update(memory_id, data)` | Update a memory |
|
||||
| `delete(memory_id)` | Delete a memory |
|
||||
| `delete_all(*, user_id, agent_id, run_id)` | Delete all memories |
|
||||
| `history(memory_id)` | Get change history for a memory |
|
||||
|
||||
### TypeScript
|
||||
|
||||
| Export | Purpose | Import |
|
||||
|--------|---------|--------|
|
||||
| `MemoryClient` | Hosted platform client | `import { MemoryClient } from 'mem0ai'` |
|
||||
| `Memory` | Self-hosted OSS memory | `import { Memory } from 'mem0ai/oss'` |
|
||||
|
||||
## Import Patterns
|
||||
|
||||
### Python
|
||||
|
||||
| What | Import |
|
||||
|------|--------|
|
||||
| Core memory classes | `from mem0 import Memory, AsyncMemory` |
|
||||
| Platform client | `from mem0 import MemoryClient, AsyncMemoryClient` |
|
||||
| Configuration | `from mem0.configs.base import MemoryConfig` |
|
||||
| LLM providers | `from mem0.llms.<provider> import <ProviderLLM>` |
|
||||
| Embedding providers | `from mem0.embeddings.<provider> import <ProviderEmbedding>` |
|
||||
| Vector store providers | `from mem0.vector_stores.<provider> import <ProviderVectorStore>` |
|
||||
|
||||
### TypeScript
|
||||
|
||||
| What | Import |
|
||||
|------|--------|
|
||||
| Hosted client | `import { MemoryClient } from 'mem0ai'` |
|
||||
| OSS memory | `import { Memory } from 'mem0ai/oss'` |
|
||||
| Specific providers (OSS) | `import { OpenAIEmbedding } from 'mem0ai/oss'` |
|
||||
|
||||
## Coding Standards
|
||||
|
||||
### File Naming Conventions
|
||||
|
||||
- **Python source files:** `snake_case.py` (e.g., `azure_openai.py`, `cohere_reranker.py`)
|
||||
- **Python test files:** `test_<module>.py` (e.g., `test_memory.py`, `test_main.py`)
|
||||
- **TypeScript source files:** `snake_case.ts` (e.g., `azure_ai_search.ts`)
|
||||
- **TypeScript test files:** `<module>.test.ts` (e.g., `memory.test.ts`)
|
||||
- **Config/manifest files:** `kebab-case` (e.g., `openclaw.plugin.json`, `jest.config.js`)
|
||||
|
||||
### Python Conventions
|
||||
|
||||
- **Provider pattern:** All providers (LLMs, embeddings, vector stores, graphs, rerankers) inherit from a `base.py` abstract class in their directory. Config classes live in `configs.py`.
|
||||
- **Pydantic v2** for all data models and configuration.
|
||||
- **Ruff** is the single linting and formatting tool — no black, no flake8.
|
||||
- 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
|
||||
|
||||
- **Build:** tsup across all packages.
|
||||
- **Package manager:** pnpm everywhere (no npm, no yarn).
|
||||
- **TypeScript strict mode** across all packages.
|
||||
- **Linting varies by package:**
|
||||
|
||||
| Package | Linter | Formatter | Test Framework |
|
||||
|---------|--------|-----------|---------------|
|
||||
| `mem0-ts/` | — | Prettier | jest |
|
||||
| `cli/node/` | Biome | Biome | vitest |
|
||||
| `vercel-ai-sdk/` | ESLint | Prettier | jest + vitest |
|
||||
| `openclaw/` | — | — | vitest |
|
||||
|
||||
### Type Checking
|
||||
|
||||
Always run type checking after modifying TypeScript code:
|
||||
|
||||
```bash
|
||||
cd <package> && pnpm run typecheck # or: tsc --noEmit
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Provider Pattern
|
||||
|
||||
The SDK uses a consistent plugin architecture across 5 categories. Each category has a `base.py` abstract class and concrete provider implementations:
|
||||
|
||||
| Category | Count | Examples |
|
||||
|----------|-------|---------|
|
||||
| **LLMs** | 24 | OpenAI, Anthropic, AWS Bedrock, Azure OpenAI, Gemini, Groq, Ollama, Together, DeepSeek, vLLM, LiteLLM, LM Studio, xAI |
|
||||
| **Vector Stores** | 30 | Qdrant, Pinecone, Chroma, Weaviate, Milvus, MongoDB, Redis, Elasticsearch, pgvector, Supabase, Faiss, S3 Vectors |
|
||||
| **Embeddings** | 15 | OpenAI, Azure OpenAI, Gemini, HuggingFace, FastEmbed, Together, AWS Bedrock, Ollama, Vertex AI |
|
||||
| **Graph Stores** | 4 | Neo4j, Memgraph, Kuzu, Apache AGE |
|
||||
| **Rerankers** | 5 | Cohere, HuggingFace, LLM-based, Sentence Transformer, Zero Entropy |
|
||||
|
||||
### Two Usage Modes
|
||||
|
||||
Self-hosted `Memory` / `AsyncMemory` classes and hosted-platform `MemoryClient` — both in Python and TypeScript.
|
||||
|
||||
### Graph Memory
|
||||
|
||||
Optional layer on top of vector memory for relationship-aware retrieval. Configured via the `graph` section of `MemoryConfig`.
|
||||
|
||||
### MCP Integration
|
||||
|
||||
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 & Skills System
|
||||
|
||||
- `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, covering SDK usage, CLI workflows, and Vercel AI SDK patterns.
|
||||
|
||||
### Adding a New Provider
|
||||
|
||||
To add a new LLM, embedding, vector store, or reranker provider:
|
||||
|
||||
1. Create `mem0/<category>/<provider_name>.py`
|
||||
2. Inherit from the abstract base class in `mem0/<category>/base.py`
|
||||
3. Add configuration to `mem0/<category>/configs.py` (if the category uses one)
|
||||
4. Register the provider in `mem0/<category>/__init__.py`
|
||||
5. Add tests in `tests/<category>/<provider_name>/`
|
||||
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
|
||||
|
||||
## 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 |
|
||||
|
||||
### CD Workflows (automated publishing)
|
||||
|
||||
| Workflow | File | Tag Prefix | Target |
|
||||
|----------|------|------------|--------|
|
||||
| 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`) |
|
||||
|
||||
- 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.
|
||||
|
||||
### Utility Workflows
|
||||
|
||||
| Workflow | File | Purpose |
|
||||
|----------|------|---------|
|
||||
| Issue Labeler | `issue-labeler.yml` | Automatic issue labeling |
|
||||
| Stale Bot | `stale.yml` | Marks stale issues and PRs |
|
||||
|
||||
## Task Completion Guidelines
|
||||
|
||||
These guidelines outline typical artifacts for different task types. Use judgment to adapt based on scope and context.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
1. **Unit tests**: Add tests that would fail without the fix (regression tests)
|
||||
2. **Implementation**: Fix the bug
|
||||
3. **Manual verification**: Run the relevant test suite to confirm the fix
|
||||
4. **Lint**: Run the appropriate linter for the package you modified
|
||||
|
||||
### New Features
|
||||
|
||||
1. **Implementation**: Build the feature following existing patterns
|
||||
2. **Unit tests**: Comprehensive test coverage for new functionality
|
||||
3. **Documentation**: Update relevant docs in `docs/` for public APIs
|
||||
4. **Examples**: Add usage examples if the feature introduces new user-facing behavior
|
||||
|
||||
### New Provider (LLM / Embedding / Vector Store / Reranker)
|
||||
|
||||
1. **Implementation**: Follow the "Adding a New Provider" steps above
|
||||
2. **Tests**: Add unit tests matching the pattern of existing providers
|
||||
3. **Configuration**: Add to the appropriate `configs.py` and `__init__.py`
|
||||
4. **Dependencies**: Add to the correct optional group in `pyproject.toml`
|
||||
5. **Documentation**: Add an integration guide in `docs/integrations/`
|
||||
|
||||
### Refactoring / Internal Changes
|
||||
|
||||
- Unit tests for any changed behavior
|
||||
- No documentation needed for internal-only changes
|
||||
- Ensure all existing tests still pass
|
||||
|
||||
### When to Deviate
|
||||
|
||||
These are guidelines, not rigid rules. Adjust based on:
|
||||
|
||||
- **Scope**: Trivial fixes (typos, comments) may not need tests
|
||||
- **Visibility**: Internal changes may not need documentation
|
||||
- **Context**: Some changes span multiple categories — use judgment
|
||||
|
||||
When uncertain about expected artifacts, ask for clarification.
|
||||
|
||||
## Contributing Guidelines
|
||||
|
||||
### Workflow
|
||||
|
||||
1. Fork and clone the repository.
|
||||
2. Create a feature branch from `main` (e.g., `feature/my-new-feature`).
|
||||
3. Make your changes — add tests, docs, and examples as appropriate.
|
||||
4. Run linting and tests for every package you modified (see commands above).
|
||||
5. Run `pre-commit install` on first setup — hooks run ruff + isort automatically.
|
||||
6. Commit with a clear message following [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat:`, `fix:`, `docs:`, `refactor:`).
|
||||
7. Push and open a Pull Request against `main`.
|
||||
|
||||
### Pull Request Requirements
|
||||
|
||||
Every PR must follow the repo's PR template (`.github/PULL_REQUEST_TEMPLATE.md`):
|
||||
|
||||
1. **Linked Issue** — Reference the issue with `Closes #<number>`. If no issue exists, create one first or explain why in the description.
|
||||
2. **Description** — Explain what the PR does and why it's needed.
|
||||
3. **Type of Change** — Check the appropriate box:
|
||||
- Bug fix / New feature / Breaking change / Refactor / Documentation update
|
||||
4. **Breaking Changes** — If applicable, describe what breaks and the migration path.
|
||||
5. **Test Coverage** — Check what applies:
|
||||
- Added/updated unit tests
|
||||
- Added/updated integration tests
|
||||
- Tested manually (describe how)
|
||||
- No tests needed (explain why)
|
||||
6. **Checklist** — All must be checked before merge:
|
||||
- [ ] Code follows the project's style guidelines
|
||||
- [ ] Self-review performed
|
||||
- [ ] Tests added that prove the fix/feature works
|
||||
- [ ] New and existing tests pass locally
|
||||
- [ ] Documentation updated if needed
|
||||
|
||||
### PR Description Template
|
||||
|
||||
```markdown
|
||||
## Linked Issue
|
||||
|
||||
Closes #<!-- issue number -->
|
||||
|
||||
## Description
|
||||
|
||||
<!-- What does this PR do? Why is it needed? -->
|
||||
|
||||
## Type of Change
|
||||
|
||||
- [ ] Bug fix (non-breaking change that fixes an issue)
|
||||
- [ ] New feature (non-breaking change that adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
- [ ] Refactor (no functional changes)
|
||||
- [ ] Documentation update
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
N/A
|
||||
|
||||
## Test Coverage
|
||||
|
||||
- [ ] I added/updated unit tests
|
||||
- [ ] I added/updated integration tests
|
||||
- [ ] I tested manually (describe below)
|
||||
- [ ] No tests needed (explain why)
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] My code follows the project's style guidelines
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] I have added tests that prove my fix/feature works
|
||||
- [ ] New and existing tests pass locally
|
||||
- [ ] I have updated documentation if needed
|
||||
```
|
||||
|
||||
### General Rules
|
||||
|
||||
- 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.
|
||||
- Do NOT use `pip` or `conda` for dependency management — use `hatch` (see `docs/contributing/development.mdx`).
|
||||
|
||||
### Contributing Guides
|
||||
|
||||
| Task | Guide |
|
||||
|------|-------|
|
||||
| Code contributions | `docs/contributing/development.mdx` |
|
||||
| Documentation contributions | `docs/contributing/documentation.mdx` |
|
||||
| PR template | `.github/PULL_REQUEST_TEMPLATE.md` |
|
||||
| Bug reports | `.github/ISSUE_TEMPLATE/bug_report.yml` |
|
||||
| Feature requests | `.github/ISSUE_TEMPLATE/feature_request.yml` |
|
||||
| Documentation issues | `.github/ISSUE_TEMPLATE/documentation_issue.yml` |
|
||||
|
||||
## Do NOT
|
||||
|
||||
- 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/`.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/cli",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"description": "The official CLI for mem0 — the memory layer for AI agents",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
@@ -116,6 +116,7 @@ export class PlatformBackend implements Backend {
|
||||
if (opts.expires) payload.expiration_date = opts.expires;
|
||||
if (opts.categories) payload.categories = opts.categories;
|
||||
if (opts.enableGraph) payload.enable_graph = true;
|
||||
payload.source = "CLI";
|
||||
|
||||
return (await this._request("POST", "/v1/memories/", {
|
||||
json: payload,
|
||||
@@ -176,6 +177,7 @@ export class PlatformBackend implements Backend {
|
||||
if (opts.keyword) payload.keyword_search = true;
|
||||
if (opts.fields) payload.fields = opts.fields;
|
||||
if (opts.enableGraph) payload.enable_graph = true;
|
||||
payload.source = "CLI";
|
||||
|
||||
const result = (await this._request("POST", "/v2/memories/search/", {
|
||||
json: payload,
|
||||
@@ -186,10 +188,9 @@ export class PlatformBackend implements Backend {
|
||||
}
|
||||
|
||||
async get(memoryId: string): Promise<Record<string, unknown>> {
|
||||
return (await this._request("GET", `/v1/memories/${memoryId}/`)) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
return (await this._request("GET", `/v1/memories/${memoryId}/`, {
|
||||
params: { source: "CLI" },
|
||||
})) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
async listMemories(
|
||||
@@ -227,6 +228,7 @@ export class PlatformBackend implements Backend {
|
||||
});
|
||||
if (apiFilters) payload.filters = apiFilters;
|
||||
if (opts.enableGraph) payload.enable_graph = true;
|
||||
payload.source = "CLI";
|
||||
|
||||
const result = (await this._request("POST", "/v2/memories/", {
|
||||
json: payload,
|
||||
@@ -245,6 +247,7 @@ export class PlatformBackend implements Backend {
|
||||
const payload: Record<string, unknown> = {};
|
||||
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>;
|
||||
@@ -255,7 +258,7 @@ export class PlatformBackend implements Backend {
|
||||
opts: DeleteOptions = {},
|
||||
): Promise<Record<string, unknown>> {
|
||||
if (opts.all) {
|
||||
const params: Record<string, string> = {};
|
||||
const params: Record<string, string> = { source: "CLI" };
|
||||
if (opts.userId) params.user_id = opts.userId;
|
||||
if (opts.agentId) params.agent_id = opts.agentId;
|
||||
if (opts.appId) params.app_id = opts.appId;
|
||||
@@ -265,10 +268,9 @@ export class PlatformBackend implements Backend {
|
||||
})) as Record<string, unknown>;
|
||||
}
|
||||
if (memoryId) {
|
||||
return (await this._request(
|
||||
"DELETE",
|
||||
`/v1/memories/${memoryId}/`,
|
||||
)) as Record<string, unknown>;
|
||||
return (await this._request("DELETE", `/v1/memories/${memoryId}/`, {
|
||||
params: { source: "CLI" },
|
||||
})) as Record<string, unknown>;
|
||||
}
|
||||
throw new Error("Either memoryId or --all is required");
|
||||
}
|
||||
@@ -291,6 +293,7 @@ export class PlatformBackend implements Backend {
|
||||
result = (await this._request(
|
||||
"DELETE",
|
||||
`/v2/entities/${entityType}/${entityId}/`,
|
||||
{ params: { source: "CLI" } },
|
||||
)) as Record<string, unknown>;
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -31,10 +31,15 @@ export interface DefaultsConfig {
|
||||
enableGraph: boolean;
|
||||
}
|
||||
|
||||
export interface TelemetryConfig {
|
||||
anonymousId: string;
|
||||
}
|
||||
|
||||
export interface Mem0Config {
|
||||
version: number;
|
||||
defaults: DefaultsConfig;
|
||||
platform: PlatformConfig;
|
||||
telemetry: TelemetryConfig;
|
||||
}
|
||||
|
||||
export function createDefaultConfig(): Mem0Config {
|
||||
@@ -52,6 +57,9 @@ export function createDefaultConfig(): Mem0Config {
|
||||
baseUrl: DEFAULT_BASE_URL,
|
||||
userEmail: "",
|
||||
},
|
||||
telemetry: {
|
||||
anonymousId: "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -80,6 +88,9 @@ export function loadConfig(): Mem0Config {
|
||||
config.defaults.appId = defaults.app_id ?? "";
|
||||
config.defaults.runId = defaults.run_id ?? "";
|
||||
config.defaults.enableGraph = defaults.enable_graph ?? false;
|
||||
|
||||
const telemetry = data.telemetry ?? {};
|
||||
config.telemetry.anonymousId = telemetry.anonymous_id ?? "";
|
||||
}
|
||||
|
||||
// Environment variable overrides
|
||||
@@ -119,6 +130,9 @@ export function saveConfig(config: Mem0Config): void {
|
||||
base_url: config.platform.baseUrl,
|
||||
user_email: config.platform.userEmail,
|
||||
},
|
||||
telemetry: {
|
||||
anonymous_id: config.telemetry.anonymousId,
|
||||
},
|
||||
};
|
||||
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2));
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
*/
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { CONFIG_FILE, loadConfig } from "./config.js";
|
||||
import { CONFIG_FILE, loadConfig, saveConfig } from "./config.js";
|
||||
import { CLI_VERSION } from "./version.js";
|
||||
|
||||
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
|
||||
@@ -29,11 +29,34 @@ function isTelemetryEnabled(): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a persistent per-machine anonymous ID, generating one if needed.
|
||||
*
|
||||
* Stored in ~/.mem0/config.json under `telemetry.anonymous_id` so that
|
||||
* repeat runs on the same machine share one PostHog identity instead of
|
||||
* collapsing into a single shared fallback string.
|
||||
*/
|
||||
function getOrCreateAnonymousId(): string {
|
||||
const config = loadConfig();
|
||||
if (config.telemetry.anonymousId) {
|
||||
return config.telemetry.anonymousId;
|
||||
}
|
||||
|
||||
const newId = `cli-anon-${randomUUID().replace(/-/g, "")}`;
|
||||
config.telemetry.anonymousId = newId;
|
||||
try {
|
||||
saveConfig(config);
|
||||
} catch {
|
||||
/* ignore persistence failure — still return the generated ID */
|
||||
}
|
||||
return newId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a stable anonymous identifier for the current user.
|
||||
*
|
||||
* Priority: cached user_email (from /v1/ping/) > MD5(api_key) > fallback.
|
||||
* Matches the SDK pattern in mem0-ts/src/client/mem0.ts.
|
||||
* Priority: cached user_email (from /v1/ping/) > MD5(api_key) >
|
||||
* persistent per-machine anonymous ID.
|
||||
*/
|
||||
function getDistinctId(): string {
|
||||
try {
|
||||
@@ -47,7 +70,11 @@ function getDistinctId(): string {
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return "anonymous-cli";
|
||||
try {
|
||||
return getOrCreateAnonymousId();
|
||||
} catch {
|
||||
return `cli-anon-${randomUUID().replace(/-/g, "")}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,6 +96,25 @@ export function captureEvent(
|
||||
const config = loadConfig();
|
||||
const distinctId = preResolvedEmail || getDistinctId();
|
||||
|
||||
// Detect anonymous → identified transition. If a stored anonymous_id
|
||||
// exists and we just resolved to a real identity, fire a one-shot
|
||||
// $identify event so PostHog stitches the pre-signup history onto
|
||||
// the authenticated profile. Clear the stored id so we don't re-alias.
|
||||
let anonIdToAlias: string | null = null;
|
||||
if (
|
||||
distinctId &&
|
||||
!distinctId.startsWith("cli-anon-") &&
|
||||
config.telemetry.anonymousId
|
||||
) {
|
||||
anonIdToAlias = config.telemetry.anonymousId;
|
||||
config.telemetry.anonymousId = "";
|
||||
try {
|
||||
saveConfig(config);
|
||||
} catch {
|
||||
/* ignore — alias may double-fire next run, harmless */
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
api_key: POSTHOG_API_KEY,
|
||||
distinct_id: distinctId,
|
||||
@@ -92,6 +138,7 @@ export function captureEvent(
|
||||
mem0ApiKey: config.platform.apiKey || "",
|
||||
mem0BaseUrl: config.platform.baseUrl || "https://api.mem0.ai",
|
||||
configPath: CONFIG_FILE,
|
||||
anonDistinctIdToAlias: anonIdToAlias,
|
||||
};
|
||||
|
||||
const child = spawn(
|
||||
|
||||
@@ -94,6 +94,19 @@ async function sendPosthogEvent(posthogHost, payload) {
|
||||
}
|
||||
}
|
||||
|
||||
async function sendIdentifyEvent(ctx, payload, anonId) {
|
||||
const identifyPayload = {
|
||||
api_key: payload.api_key,
|
||||
event: "$identify",
|
||||
distinct_id: payload.distinct_id,
|
||||
properties: {
|
||||
$anon_distinct_id: anonId,
|
||||
$lib: (payload.properties && payload.properties.$lib) || "posthog-node",
|
||||
},
|
||||
};
|
||||
await sendPosthogEvent(ctx.posthogHost, identifyPayload);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const ctx = JSON.parse(process.argv[2]);
|
||||
const payload = ctx.payload;
|
||||
@@ -102,6 +115,14 @@ async function main() {
|
||||
await resolveAndCacheEmail(ctx, payload);
|
||||
}
|
||||
|
||||
// Fire $identify *after* email resolution so PostHog links the stored
|
||||
// anonymous id directly to the final identity (email, not the api-key
|
||||
// hash). The regular event is sent next so it lands under the merged
|
||||
// profile.
|
||||
if (ctx.anonDistinctIdToAlias) {
|
||||
await sendIdentifyEvent(ctx, payload, ctx.anonDistinctIdToAlias);
|
||||
}
|
||||
|
||||
await sendPosthogEvent(ctx.posthogHost, payload);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "mem0-cli"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
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.2"
|
||||
__version__ = "0.2.3"
|
||||
|
||||
@@ -93,6 +93,7 @@ class PlatformBackend(Backend):
|
||||
payload["categories"] = categories
|
||||
if enable_graph:
|
||||
payload["enable_graph"] = True
|
||||
payload["source"] = "CLI"
|
||||
|
||||
return self._request("POST", "/v1/memories/", json=payload)
|
||||
|
||||
@@ -172,6 +173,7 @@ class PlatformBackend(Backend):
|
||||
payload["fields"] = fields
|
||||
if enable_graph:
|
||||
payload["enable_graph"] = True
|
||||
payload["source"] = "CLI"
|
||||
|
||||
result = self._request("POST", "/v2/memories/search/", json=payload)
|
||||
return (
|
||||
@@ -181,7 +183,7 @@ class PlatformBackend(Backend):
|
||||
)
|
||||
|
||||
def get(self, memory_id: str) -> dict:
|
||||
return self._request("GET", f"/v1/memories/{memory_id}/")
|
||||
return self._request("GET", f"/v1/memories/{memory_id}/", params={"source": "CLI"})
|
||||
|
||||
def list_memories(
|
||||
self,
|
||||
@@ -220,6 +222,7 @@ class PlatformBackend(Backend):
|
||||
payload["filters"] = api_filters
|
||||
if enable_graph:
|
||||
payload["enable_graph"] = True
|
||||
payload["source"] = "CLI"
|
||||
|
||||
result = self._request("POST", "/v2/memories/", json=payload, params=params)
|
||||
return (
|
||||
@@ -236,6 +239,7 @@ class PlatformBackend(Backend):
|
||||
payload["text"] = content
|
||||
if metadata:
|
||||
payload["metadata"] = metadata
|
||||
payload["source"] = "CLI"
|
||||
return self._request("PUT", f"/v1/memories/{memory_id}/", json=payload)
|
||||
|
||||
def delete(
|
||||
@@ -249,7 +253,7 @@ class PlatformBackend(Backend):
|
||||
run_id: str | None = None,
|
||||
) -> dict:
|
||||
if all:
|
||||
params: dict[str, str] = {}
|
||||
params: dict[str, str] = {"source": "CLI"}
|
||||
if user_id:
|
||||
params["user_id"] = user_id
|
||||
if agent_id:
|
||||
@@ -260,7 +264,7 @@ 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}/")
|
||||
return self._request("DELETE", f"/v1/memories/{memory_id}/", params={"source": "CLI"})
|
||||
else:
|
||||
raise ValueError("Either memory_id or --all is required")
|
||||
|
||||
@@ -285,7 +289,9 @@ class PlatformBackend(Backend):
|
||||
# Delete each provided entity via the v2 path-based endpoint
|
||||
result: dict = {}
|
||||
for entity_type, entity_id in entities.items():
|
||||
result = self._request("DELETE", f"/v2/entities/{entity_type}/{entity_id}/")
|
||||
result = self._request(
|
||||
"DELETE", f"/v2/entities/{entity_type}/{entity_id}/", params={"source": "CLI"}
|
||||
)
|
||||
return result
|
||||
|
||||
def ping(self, timeout: float | None = None) -> dict:
|
||||
|
||||
@@ -39,11 +39,17 @@ class DefaultsConfig:
|
||||
enable_graph: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class TelemetryConfig:
|
||||
anonymous_id: 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)
|
||||
|
||||
|
||||
SHORT_KEY_ALIASES: dict[str, str] = {
|
||||
@@ -87,6 +93,9 @@ def load_config() -> Mem0Config:
|
||||
config.defaults.run_id = defaults.get("run_id", "")
|
||||
config.defaults.enable_graph = defaults.get("enable_graph", False)
|
||||
|
||||
telemetry = data.get("telemetry", {})
|
||||
config.telemetry.anonymous_id = telemetry.get("anonymous_id", "")
|
||||
|
||||
# Environment variable overrides
|
||||
env_key = os.environ.get("MEM0_API_KEY")
|
||||
if env_key:
|
||||
@@ -137,6 +146,9 @@ def save_config(config: Mem0Config) -> None:
|
||||
"base_url": config.platform.base_url,
|
||||
"user_email": config.platform.user_email,
|
||||
},
|
||||
"telemetry": {
|
||||
"anonymous_id": config.telemetry.anonymous_id,
|
||||
},
|
||||
}
|
||||
|
||||
with open(CONFIG_FILE, "w") as f:
|
||||
|
||||
@@ -9,12 +9,14 @@ Disable with: MEM0_TELEMETRY=false
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
||||
@@ -26,11 +28,31 @@ def _is_telemetry_enabled() -> bool:
|
||||
return val not in ("false", "0", "no")
|
||||
|
||||
|
||||
def _get_or_create_anonymous_id() -> str:
|
||||
"""Return a persistent per-machine anonymous ID, generating one if needed.
|
||||
|
||||
Stored in ~/.mem0/config.json under `telemetry.anonymous_id` so that
|
||||
repeat runs on the same machine share one PostHog identity instead of
|
||||
collapsing into a single shared fallback string.
|
||||
"""
|
||||
from mem0_cli.config import load_config, save_config
|
||||
|
||||
config = load_config()
|
||||
if config.telemetry.anonymous_id:
|
||||
return config.telemetry.anonymous_id
|
||||
|
||||
new_id = f"cli-anon-{uuid.uuid4().hex}"
|
||||
config.telemetry.anonymous_id = new_id
|
||||
with contextlib.suppress(Exception):
|
||||
save_config(config)
|
||||
return new_id
|
||||
|
||||
|
||||
def _get_distinct_id() -> str:
|
||||
"""Return a stable anonymous identifier for the current user.
|
||||
|
||||
Priority: cached user_email (from /v1/ping/) > MD5(api_key) > fallback.
|
||||
Matches the SDK pattern in mem0/client/main.py.
|
||||
Priority: cached user_email (from /v1/ping/) > MD5(api_key) >
|
||||
persistent per-machine anonymous ID.
|
||||
"""
|
||||
try:
|
||||
from mem0_cli.config import load_config
|
||||
@@ -42,7 +64,10 @@ def _get_distinct_id() -> str:
|
||||
return hashlib.md5(config.platform.api_key.encode()).hexdigest()
|
||||
except Exception:
|
||||
pass
|
||||
return "anonymous-cli"
|
||||
try:
|
||||
return _get_or_create_anonymous_id()
|
||||
except Exception:
|
||||
return f"cli-anon-{uuid.uuid4().hex}"
|
||||
|
||||
|
||||
def capture_event(
|
||||
@@ -61,12 +86,27 @@ def capture_event(
|
||||
|
||||
try:
|
||||
from mem0_cli import __version__
|
||||
from mem0_cli.config import CONFIG_FILE, load_config
|
||||
from mem0_cli.config import CONFIG_FILE, load_config, save_config
|
||||
from mem0_cli.state import is_agent_mode
|
||||
|
||||
config = load_config()
|
||||
distinct_id = pre_resolved_email or _get_distinct_id()
|
||||
|
||||
# Detect anonymous → identified transition. If a stored anonymous_id
|
||||
# exists and we just resolved to a real identity, fire a one-shot
|
||||
# $identify event so PostHog stitches the pre-signup history onto
|
||||
# the authenticated profile. Clear the stored id so we don't re-alias.
|
||||
anon_id_to_alias: str | None = None
|
||||
if (
|
||||
distinct_id
|
||||
and not distinct_id.startswith("cli-anon-")
|
||||
and config.telemetry.anonymous_id
|
||||
):
|
||||
anon_id_to_alias = config.telemetry.anonymous_id
|
||||
config.telemetry.anonymous_id = ""
|
||||
with contextlib.suppress(Exception):
|
||||
save_config(config)
|
||||
|
||||
payload = {
|
||||
"api_key": POSTHOG_API_KEY,
|
||||
"distinct_id": distinct_id,
|
||||
@@ -92,6 +132,7 @@ def capture_event(
|
||||
"mem0_api_key": config.platform.api_key or "",
|
||||
"mem0_base_url": config.platform.base_url or "https://api.mem0.ai",
|
||||
"config_path": str(CONFIG_FILE),
|
||||
"anon_distinct_id_to_alias": anon_id_to_alias,
|
||||
}
|
||||
|
||||
subprocess.Popen(
|
||||
|
||||
@@ -27,9 +27,31 @@ def main() -> None:
|
||||
if ctx.get("needs_email") and ctx.get("mem0_api_key"):
|
||||
_resolve_and_cache_email(ctx, payload)
|
||||
|
||||
# Fire $identify *after* email resolution so PostHog links the stored
|
||||
# anonymous id directly to the final identity (email, not the api-key
|
||||
# hash). The regular event is sent next so it lands under the merged
|
||||
# profile.
|
||||
anon_id = ctx.get("anon_distinct_id_to_alias")
|
||||
if anon_id:
|
||||
_send_identify_event(ctx, payload, anon_id)
|
||||
|
||||
_send_posthog_event(ctx["posthog_host"], payload)
|
||||
|
||||
|
||||
def _send_identify_event(ctx: dict, payload: dict, anon_id: str) -> None:
|
||||
"""Send a PostHog $identify event aliasing anon_id → payload['distinct_id']."""
|
||||
identify_payload = {
|
||||
"api_key": payload["api_key"],
|
||||
"event": "$identify",
|
||||
"distinct_id": payload["distinct_id"],
|
||||
"properties": {
|
||||
"$anon_distinct_id": anon_id,
|
||||
"$lib": payload.get("properties", {}).get("$lib", "posthog-python"),
|
||||
},
|
||||
}
|
||||
_send_posthog_event(ctx["posthog_host"], identify_payload)
|
||||
|
||||
|
||||
def _resolve_and_cache_email(ctx: dict, payload: dict) -> None:
|
||||
"""Call /v1/ping/ to get the user's email, update the payload, and cache it."""
|
||||
try:
|
||||
|
||||
@@ -10,7 +10,7 @@ description: "REST APIs for memory management, search, and entity operations"
|
||||
Mem0 provides a comprehensive REST API for integrating advanced memory capabilities into your applications. Create, search, update, and manage memories across users, agents, and custom entities with simple HTTP requests.
|
||||
|
||||
<Info>
|
||||
**Quick start:** Get your API key from the [Mem0 Dashboard](https://app.mem0.ai/dashboard/api-keys) and make your first memory operation in minutes.
|
||||
**Quick start:** Get your API key from the <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 Dashboard</a> and make your first memory operation in minutes.
|
||||
</Info>
|
||||
|
||||
---
|
||||
@@ -87,7 +87,7 @@ All API requests require authentication using Token-based authentication. Includ
|
||||
Authorization: Token <your-api-key>
|
||||
```
|
||||
|
||||
Get your API key from the [Mem0 Dashboard](https://app.mem0.ai/dashboard/api-keys).
|
||||
Get your API key from the <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 Dashboard</a>.
|
||||
|
||||
<Warning>
|
||||
**Keep your API key secure.** Never expose it in client-side code or public repositories. Use environment variables and server-side requests only.
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: "Highlights"
|
||||
description: "Major product launches, headline features, and milestones for Mem0."
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
<Update label="2026-04-06" description="Mem0 Skill Graph">
|
||||
|
||||
**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:
|
||||
|
||||
- **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
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-06" description="Mem0 CLI v0.2.2">
|
||||
|
||||
**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-04" 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
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-03-21" description="New Providers">
|
||||
|
||||
**Apache AGE, Turbopuffer, MiniMax, and pgvector for Node.js**
|
||||
|
||||
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
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-03-14" description="Mem0 Platform Skill">
|
||||
|
||||
**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.
|
||||
|
||||
</Update>
|
||||
@@ -0,0 +1,199 @@
|
||||
---
|
||||
title: "OpenClaw"
|
||||
description: "Release notes for the OpenClaw plugin and agent harness."
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
<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>
|
||||
@@ -0,0 +1,290 @@
|
||||
---
|
||||
title: "Platform"
|
||||
description: "Release notes for the Mem0 hosted platform — backend, dashboard, billing, and infrastructure changes."
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
<Update label="2025-07-23" description="">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Memory:** Fixed ADD functionality
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-19" description="">
|
||||
|
||||
**New Features:**
|
||||
- **UI:** Added Settings UI and latency display
|
||||
- **Performance:** Neo4j query optimization
|
||||
|
||||
**Bug Fixes:**
|
||||
- **OpenMemory:** Fixed OMM raising unnecessary exceptions
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-18" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **UI:** Updated Event UI
|
||||
- **Performance:** Fixed N+1 query issue in semantic_search_v2 by optimizing MemorySerializer field selection
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Memory:** Fixed duplicate memory index sentry error
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-17" description="">
|
||||
|
||||
**New Features:**
|
||||
- **UI:** New Settings Page
|
||||
- **Memory:** Duplicate memories entities support
|
||||
|
||||
**Improvements:**
|
||||
- **Performance:** Optimized semantic search and get_all APIs by eliminating N+1 queries
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-16" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Database:** Implemented read replica routing with enhanced logging and app-specific DB routing
|
||||
|
||||
**Improvements:**
|
||||
- **Performance:** Improved query performance in search v2 and get all v2 endpoints
|
||||
|
||||
**Bug Fixes:**
|
||||
- **API:** Fixed pagination for get all API
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-12" description="">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Graph:** Fixed social graph bugs and connection issues
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-11" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Rate Limiting:** New rate limit for V2 Search
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Slack:** Fixed Slack rate limit error with backend improvements
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-10" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Performance:**
|
||||
- Changed connection pooling time to 5 minutes
|
||||
- Separated graph lambdas for better performance
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-09" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Graph:** Graph Optimizations V2 and memory improvements
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-08" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Database:** Added read replica support for improved database performance
|
||||
- **UI:** Implemented UI changes for Users Page
|
||||
- **Feedback:** Enabled feedback functionality
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Serializer:** Fixed GET ALL Serializer
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-05" description="">
|
||||
|
||||
**New Features:**
|
||||
- **UI:** User Page Revamp and New Users Page
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-04" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Users:** New Users Page implementation
|
||||
- **Tools:** Added script to backfill memory categories
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Filters:** Fixed Filters Get All functionality
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-03" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Graph:** Graph Memory optimization
|
||||
- **Memory:** Fixed exact memories and semantically similar memories retrieval
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-02" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Categorization:** Refactored categorization logic to utilize Gemini 2.5 Flash and improve message handling
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-01" description="">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Memory:** Fixed old_memory issue in Async memory addition lambda
|
||||
- **Events:** Fixed missing events
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-30" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Graph:** Improvements to graph memory and added user to LTM-STM
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-28" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Graph:** Added support for SQS in graph memory addition
|
||||
- **Testing:** Added Locust load testing script and Grafana Dashboard
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-27" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Rate Limiting:** Updated rate limiting for ADD API to 1000/min
|
||||
- **Performance:** Improved Neo4j performance
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-26" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Memory:** Edit Memory From Drawer functionality
|
||||
- **API:** Added Topic Suggestions API Endpoint
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-25" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Group Chat:** Group-Chat v2 with Actor-Aware Memories
|
||||
- **Memory:** Editable Metadata in Memories
|
||||
- **UI:** Memory Actions Badges
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-19" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Rate Limiting:** Implemented comprehensive rate limiting system
|
||||
|
||||
**Improvements:**
|
||||
- **Performance:** Added performance indexes for memory stats query
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Search:** Fixed search events not respecting top-k parameter
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-18" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Memory Management:** Implemented OpenAI Batch API for Memory Cleaning with fallback
|
||||
- **Playground:** Added Claude 4 support on Playground
|
||||
|
||||
**Improvements:**
|
||||
- **Memory:** Added ability to update memory metadata
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-17" description="">
|
||||
|
||||
**New Features:**
|
||||
- **UI:** New Memories Page UI design
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-16" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Infrastructure:** Migrated to Application Load Balancer (ALB)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-13" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Memory Management:** Enhanced Memory Management with Cosine Similarity Fallback
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-11" description="">
|
||||
|
||||
**New Features:**
|
||||
- **OMM:** Added OMM Script and UI functionality
|
||||
|
||||
**Improvements:**
|
||||
- **API:** Added filters validation to semantic_search_v2 endpoint
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-09" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Intercom:** Set Intercom events for ADD and SEARCH operations
|
||||
- **OpenMemory:** Added Posthog integration and feedback functionality
|
||||
- **MCP:** New JavaScript MCP Server with feedback support
|
||||
|
||||
**Improvements:**
|
||||
- **Structured Data:** Enhanced structured data handling in memory management
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-06" description="">
|
||||
|
||||
**New Features:**
|
||||
- **OAuth:** Added Mem0 OAuth integration
|
||||
- **OMM:** Added OMM-Mem0 sync for deleted memories
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-05" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Filters:** Implemented Wildcard Filters and refactored filter logic in V2 Views
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-02" description="">
|
||||
|
||||
**New Features:**
|
||||
- **OpenMemory Cloud:** Added OpenMemory Cloud support
|
||||
- **Structured Data:** Added 'structured_attributes' field to Memory model
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-05-30" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Projects:** Added version and enable_graph to project views
|
||||
- **OpenMemory:** Added Postgres support for OpenMemory
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-05-19" description="">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Core:** Fixed unicode error in user_id, agent_id, run_id and app_id
|
||||
|
||||
</Update>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
---
|
||||
title: "Product Updates"
|
||||
description: "Latest releases, bug fixes, and improvements for the Mem0 Python and TypeScript SDKs."
|
||||
title: "SDK & Tools"
|
||||
description: "Release notes for the Mem0 Python SDK, TypeScript SDK, Vercel AI SDK, CLI, and editor plugins."
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
|
||||
@@ -844,7 +843,6 @@ mode: "wide"
|
||||
</Tab>
|
||||
|
||||
<Tab title="TypeScript">
|
||||
|
||||
<Update label="2026-04-04" description="v2.4.6">
|
||||
|
||||
**New Features & Updates:**
|
||||
@@ -1160,352 +1158,152 @@ mode: "wide"
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Platform">
|
||||
<Tab title="CLI">
|
||||
|
||||
<Update label="2025-07-23" description="">
|
||||
<Update label="2026-04-11" description="Python v0.2.3 / Node v0.2.3">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Memory:** Fixed ADD functionality
|
||||
- **Telemetry:** Replaced shared `"anonymous-cli"` fallback with a persistent per-machine random hash (`cli-anon-<uuid>`), so anonymous CLI users are counted individually in PostHog instead of collapsing into one identity ([#4789](https://github.com/mem0ai/mem0/pull/4789))
|
||||
- **Telemetry:** Added PostHog `$identify` event on first authenticated run to stitch pre-signup anonymous history onto the authenticated user profile ([#4789](https://github.com/mem0ai/mem0/pull/4789))
|
||||
|
||||
**Improvements:**
|
||||
- **API:** All API calls now include `source=CLI` in request bodies (POST/PUT) and query params (GET/DELETE) for server-side attribution ([#4789](https://github.com/mem0ai/mem0/pull/4789))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-19" description="">
|
||||
<Update label="2026-04-06" description="Python v0.2.2 / Node v0.2.2">
|
||||
|
||||
**New Features:**
|
||||
- **UI:** Added Settings UI and latency display
|
||||
- **Performance:** Neo4j query optimization
|
||||
- **Telemetry:** Added PostHog telemetry and source tracking to both Python and Node CLIs ([#4699](https://github.com/mem0ai/mem0/pull/4699))
|
||||
- **Validation:** API key validated upfront via `/v1/ping/` on startup — fail-fast with a helpful error instead of cryptic 401s ([#4701](https://github.com/mem0ai/mem0/pull/4701))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **OpenMemory:** Fixed OMM raising unnecessary exceptions
|
||||
- **CD:** Fixed OIDC trusted publishing with `npx npm@latest` ([#4724](https://github.com/mem0ai/mem0/pull/4724))
|
||||
- **CD:** Removed npm self-upgrade from CD workflows ([#4723](https://github.com/mem0ai/mem0/pull/4723))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-18" description="">
|
||||
<Update label="2026-04-03" description="Python v0.2.1 / Node v0.2.1">
|
||||
|
||||
**Improvements:**
|
||||
- **UI:** Updated Event UI
|
||||
- **Performance:** Fixed N+1 query issue in semantic_search_v2 by optimizing MemorySerializer field selection
|
||||
**New Features:**
|
||||
- **Docs:** Comprehensive README with installation, usage examples, and purple branding ([#4680](https://github.com/mem0ai/mem0/pull/4680))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Memory:** Fixed duplicate memory index sentry error
|
||||
- **npm:** Added `repository` field to Node packages for npm provenance ([#4671](https://github.com/mem0ai/mem0/pull/4671))
|
||||
- **CD:** Added CD workflows for Node SDK packages with OIDC trusted publishing ([#4670](https://github.com/mem0ai/mem0/pull/4670))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-17" description="">
|
||||
<Update label="2026-04-02" description="Python v0.2.0 / Node v0.1.1">
|
||||
|
||||
**New Features:**
|
||||
- **UI:** New Settings Page
|
||||
- **Memory:** Duplicate memories entities support
|
||||
|
||||
**Improvements:**
|
||||
- **Performance:** Optimized semantic search and get_all APIs by eliminating N+1 queries
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-16" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Database:** Implemented read replica routing with enhanced logging and app-specific DB routing
|
||||
|
||||
**Improvements:**
|
||||
- **Performance:** Improved query performance in search v2 and get all v2 endpoints
|
||||
- **`event` commands:** `mem0 event list` shows recent background processing events in a table; `mem0 event status <id>` shows full detail including nested memory results ([#4649](https://github.com/mem0ai/mem0/pull/4649))
|
||||
- **`--json` / `--agent` flag:** Root-level flag switches all command output to a structured JSON envelope for programmatic/agent consumption. Envelope format: `{"status", "command", "duration_ms", "scope", "count", "data"}` ([#4649](https://github.com/mem0ai/mem0/pull/4649))
|
||||
- **Agent output sanitization:** Raw API responses projected to only relevant fields per command (e.g., `add` → `{id, memory, event}`, `search` → `{id, memory, score, created_at, categories}`) ([#4649](https://github.com/mem0ai/mem0/pull/4649))
|
||||
- **Email login:** Added email verification code login to `mem0 init` ([#4623](https://github.com/mem0ai/mem0/pull/4623))
|
||||
- **Brand update:** Updated color palette from purple to golden ([#4664](https://github.com/mem0ai/mem0/pull/4664))
|
||||
- **CI/CD:** Added CI pipelines and CD workflows for both CLIs ([#4640](https://github.com/mem0ai/mem0/pull/4640), [#4653](https://github.com/mem0ai/mem0/pull/4653))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **API:** Fixed pagination for get all API
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-12" description="">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Graph:** Fixed social graph bugs and connection issues
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-11" description="">
|
||||
- **Node:** Fixed critical `MODULE_NOT_FOUND` crash on `status`, `import`, and all commands when installed globally — replaced runtime `createRequire` with build-time version injection ([#4636](https://github.com/mem0ai/mem0/pull/4636))
|
||||
- **Node:** API errors now show full response detail instead of bare "Bad Request" ([#4636](https://github.com/mem0ai/mem0/pull/4636))
|
||||
- **Python:** Fixed double error printing on all commands ([#4636](https://github.com/mem0ai/mem0/pull/4636))
|
||||
- **`status` command:** Replaced heavyweight `/v1/entities/` check with dedicated `GET /v1/ping/` endpoint ([#4649](https://github.com/mem0ai/mem0/pull/4649))
|
||||
- **`add` command:** Deduplicated PENDING results from API; changed misleading count message ([#4649](https://github.com/mem0ai/mem0/pull/4649))
|
||||
- **`init` command:** Partial flags now work in non-TTY; warns before overwriting existing config; added `--force` flag ([#4649](https://github.com/mem0ai/mem0/pull/4649))
|
||||
- **`delete` command:** Fixed entity delete via v2 API for all entity types ([#4649](https://github.com/mem0ai/mem0/pull/4649))
|
||||
|
||||
**Improvements:**
|
||||
- **Rate Limiting:** New rate limit for V2 Search
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Slack:** Fixed Slack rate limit error with backend improvements
|
||||
- Tables now show full UUIDs (was truncated to 8 chars, making `mem0 get <id>` fail) ([#4636](https://github.com/mem0ai/mem0/pull/4636))
|
||||
- Search table includes Score column ([#4636](https://github.com/mem0ai/mem0/pull/4636))
|
||||
- `config get api_key` short-form aliases added ([#4636](https://github.com/mem0ai/mem0/pull/4636))
|
||||
- Client-side validation for `--expires`, `--page-size`, `--page`, `--top-k`, `--threshold`, and empty content ([#4636](https://github.com/mem0ai/mem0/pull/4636))
|
||||
- `printInfo` / `printScope` moved to stderr to avoid contaminating JSON piping ([#4636](https://github.com/mem0ai/mem0/pull/4636))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-10" description="">
|
||||
<Update label="2026-03-26" description="Python v0.1.0 / Node v0.1.0">
|
||||
|
||||
**Improvements:**
|
||||
- **Performance:**
|
||||
- Changed connection pooling time to 5 minutes
|
||||
- Separated graph lambdas for better performance
|
||||
**Initial Release — Official Mem0 CLI**
|
||||
|
||||
</Update>
|
||||
A full-featured command-line interface for Mem0, available in both Python and Node.js:
|
||||
|
||||
<Update label="2025-07-09" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Graph:** Graph Optimizations V2 and memory improvements
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-08" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Database:** Added read replica support for improved database performance
|
||||
- **UI:** Implemented UI changes for Users Page
|
||||
- **Feedback:** Enabled feedback functionality
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Serializer:** Fixed GET ALL Serializer
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-05" description="">
|
||||
|
||||
**New Features:**
|
||||
- **UI:** User Page Revamp and New Users Page
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-04" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Users:** New Users Page implementation
|
||||
- **Tools:** Added script to backfill memory categories
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Filters:** Fixed Filters Get All functionality
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-03" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Graph:** Graph Memory optimization
|
||||
- **Memory:** Fixed exact memories and semantically similar memories retrieval
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-02" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Categorization:** Refactored categorization logic to utilize Gemini 2.5 Flash and improve message handling
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-07-01" description="">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Memory:** Fixed old_memory issue in Async memory addition lambda
|
||||
- **Events:** Fixed missing events
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-30" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Graph:** Improvements to graph memory and added user to LTM-STM
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-28" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Graph:** Added support for SQS in graph memory addition
|
||||
- **Testing:** Added Locust load testing script and Grafana Dashboard
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-27" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Rate Limiting:** Updated rate limiting for ADD API to 1000/min
|
||||
- **Performance:** Improved Neo4j performance
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-26" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Memory:** Edit Memory From Drawer functionality
|
||||
- **API:** Added Topic Suggestions API Endpoint
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-25" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Group Chat:** Group-Chat v2 with Actor-Aware Memories
|
||||
- **Memory:** Editable Metadata in Memories
|
||||
- **UI:** Memory Actions Badges
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-19" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Rate Limiting:** Implemented comprehensive rate limiting system
|
||||
|
||||
**Improvements:**
|
||||
- **Performance:** Added performance indexes for memory stats query
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Search:** Fixed search events not respecting top-k parameter
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-18" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Memory Management:** Implemented OpenAI Batch API for Memory Cleaning with fallback
|
||||
- **Playground:** Added Claude 4 support on Playground
|
||||
|
||||
**Improvements:**
|
||||
- **Memory:** Added ability to update memory metadata
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-17" description="">
|
||||
|
||||
**New Features:**
|
||||
- **UI:** New Memories Page UI design
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-16" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Infrastructure:** Migrated to Application Load Balancer (ALB)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-13" description="">
|
||||
|
||||
**Improvements:**
|
||||
- **Memory Management:** Enhanced Memory Management with Cosine Similarity Fallback
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-11" description="">
|
||||
|
||||
**New Features:**
|
||||
- **OMM:** Added OMM Script and UI functionality
|
||||
|
||||
**Improvements:**
|
||||
- **API:** Added filters validation to semantic_search_v2 endpoint
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-09" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Intercom:** Set Intercom events for ADD and SEARCH operations
|
||||
- **OpenMemory:** Added Posthog integration and feedback functionality
|
||||
- **MCP:** New JavaScript MCP Server with feedback support
|
||||
|
||||
**Improvements:**
|
||||
- **Structured Data:** Enhanced structured data handling in memory management
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-06" description="">
|
||||
|
||||
**New Features:**
|
||||
- **OAuth:** Added Mem0 OAuth integration
|
||||
- **OMM:** Added OMM-Mem0 sync for deleted memories
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-05" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Filters:** Implemented Wildcard Filters and refactored filter logic in V2 Views
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-02" description="">
|
||||
|
||||
**New Features:**
|
||||
- **OpenMemory Cloud:** Added OpenMemory Cloud support
|
||||
- **Structured Data:** Added 'structured_attributes' field to Memory model
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-05-30" description="">
|
||||
|
||||
**New Features:**
|
||||
- **Projects:** Added version and enable_graph to project views
|
||||
- **OpenMemory:** Added Postgres support for OpenMemory
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-05-19" description="">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Core:** Fixed unicode error in user_id, agent_id, run_id and app_id
|
||||
- **Install:** `pip install mem0-cli` (Python) or `npm install -g @mem0/cli` (Node.js)
|
||||
- **Full command suite:** `add`, `search`, `list`, `get`, `update`, `delete`, `import`, `config`, `init`, `status`, `entity`
|
||||
- **Interactive setup:** `mem0 init` with API key entry and user ID configuration
|
||||
- **Works everywhere:** Platform (Mem0 Cloud) and self-hosted OSS modes
|
||||
- **Scriptable:** `-o json` flag for CI/CD pipelines and automation
|
||||
- **Dual SDK:** Same commands, same experience across Python and Node.js
|
||||
- **Shared spec:** Both implementations driven by a single `cli-spec.json` ensuring identical behavior ([#4575](https://github.com/mem0ai/mem0/pull/4575))
|
||||
|
||||
</Update>
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Vercel AI SDK">
|
||||
<Tab title="Plugins">
|
||||
|
||||
<Update label="2025-12-26" description="v2.0.5">
|
||||
<Update label="2026-04-02" description="mem0-plugin v1.0.0">
|
||||
|
||||
**Mem0 Plugin for Claude Code, Cursor, and Codex**
|
||||
|
||||
The unified Mem0 plugin for AI development environments:
|
||||
|
||||
- **9 MCP memory tools:** `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities` — all 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
|
||||
- **Supported editors:** Claude Code, Claude Cowork, Cursor, Codex
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-12-26" description="Vercel AI SDK v2.0.5">
|
||||
**Bug Fix:**
|
||||
- **Vercel AI SDK:** Removed unnecessary dependencies to make the package lighter.
|
||||
- Removed unnecessary dependencies to make the package lighter.
|
||||
</Update>
|
||||
|
||||
<Update label="2025-09-25" description="v2.0.4">
|
||||
<Update label="2025-09-25" description="Vercel AI SDK v2.0.3 – v2.0.4">
|
||||
**New Features:**
|
||||
- Added file support for multimodal capabilities with memory context (v2.0.3)
|
||||
|
||||
**Bug Fix:**
|
||||
- **Vercel AI SDK:** Fixed version parameter in the AI SDK to use V2 for addition.
|
||||
- Fixed version parameter to use V2 for addition (v2.0.4)
|
||||
</Update>
|
||||
|
||||
<Update label="2025-09-25" description="v2.0.3">
|
||||
**New Features:**
|
||||
- **Vercel AI SDK:** Added file support for multimodal capabilities with memory context
|
||||
</Update>
|
||||
|
||||
<Update label="2025-09-03" description="v2.0.2">
|
||||
<Update label="2025-09-03" description="Vercel AI SDK v2.0.2">
|
||||
**Bug Fix:**
|
||||
- **Vercel AI SDK:** Fixed streaming response in the AI SDK.
|
||||
- Fixed streaming response in the AI SDK.
|
||||
</Update>
|
||||
|
||||
<Update label="2025-08-05" description="v2.0.1">
|
||||
<Update label="2025-08-05" description="Vercel AI SDK v2.0.0 – v2.0.1">
|
||||
**New Features:**
|
||||
- **Vercel AI SDK:** Added a new param `host` to the config.
|
||||
- Migration to AI SDK V5 (v2.0.0)
|
||||
- Added `host` param to the config (v2.0.1)
|
||||
</Update>
|
||||
|
||||
<Update label="2025-08-05" description="v2.0.0">
|
||||
<Update label="2025-06-15" description="Vercel AI SDK v1.0.6">
|
||||
**New Features:**
|
||||
- **Vercel AI SDK:** Migration to AI SDK V5.
|
||||
- Added `filter_memories` param.
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-15" description="v1.0.6">
|
||||
<Update label="2025-05-23" description="Vercel AI SDK v1.0.5">
|
||||
**New Features:**
|
||||
- **Vercel AI SDK:** Added param `filter_memories`.
|
||||
- Added support for Google provider.
|
||||
</Update>
|
||||
|
||||
<Update label="2025-05-23" description="v1.0.5">
|
||||
<Update label="2025-05-10" description="Vercel AI SDK v1.0.3 – v1.0.4">
|
||||
**New Features:**
|
||||
- **Vercel AI SDK:** Added support for Google provider.
|
||||
</Update>
|
||||
- Added support for `output_format` param (v1.0.4)
|
||||
|
||||
<Update label="2025-05-10" description="v1.0.4">
|
||||
**New Features:**
|
||||
- **Vercel AI SDK:** Added support for new param `output_format`.
|
||||
</Update>
|
||||
|
||||
<Update label="2025-05-08" description="v1.0.3">
|
||||
**Improvements:**
|
||||
- **Vercel AI SDK:** Added support for graceful failure in cases services are down.
|
||||
- Added graceful failure handling when services are down (v1.0.3)
|
||||
</Update>
|
||||
|
||||
<Update label="2025-05-01" description="v1.0.1">
|
||||
<Update label="2025-05-01" description="Vercel AI SDK v1.0.1">
|
||||
**New Features:**
|
||||
- **Vercel AI SDK:** Added support for graph memories
|
||||
- Added support for graph memories.
|
||||
</Update>
|
||||
|
||||
</Tab>
|
||||
|
||||
</Tabs>
|
||||
|
||||
@@ -45,7 +45,7 @@ Before you begin, follow these steps to set up the demo application:
|
||||
OPENAI_API_KEY=your_openai_api_key
|
||||
MEM0_API_KEY=your_mem0_api_key
|
||||
```
|
||||
You can obtain your `MEM0_API_KEY` by signing up at [Mem0 API Dashboard](https://app.mem0.ai/dashboard/api-keys).
|
||||
You can obtain your `MEM0_API_KEY` by signing up at <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 API Dashboard</a>.
|
||||
|
||||
5. Start the development server:
|
||||
```bash
|
||||
|
||||
@@ -38,7 +38,7 @@ client = MemoryClient(api_key="your-api-key")
|
||||
```
|
||||
|
||||
<Note>
|
||||
Replace `your-api-key` with your actual Mem0 API key from the [dashboard](https://app.mem0.ai). Without proper API authentication, memory operations will fail.
|
||||
Replace `your-api-key` with your actual Mem0 API key from the <a href="https://app.mem0.ai" rel="nofollow">dashboard</a>. Without proper API authentication, memory operations will fail.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
@@ -17,7 +17,7 @@ from mem0 import MemoryClient
|
||||
client = MemoryClient(api_key="m0-...")
|
||||
```
|
||||
|
||||
Grab an API key from the <Link href="https://app.mem0.ai/">Mem0 dashboard</Link> to get started.
|
||||
Grab an API key from the <a href="https://app.mem0.ai/" rel="nofollow">Mem0 dashboard</a> to get started.
|
||||
|
||||
## Store and Retrieve Scoped Memories
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ client = MemoryClient(api_key="your-api-key")
|
||||
```
|
||||
|
||||
<Note>
|
||||
Your API key needs export permissions to download memory data. Check your project settings on the [dashboard](https://app.mem0.ai) if export operations fail with authentication errors.
|
||||
Your API key needs export permissions to download memory data. Check your project settings on the <a href="https://app.mem0.ai" rel="nofollow">dashboard</a> if export operations fail with authentication errors.
|
||||
</Note>
|
||||
|
||||
Let's add some sample memories to work with:
|
||||
|
||||
@@ -55,7 +55,7 @@ GEMINI_API_KEY=your-gemini-api-key-here
|
||||
```
|
||||
|
||||
<Note>
|
||||
Ensure you have your Mem0 API key from the [Mem0 Dashboard](https://app.mem0.ai) and your Gemini API key from the [Google AI Studio](https://ai.studio/app/api-keys).
|
||||
Ensure you have your Mem0 API key from the <a href="https://app.mem0.ai" rel="nofollow">Mem0 Dashboard</a> and your Gemini API key from the [Google AI Studio](https://ai.studio/app/api-keys).
|
||||
</Note>
|
||||
|
||||
## Gemini Memory Agent
|
||||
|
||||
@@ -41,7 +41,7 @@ Set up your environment variables:
|
||||
- `MEM0_API_KEY`: Your Mem0 Platform API key
|
||||
- `OPENAI_API_KEY`: Your OpenAI API key
|
||||
|
||||
You can obtain your Mem0 Platform API key from the [Mem0 Platform](https://app.mem0.ai).
|
||||
You can obtain your Mem0 Platform API key from the <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.
|
||||
|
||||
## Complete Implementation
|
||||
|
||||
@@ -357,7 +357,7 @@ Based on our previous session, I remember we covered Vision Language Models and
|
||||
## Help & Resources
|
||||
|
||||
- [LlamaIndex Agent Workflows](https://docs.llamaindex.ai/en/stable/use_cases/agents/)
|
||||
- [Mem0 Platform](https://app.mem0.ai/)
|
||||
- <a href="https://app.mem0.ai/" rel="nofollow">Mem0 Platform</a>
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ os.environ["OPENAI_API_KEY"] = "<your-openai-api-key>"
|
||||
llm = OpenAI(model="gpt-4.1-nano-2025-04-14")
|
||||
```
|
||||
|
||||
Initialize the Mem0 client. You can find your API key [here](https://app.mem0.ai/dashboard/api-keys). Read about Mem0 [Open Source](https://docs.mem0.ai/open-source/overview).
|
||||
Initialize the Mem0 client. You can find your API key <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">here</a>. Read about Mem0 [Open Source](https://docs.mem0.ai/open-source/overview).
|
||||
```python
|
||||
os.environ["MEM0_API_KEY"] = "<your-mem0-api-key>"
|
||||
|
||||
|
||||
@@ -754,7 +754,7 @@ Exact output varies as Mem0 automatically extracts and deduplicates entities. Th
|
||||
- [MiroFish GitHub](https://github.com/666ghj/MiroFish) — Source code and setup guide
|
||||
- [MiroFish Documentation](https://deepwiki.com/666ghj/MiroFish) — Full framework docs
|
||||
- [Mem0 Graph Memory](/open-source/features/graph-memory) — Graph Memory documentation
|
||||
- [Mem0 Documentation](https://docs.mem0.ai/) — Full API reference
|
||||
- [Mem0 Documentation](https://docs.mem0.ai/introduction) — Full API reference
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Graph Memory" icon="network-wired" href="/open-source/features/graph-memory">
|
||||
|
||||
@@ -222,8 +222,8 @@ context = Mem0Context(user_id="user123")
|
||||
|
||||
## Resources
|
||||
|
||||
- [Mem0 Documentation](https://docs.mem0.ai)
|
||||
- [Mem0 Dashboard](https://app.mem0.ai/dashboard)
|
||||
- [Mem0 Documentation](https://docs.mem0.ai/introduction)
|
||||
- <a href="https://app.mem0.ai/dashboard" rel="nofollow">Mem0 Dashboard</a>
|
||||
- [API Reference](https://docs.mem0.ai/api-reference)
|
||||
|
||||
---
|
||||
|
||||
@@ -23,7 +23,7 @@ MEM0_API_KEY=your_mem0_api_key
|
||||
OPENAI_API_KEY=your_openai_api_key
|
||||
```
|
||||
|
||||
Get your Mem0 API key from the [Mem0 Dashboard](https://app.mem0.ai/dashboard/api-keys).
|
||||
Get your Mem0 API key from the <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 Dashboard</a>.
|
||||
|
||||
### Configuration
|
||||
|
||||
@@ -308,8 +308,8 @@ run().catch(console.error);
|
||||
|
||||
## Resources
|
||||
|
||||
- [Mem0 Documentation](https://docs.mem0.ai)
|
||||
- [Mem0 Dashboard](https://app.mem0.ai/dashboard)
|
||||
- [Mem0 Documentation](https://docs.mem0.ai/introduction)
|
||||
- <a href="https://app.mem0.ai/dashboard" rel="nofollow">Mem0 Dashboard</a>
|
||||
- [API Reference](https://docs.mem0.ai/api-reference)
|
||||
- [OpenAI Documentation](https://platform.openai.com/docs)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Adding memory is how Mem0 captures useful details from a conversation so your ag
|
||||
- **Messages** – The ordered list of user/assistant turns you send to `add`.
|
||||
- **Infer** – Controls whether Mem0 extracts structured memories (`infer=True`, default) or stores raw messages.
|
||||
- **Metadata** – Optional filters (e.g., `{"category": "movie_recommendations"}`) that improve retrieval later.
|
||||
- **User / Session identifiers** – `user_id`, `session_id`, or `run_id` that scope the memory for future searches.
|
||||
- **User / Session identifiers** – `user_id`, `agent_id`, or `run_id` that scope the memory for future searches.
|
||||
|
||||
## How does it work?
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ Mem0 maps these classic categories onto its layered storage so you can decide wh
|
||||
Mem0 stores each layer separately and merges them when you query:
|
||||
|
||||
1. **Capture** – Messages enter the conversation layer while the turn is active.
|
||||
2. **Promote** – Relevant details persist to session or user memory based on your `user_id`, `session_id`, and metadata.
|
||||
2. **Promote** – Relevant details persist to session or user memory based on your `user_id`, `run_id`, and metadata.
|
||||
3. **Retrieve** – The search pipeline pulls from all layers, ranking user memories first, then session notes, then raw history.
|
||||
|
||||
```python
|
||||
@@ -66,19 +66,19 @@ memory = Memory(api_key=os.environ["MEM0_API_KEY"])
|
||||
memory.add(
|
||||
["I'm Alex and I prefer boutique hotels."],
|
||||
user_id="alex",
|
||||
session_id="trip-planning-2025",
|
||||
run_id="trip-planning-2025",
|
||||
)
|
||||
|
||||
# Later in the session, pull long-term + session context
|
||||
results = memory.search(
|
||||
"Any hotel preferences?",
|
||||
user_id="alex",
|
||||
session_id="trip-planning-2025",
|
||||
run_id="trip-planning-2025",
|
||||
)
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Use `session_id` when you want short-term context to expire automatically; rely on `user_id` for lasting personalization.
|
||||
Use `run_id` when you want short-term context to expire automatically; rely on `user_id` for lasting personalization.
|
||||
</Tip>
|
||||
|
||||
## When should you use each layer?
|
||||
|
||||
+11
-3
@@ -12,7 +12,7 @@
|
||||
"logo": {
|
||||
"light": "/logo/light.svg",
|
||||
"dark": "/logo/dark.svg",
|
||||
"href": "https://app.mem0.ai/"
|
||||
"href": "https://mem0.ai"
|
||||
},
|
||||
"navigation": {
|
||||
"anchors": [
|
||||
@@ -138,7 +138,10 @@
|
||||
"group": "Release Notes",
|
||||
"icon": "rocket",
|
||||
"pages": [
|
||||
"changelog"
|
||||
"changelog/highlights",
|
||||
"changelog/sdk",
|
||||
"changelog/platform",
|
||||
"changelog/openclaw"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -416,7 +419,8 @@
|
||||
"integrations/openai-agents-sdk",
|
||||
"integrations/google-ai-adk",
|
||||
"integrations/mastra",
|
||||
"integrations/vercel-ai-sdk"
|
||||
"integrations/vercel-ai-sdk",
|
||||
"integrations/chatdev"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -608,6 +612,10 @@
|
||||
]
|
||||
},
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/changelog",
|
||||
"destination": "/changelog/highlights"
|
||||
},
|
||||
{
|
||||
"source": "/api-reference/memory/v2-search-memories",
|
||||
"destination": "/api-reference/memory/search-memories"
|
||||
|
||||
@@ -409,4 +409,11 @@ Here are the available integrations for Mem0:
|
||||
>
|
||||
Use Mem0 with AWS Bedrock and OpenSearch Service for cloud-native persistent semantic memory storage.
|
||||
</Card>
|
||||
<Card
|
||||
title="ChatDev"
|
||||
icon="comments"
|
||||
href="/integrations/chatdev"
|
||||
>
|
||||
Add persistent cloud-managed memory to ChatDev multi-agent workflows with zero-code YAML configuration.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -24,7 +24,7 @@ pip install mem0ai agentops python-dotenv
|
||||
2. Valid API keys:
|
||||
- [AgentOps API Key](https://app.agentops.ai/dashboard/api-keys)
|
||||
- OpenAI API Key (for LLM operations)
|
||||
- [Mem0 API Key](https://app.mem0.ai/dashboard/api-keys) (optional, for cloud operations)
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 API Key</a> (optional, for cloud operations)
|
||||
|
||||
## Basic Integration Example
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ pip install agno mem0ai python-dotenv
|
||||
```
|
||||
|
||||
2. Valid API keys:
|
||||
- [Mem0 API Key](https://app.mem0.ai/dashboard/api-keys)
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 API Key</a>
|
||||
- OpenAI API Key (for the agent model)
|
||||
|
||||
## Quick Integration (Using `Mem0Tools`)
|
||||
|
||||
@@ -19,7 +19,7 @@ pip install autogen mem0ai openai python-dotenv
|
||||
|
||||
First, we'll import the necessary libraries and set up our configurations.
|
||||
|
||||
<Note>Remember to get the Mem0 API key from [Mem0 Platform](https://app.mem0.ai).</Note>
|
||||
<Note>Remember to get the Mem0 API key from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
---
|
||||
title: ChatDev
|
||||
description: "Add persistent, cloud-managed memory to ChatDev multi-agent workflows with Mem0 — no code required, just YAML configuration."
|
||||
---
|
||||
|
||||
Build multi-agent workflows in [ChatDev](https://github.com/OpenBMB/ChatDev) with persistent memory powered by Mem0. ChatDev is a zero-code multi-agent platform where agents, tools, and workflows are defined entirely in YAML. Mem0 integrates as a built-in memory store (`type: mem0`), giving your agents cloud-managed semantic search and cross-session persistence — all without writing any code.
|
||||
|
||||
## Overview
|
||||
|
||||
In this guide, you'll:
|
||||
1. Set up ChatDev with the Mem0 memory store
|
||||
2. Configure agents with persistent memory using YAML
|
||||
3. Enable automatic memory retrieval and storage across conversations
|
||||
4. Leverage cross-session persistence for personalized multi-agent interactions
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Python 3.12+**
|
||||
- **[uv](https://docs.astral.sh/uv/)** — Python package manager
|
||||
- **Node.js 18+** and **npm** — only needed if using the web console
|
||||
- A **Mem0 API key** from <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a>
|
||||
- An **OpenAI API key** (or another LLM provider supported by ChatDev)
|
||||
|
||||
## Setup and Configuration
|
||||
|
||||
Install ChatDev and its dependencies (includes `mem0ai`):
|
||||
|
||||
```bash
|
||||
git clone https://github.com/OpenBMB/ChatDev.git
|
||||
cd ChatDev
|
||||
uv sync
|
||||
```
|
||||
|
||||
If you plan to use the web console, also install the frontend:
|
||||
|
||||
```bash
|
||||
cd frontend && npm install && cd ..
|
||||
```
|
||||
|
||||
Set up your environment variables in a `.env` file:
|
||||
|
||||
<Note>Get your Mem0 API key from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
|
||||
```bash
|
||||
MEM0_API_KEY=your-mem0-api-key
|
||||
API_KEY=your-openai-api-key
|
||||
BASE_URL=https://api.openai.com/v1
|
||||
```
|
||||
|
||||
## Configure Mem0 Memory Store
|
||||
|
||||
In your ChatDev workflow YAML, add a Mem0 memory store in the `memory` section:
|
||||
|
||||
```yaml
|
||||
memory:
|
||||
- name: mem0_store
|
||||
type: mem0
|
||||
config:
|
||||
api_key: ${MEM0_API_KEY}
|
||||
user_id: my-user-123 # optional: scope memories to a user
|
||||
agent_id: my-agent # optional: scope memories to an agent
|
||||
```
|
||||
|
||||
Mem0 handles all storage, embeddings, and search server-side — no local vector databases or embedding models are needed.
|
||||
|
||||
## Attach Memory to an Agent
|
||||
|
||||
Reference the memory store in your agent node's `memories` list:
|
||||
|
||||
```yaml
|
||||
nodes:
|
||||
- id: writer
|
||||
type: agent
|
||||
config:
|
||||
role: |
|
||||
You are a knowledgeable writer. Use your memories to build
|
||||
on past interactions.
|
||||
memories:
|
||||
- name: mem0_store
|
||||
top_k: 5
|
||||
similarity_threshold: 0.5 # minimum relevance score (0.0–1.0); set to -1.0 to disable
|
||||
retrieve_stage:
|
||||
- gen
|
||||
read: true
|
||||
write: true
|
||||
```
|
||||
|
||||
- **`read: true`** — Agent retrieves relevant memories before generating a response
|
||||
- **`write: true`** — Agent stores new memories from user input after each interaction
|
||||
- **`top_k`** — Number of memories to retrieve per query
|
||||
- **`similarity_threshold`** — Minimum relevance score for retrieved memories. Set to `-1.0` to return all results regardless of score
|
||||
- **`retrieve_stage`** — When to retrieve memories. Options: `pre_gen_thinking` (before generation), `gen` (during generation), `post_gen_thinking` (after generation), `finished` (after completion)
|
||||
|
||||
## Full Example Workflow
|
||||
|
||||
Here's a complete workflow YAML that creates a memory-backed conversational agent:
|
||||
|
||||
```yaml
|
||||
version: 0.4.0
|
||||
graph:
|
||||
description: Memory-backed conversation using Mem0
|
||||
|
||||
nodes:
|
||||
- id: writer
|
||||
type: agent
|
||||
config:
|
||||
base_url: ${BASE_URL}
|
||||
api_key: ${API_KEY}
|
||||
provider: openai
|
||||
name: gpt-5.4
|
||||
role: |
|
||||
You are a knowledgeable writer. Use your memories to build
|
||||
on past interactions. If memory sections are provided
|
||||
(wrapped by ===== Related Memories =====), incorporate
|
||||
relevant context from those memories into your response.
|
||||
params:
|
||||
temperature: 0.7
|
||||
max_tokens: 2000
|
||||
memories:
|
||||
- name: mem0_store
|
||||
top_k: 5
|
||||
retrieve_stage:
|
||||
- gen
|
||||
read: true
|
||||
write: true
|
||||
|
||||
memory:
|
||||
- name: mem0_store
|
||||
type: mem0
|
||||
config:
|
||||
api_key: ${MEM0_API_KEY}
|
||||
user_id: project-user-123
|
||||
agent_id: writer-agent
|
||||
|
||||
start:
|
||||
- writer
|
||||
end: []
|
||||
```
|
||||
|
||||
Run the workflow:
|
||||
|
||||
```bash
|
||||
# Option 1: CLI (recommended for quick testing)
|
||||
uv run python run.py --path yaml_instance/demo_mem0_memory.yaml --name my_project
|
||||
|
||||
# Option 2: Web console
|
||||
make dev
|
||||
# Backend starts at http://localhost:6400, frontend at http://localhost:5173
|
||||
```
|
||||
|
||||
To use the web console, open `http://localhost:5173`, create a new workflow, and paste your YAML configuration into the editor. The web console provides a visual chat interface for interacting with your memory-backed agents.
|
||||
|
||||
## How It Works
|
||||
|
||||
When an agent with Mem0 memory receives input, the following cycle runs automatically:
|
||||
|
||||
**1. Retrieve** — Before generating a response, ChatDev queries Mem0 with the user's input using semantic search. Relevant memories are injected into the agent's context in this format:
|
||||
|
||||
```
|
||||
===== Related Memories =====
|
||||
--- mem0_store ---
|
||||
1. User's favorite language is Rust
|
||||
2. User lives in San Francisco
|
||||
===== End of Memory =====
|
||||
```
|
||||
|
||||
This is why the role prompt in the example references `===== Related Memories =====` — the agent needs to know how to use this injected context.
|
||||
|
||||
**2. Generate** — The agent produces a response using the retrieved memories as additional context.
|
||||
|
||||
**3. Store** — After generation, the user's input is sent to Mem0 via `client.add()`. Mem0's extraction model automatically identifies and stores facts, preferences, and key information. Only user input is stored — agent output is excluded to keep memories clean.
|
||||
|
||||
Memories persist in Mem0's cloud across all sessions. The next time the same `user_id` or `agent_id` is used, previous memories are automatically retrieved.
|
||||
|
||||
## Dual-Scope Memory (User + Agent)
|
||||
|
||||
When both `user_id` and `agent_id` are configured, Mem0 uses an OR filter to search across both scopes in a single query:
|
||||
|
||||
```yaml
|
||||
memory:
|
||||
- name: shared_store
|
||||
type: mem0
|
||||
config:
|
||||
api_key: ${MEM0_API_KEY}
|
||||
user_id: alice # stores user preferences ("Alice prefers dark mode")
|
||||
agent_id: support-bot # stores agent-learned context ("Resolved Alice's billing issue")
|
||||
```
|
||||
|
||||
This means retrieval returns memories from **both** the user's scope and the agent's scope. Writes include both IDs, so each memory is accessible from either dimension. Use this when you want an agent to remember both what the user told it *and* what the agent learned across sessions.
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
### Memory Store Config
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `api_key` | Yes | Mem0 API key from <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a> |
|
||||
| `user_id` | No | Scope memories to a specific user |
|
||||
| `agent_id` | No | Scope memories to a specific agent |
|
||||
|
||||
### Memory Attachment Config
|
||||
|
||||
| Field | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `top_k` | `3` | Number of memories to retrieve |
|
||||
| `similarity_threshold` | `-1.0` (disabled) | Minimum relevance score. Set a value between `0.0` and `1.0` to filter low-relevance results. Default (`-1.0`) returns all matches without filtering |
|
||||
| `retrieve_stage` | `["gen"]` | When to retrieve: `pre_gen_thinking`, `gen`, `post_gen_thinking`, or `finished` |
|
||||
| `read` | `true` | Whether the agent retrieves memories |
|
||||
| `write` | `true` | Whether the agent stores new memories |
|
||||
|
||||
## Tips and Common Pitfalls
|
||||
|
||||
<Info>
|
||||
**Indexing delay** — Freshly stored memories may take a few seconds to become searchable. If a memory isn't retrieved immediately after being stored, wait a moment and try again.
|
||||
</Info>
|
||||
|
||||
- **No memories returned on first run** — This is expected. Memories are stored *after* the agent responds, so the first interaction has no prior context. Memories appear starting from the second interaction onward.
|
||||
- **`mem0ai` not installed** — If you see `ImportError: mem0ai is required for Mem0Memory`, run `uv add mem0ai` or `pip install mem0ai` to add the dependency.
|
||||
- **Invalid API key** — A wrong or expired `MEM0_API_KEY` will log errors like `Mem0 search failed` or `Mem0 add failed` but won't crash the agent. Check your key at <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a>.
|
||||
- **Pipeline headers in memories** — ChatDev automatically strips internal pipeline headers (e.g., `=== INPUT FROM TASK (user) ===`) before sending text to Mem0, so your memories stay clean.
|
||||
- **Clearing test memories** — To delete memories created during testing, use the Mem0 dashboard at <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a> or the Python SDK: `MemoryClient().delete_all(user_id="your-test-user")`.
|
||||
|
||||
## Key Features
|
||||
|
||||
1. **Zero-Code Integration** — Configure Mem0 entirely through YAML, no Python code required
|
||||
2. **Cloud-Managed Storage** — Mem0 handles embeddings, persistence, and search server-side
|
||||
3. **Semantic Search** — Retrieve contextually relevant memories, not just keyword matches
|
||||
4. **Cross-Session Persistence** — Memories survive across runs, sessions, and restarts
|
||||
5. **Multi-Agent Memory Sharing** — Multiple agents can share memories through common `user_id` or `agent_id` scopes
|
||||
6. **Intelligent Input Processing** — Only user input is stored; agent output is excluded to prevent noisy memories
|
||||
|
||||
## Conclusion
|
||||
|
||||
By adding Mem0 as a memory store in ChatDev, your multi-agent workflows gain persistent, intelligent memory with zero code changes. Agents automatically remember past interactions and use that context to provide personalized, coherent responses across sessions.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="CrewAI Integration" icon="users" href="/integrations/crewai">
|
||||
Build multi-agent systems with CrewAI and Mem0
|
||||
</Card>
|
||||
<Card title="AutoGen Integration" icon="robot" href="/integrations/autogen">
|
||||
Build conversational agents with AutoGen and Mem0
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -17,8 +17,8 @@ Add persistent memory to [**Claude Code**](https://docs.anthropic.com/en/docs/cl
|
||||
Before setting up Mem0 with Claude Code, ensure you have:
|
||||
|
||||
1. A Mem0 Platform account and API key:
|
||||
- [Sign up at app.mem0.ai](https://app.mem0.ai)
|
||||
- [Get your API key](https://app.mem0.ai/dashboard/api-keys) (starts with `m0-`)
|
||||
- <a href="https://app.mem0.ai" rel="nofollow">Sign up at app.mem0.ai</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Get your API key</a> (starts with `m0-`)
|
||||
|
||||
2. Claude Code CLI or Claude Cowork desktop app installed
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ Add persistent memory to [**OpenAI Codex**](https://openai.com/index/codex/) wit
|
||||
Before setting up Mem0 with Codex, ensure you have:
|
||||
|
||||
1. A Mem0 Platform account and API key:
|
||||
- [Sign up at app.mem0.ai](https://app.mem0.ai)
|
||||
- [Get your API key](https://app.mem0.ai/dashboard/api-keys) (starts with `m0-`)
|
||||
- <a href="https://app.mem0.ai" rel="nofollow">Sign up at app.mem0.ai</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Get your API key</a> (starts with `m0-`)
|
||||
|
||||
2. OpenAI Codex access
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ pip install crewai crewai-tools mem0ai
|
||||
|
||||
Import required modules and set up configurations:
|
||||
|
||||
<Note>Remember to get your API keys from [Mem0 Platform](https://app.mem0.ai), [OpenAI](https://platform.openai.com) and [Serper Dev](https://serper.dev) for search capabilities.</Note>
|
||||
<Note>Remember to get your API keys from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>, [OpenAI](https://platform.openai.com) and [Serper Dev](https://serper.dev) for search capabilities.</Note>
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
@@ -17,8 +17,8 @@ Add persistent memory to [**Cursor**](https://cursor.com) with the Mem0 plugin.
|
||||
Before setting up Mem0 with Cursor, ensure you have:
|
||||
|
||||
1. A Mem0 Platform account and API key:
|
||||
- [Sign up at app.mem0.ai](https://app.mem0.ai)
|
||||
- [Get your API key](https://app.mem0.ai/dashboard/api-keys) (starts with `m0-`)
|
||||
- <a href="https://app.mem0.ai" rel="nofollow">Sign up at app.mem0.ai</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Get your API key</a> (starts with `m0-`)
|
||||
|
||||
2. Cursor installed ([cursor.com](https://cursor.com))
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ npx flowise start
|
||||
|
||||
### 2. Obtain Your Mem0 API Key
|
||||
|
||||
1. Navigate to the [Mem0 API Key dashboard](https://app.mem0.ai/dashboard/api-keys).
|
||||
1. Navigate to the <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 API Key dashboard</a>.
|
||||
2. Generate or copy your existing Mem0 API Key.
|
||||
|
||||

|
||||
@@ -70,7 +70,7 @@ Test your memory configuration:
|
||||
|
||||
1. Save your Flowise configuration
|
||||
2. Run a test chat and store some information
|
||||
3. Verify the stored memories in the [Mem0 Dashboard](https://app.mem0.ai/dashboard/requests)
|
||||
3. Verify the stored memories in the <a href="https://app.mem0.ai/dashboard/requests" rel="nofollow">Mem0 Dashboard</a>
|
||||
|
||||

|
||||
|
||||
@@ -103,7 +103,7 @@ Available settings include:
|
||||
|
||||
### Platform Configuration
|
||||
|
||||
Additional settings available in [Mem0 Project Settings](https://app.mem0.ai/dashboard/project-settings):
|
||||
Additional settings available in <a href="https://app.mem0.ai/dashboard/project-settings" rel="nofollow">Mem0 Project Settings</a>:
|
||||
|
||||
1. **Custom Instructions**: Define memory extraction rules
|
||||
2. **Expiration Date**: Set automatic memory cleanup periods
|
||||
|
||||
@@ -22,7 +22,7 @@ pip install google-adk mem0ai python-dotenv
|
||||
```
|
||||
|
||||
2. Valid API keys:
|
||||
- [Mem0 API Key](https://app.mem0.ai/dashboard/api-keys)
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 API Key</a>
|
||||
- Google AI Studio API Key
|
||||
|
||||
## Basic Integration Example
|
||||
|
||||
@@ -52,7 +52,7 @@ hermes memory setup
|
||||
|
||||
Select **mem0** as the provider and enter your Mem0 API key when prompted. The wizard writes your config to `~/.hermes/mem0.json`.
|
||||
|
||||
<Note>Get your API key from [app.mem0.ai](https://app.mem0.ai).</Note>
|
||||
<Note>Get your API key from <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a>.</Note>
|
||||
|
||||
### Option 2: Manual Configuration
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Combining Mem0 with Keywords AI allows you to:
|
||||
4. Optimize token usage and reduce costs
|
||||
|
||||
<Note>
|
||||
You can get your Mem0 API key, user_id, and org_id from the [Mem0 dashboard](https://app.mem0.ai/). These are required for proper integration.
|
||||
You can get your Mem0 API key, user_id, and org_id from the <a href="https://app.mem0.ai/" rel="nofollow">Mem0 dashboard</a>. These are required for proper integration.
|
||||
</Note>
|
||||
|
||||
## Setup and Configuration
|
||||
|
||||
@@ -22,7 +22,7 @@ pip install langchain langchain_openai mem0ai python-dotenv
|
||||
|
||||
Import required modules and set up configurations:
|
||||
|
||||
<Note>Remember to get the Mem0 API key from [Mem0 Platform](https://app.mem0.ai).</Note>
|
||||
<Note>Remember to get the Mem0 API key from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
@@ -23,7 +23,7 @@ pip install langgraph langchain-openai mem0ai python-dotenv
|
||||
|
||||
Import required modules and set up configurations:
|
||||
|
||||
<Note>Remember to get the Mem0 API key from [Mem0 Platform](https://app.mem0.ai).</Note>
|
||||
<Note>Remember to get the Mem0 API key from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
|
||||
```python
|
||||
from typing import Annotated, TypedDict, List
|
||||
|
||||
@@ -22,7 +22,7 @@ pip install llama-index-core llama-index-memory-mem0 python-dotenv
|
||||
Set your Mem0 Platform API key as an environment variable. You can replace `<your-mem0-api-key>` with your actual API key:
|
||||
|
||||
<Note type="info">
|
||||
You can obtain your Mem0 Platform API key from the [Mem0 Platform](https://app.mem0.ai/login).
|
||||
You can obtain your Mem0 Platform API key from the <a href="https://app.mem0.ai/login" rel="nofollow">Mem0 Platform</a>.
|
||||
</Note>
|
||||
|
||||
```python
|
||||
|
||||
@@ -23,7 +23,7 @@ npm install @mastra/core @mastra/mem0 @ai-sdk/openai zod
|
||||
|
||||
Set up your environment variables:
|
||||
|
||||
<Note>Remember to get the Mem0 API key from [Mem0 Platform](https://app.mem0.ai).</Note>
|
||||
<Note>Remember to get the Mem0 API key from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
|
||||
```bash
|
||||
MEM0_API_KEY=your-mem0-api-key
|
||||
|
||||
@@ -22,7 +22,7 @@ pip install openai-agents mem0ai
|
||||
```
|
||||
|
||||
2. Valid API keys:
|
||||
- [Mem0 API Key](https://app.mem0.ai/dashboard/api-keys)
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 API Key</a>
|
||||
- [OpenAI API Key](https://platform.openai.com/api-keys)
|
||||
|
||||
## Basic Integration Example
|
||||
|
||||
@@ -42,7 +42,7 @@ All memories are scoped to this `userId` — different values create separate me
|
||||
|
||||
### Platform Mode (Mem0 Cloud)
|
||||
|
||||
<Note>Get your API key from [app.mem0.ai](https://app.mem0.ai).</Note>
|
||||
<Note>Get your API key from <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a>.</Note>
|
||||
|
||||
Add to your `openclaw.json`:
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Mem0 is a self-improving memory layer for LLM applications, enabling personalize
|
||||
|
||||
**Get your API Key**: You'll need a Mem0 API key to use this extension:
|
||||
|
||||
a. Sign up at [app.mem0.ai](https://app.mem0.ai)
|
||||
a. Sign up at <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a>
|
||||
|
||||
b. Navigate to your API Keys page
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ npm install @mem0/vercel-ai-provider
|
||||
|
||||
### Setting Up Mem0
|
||||
|
||||
1. Get your **Mem0 API Key** from the [Mem0 Dashboard](https://app.mem0.ai/dashboard/api-keys).
|
||||
1. Get your **Mem0 API Key** from the <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 Dashboard</a>.
|
||||
|
||||
2. Initialize the Mem0 Client in your application:
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Move your Mem0 implementation to managed infrastructure with enterprise features
|
||||
|
||||
## Plan
|
||||
|
||||
1. **Sign up**: Create an account on [Mem0 Platform](https://app.mem0.ai).
|
||||
1. **Sign up**: Create an account on <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.
|
||||
2. **Get API Key**: Navigate to **Settings > API Keys** and generate a new key.
|
||||
3. **Review Usage**: Identify where you instantiate `Memory` and where you call `search` or `get_all`.
|
||||
|
||||
@@ -368,7 +368,7 @@ If you encounter issues, you can revert immediately by switching your import bac
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Platform Dashboard](https://app.mem0.ai) - Monitor usage and manage settings.
|
||||
- <a href="https://app.mem0.ai" rel="nofollow">Platform Dashboard</a> - Monitor usage and manage settings.
|
||||
- [Webhooks Setup](/platform/features/webhooks) - Configure real-time event notifications.
|
||||
- [Organizations & Projects](/api-reference/organizations-projects) - Set up multi-tenancy for your team.
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ estimatedTime: "~2 minutes"
|
||||
|
||||
<Info>
|
||||
**Prerequisites**
|
||||
- Mem0 Platform account ([Sign up here](https://app.mem0.ai))
|
||||
- API key ([Get one from dashboard](https://app.mem0.ai/settings/api-keys))
|
||||
- Mem0 Platform account (<a href="https://app.mem0.ai" rel="nofollow">Sign up here</a>)
|
||||
- API key (<a href="https://app.mem0.ai/settings/api-keys" rel="nofollow">Get one from dashboard</a>)
|
||||
- Node.js 14+ (for npx)
|
||||
- An MCP-compatible client (Claude, Claude Code, Cursor, Windsurf, VS Code, OpenCode)
|
||||
</Info>
|
||||
@@ -163,7 +163,7 @@ Agent: Updated your project status successfully.
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
If you get "Connection failed", ensure you have a valid API key from [Mem0 Dashboard](https://app.mem0.ai/settings/api-keys).
|
||||
If you get "Connection failed", ensure you have a valid API key from <a href="https://app.mem0.ai/settings/api-keys" rel="nofollow">Mem0 Dashboard</a>.
|
||||
</Info>
|
||||
|
||||
---
|
||||
@@ -171,7 +171,7 @@ Agent: Updated your project status successfully.
|
||||
## Quick Recovery
|
||||
|
||||
- **"Connection refused"** → Check your internet connection and ensure the MCP client is correctly configured
|
||||
- **"Invalid API key"** → Get a new key from [Mem0 Dashboard](https://app.mem0.ai/settings/api-keys)
|
||||
- **"Invalid API key"** → Get a new key from <a href="https://app.mem0.ai/settings/api-keys" rel="nofollow">Mem0 Dashboard</a>
|
||||
- **"npx command not found"** → Install Node.js from [nodejs.org](https://nodejs.org)
|
||||
|
||||
---
|
||||
|
||||
@@ -64,7 +64,7 @@ Mem0 is the memory engine that keeps conversations contextual so users never rep
|
||||
<Card title="Connect Integrations" icon="plug" href="/integrations">
|
||||
LangChain, CrewAI, Vercel AI SDK.
|
||||
</Card>
|
||||
<Card title="Monitor in the Dashboard" icon="presentation" href="https://app.mem0.ai">
|
||||
<Card title="Monitor in the Dashboard" icon="presentation" href="https://app.mem0.ai/login">
|
||||
Track activity and manage workspaces.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -150,7 +150,7 @@ Mem0 offers two powerful ways to add memory to your AI applications. Choose base
|
||||
<Card
|
||||
title="Try Platform Free"
|
||||
icon="rocket"
|
||||
href="https://app.mem0.ai"
|
||||
href="https://app.mem0.ai/login"
|
||||
>
|
||||
Sign up and test the Platform with our free tier. No credit card required.
|
||||
</Card>
|
||||
|
||||
@@ -9,8 +9,8 @@ Get started with Mem0 Platform's hosted API in under 5 minutes. This guide shows
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Mem0 Platform account ([Sign up here](https://app.mem0.ai))
|
||||
- API key ([Get one from dashboard](https://app.mem0.ai/dashboard/settings?tab=api-keys&subtab=configuration))
|
||||
- Mem0 Platform account (<a href="https://app.mem0.ai" rel="nofollow">Sign up here</a>)
|
||||
- API key (<a href="https://app.mem0.ai/dashboard/settings?tab=api-keys&subtab=configuration" rel="nofollow">Get one from dashboard</a>)
|
||||
- Python 3.10+, Node.js 14+, or cURL
|
||||
|
||||
## Installation
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ We follow the llms.txt standard:
|
||||
- [llms.txt](https://docs.mem0.ai/llms.txt)
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Get an API Key" icon="key" href="https://app.mem0.ai">
|
||||
<Card title="Get an API Key" icon="key" href="https://app.mem0.ai/login">
|
||||
Sign up for Mem0 Platform and start building
|
||||
</Card>
|
||||
<Card title="Quickstart" icon="rocket" href="/platform/quickstart">
|
||||
@@ -34,7 +34,7 @@ Works with Claude Code, Cursor, Windsurf, and any assistant that supports skills
|
||||
|
||||
Connect Claude, Claude Code, Cursor, Windsurf, VS Code, OpenCode, or any MCP-compatible client to Mem0.
|
||||
|
||||
Get your API key from [app.mem0.ai](https://app.mem0.ai), then add Mem0 MCP with a single command:
|
||||
Get your API key from <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a>, then add Mem0 MCP with a single command:
|
||||
|
||||
```bash
|
||||
npx mcp-add \
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { OpenAILLM } from "./openai";
|
||||
import { LLMConfig, Message } from "../types";
|
||||
import { LLMResponse } from "./base";
|
||||
|
||||
export class DeepSeekLLM extends OpenAILLM {
|
||||
constructor(config: LLMConfig) {
|
||||
const apiKey = config.apiKey || process.env.DEEPSEEK_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error("DeepSeek API key is required");
|
||||
}
|
||||
super({
|
||||
...config,
|
||||
apiKey,
|
||||
baseURL:
|
||||
config.baseURL ||
|
||||
process.env.DEEPSEEK_API_BASE ||
|
||||
"https://api.deepseek.com",
|
||||
model: config.model || "deepseek-chat",
|
||||
});
|
||||
}
|
||||
|
||||
async generateResponse(
|
||||
messages: Message[],
|
||||
responseFormat?: { type: string },
|
||||
tools?: any[],
|
||||
): Promise<string | LLMResponse> {
|
||||
try {
|
||||
return await super.generateResponse(messages, responseFormat, tools);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(`DeepSeek LLM failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async generateChat(messages: Message[]): Promise<LLMResponse> {
|
||||
try {
|
||||
return await super.generateChat(messages);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(`DeepSeek LLM failed: ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import { LangchainEmbedder } from "../embeddings/langchain";
|
||||
import { LangchainVectorStore } from "../vector_stores/langchain";
|
||||
import { AzureAISearch } from "../vector_stores/azure_ai_search";
|
||||
import { PGVector } from "../vector_stores/pgvector";
|
||||
import { DeepSeekLLM } from "../llms/deepseek";
|
||||
|
||||
export class EmbedderFactory {
|
||||
static create(provider: string, config: EmbeddingConfig): Embedder {
|
||||
@@ -82,6 +83,8 @@ export class LLMFactory {
|
||||
return new MistralLLM(config);
|
||||
case "langchain":
|
||||
return new LangchainLLM(config);
|
||||
case "deepseek":
|
||||
return new DeepSeekLLM(config);
|
||||
default:
|
||||
throw new Error(`Unsupported LLM provider: ${provider}`);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,25 @@ try {
|
||||
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
|
||||
const POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/";
|
||||
|
||||
// Default sampling rate for hot-path OSS events. Lifecycle events always fire at 100%.
|
||||
// Override via MEM0_TELEMETRY_SAMPLE_RATE env var. Mirrors mem0/memory/telemetry.py.
|
||||
const DEFAULT_SAMPLE_RATE = 0.1;
|
||||
const MEM0_TELEMETRY_SAMPLE_RATE: number = ((): number => {
|
||||
try {
|
||||
const raw = process?.env?.MEM0_TELEMETRY_SAMPLE_RATE;
|
||||
if (raw !== undefined) {
|
||||
const parsed = Number(raw);
|
||||
if (Number.isFinite(parsed) && parsed >= 0 && parsed <= 1) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
return DEFAULT_SAMPLE_RATE;
|
||||
})();
|
||||
|
||||
// Events that bypass sampling. Keep in sync with _captureEvent call sites in memory/index.ts.
|
||||
const LIFECYCLE_EVENTS: ReadonlySet<string> = new Set(["init", "reset"]);
|
||||
|
||||
class UnifiedTelemetry implements TelemetryClient {
|
||||
private apiKey: string;
|
||||
private host: string;
|
||||
@@ -78,6 +97,12 @@ async function captureClientEvent(
|
||||
return;
|
||||
}
|
||||
|
||||
// >= so that rate=0 drops everything and rate=1 keeps everything (Math.random() ∈ [0, 1)).
|
||||
const isLifecycle = LIFECYCLE_EVENTS.has(eventName);
|
||||
if (!isLifecycle && Math.random() >= MEM0_TELEMETRY_SAMPLE_RATE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventData: TelemetryEventData = {
|
||||
function: `${instance.constructor.name}`,
|
||||
method: eventName,
|
||||
@@ -86,6 +111,8 @@ async function captureClientEvent(
|
||||
client_version: version,
|
||||
client_source: "nodejs",
|
||||
...additionalData,
|
||||
// sample_rate set AFTER the spread so callers can never override it
|
||||
sample_rate: isLifecycle ? 1.0 : MEM0_TELEMETRY_SAMPLE_RATE,
|
||||
};
|
||||
|
||||
await telemetry.captureEvent(
|
||||
|
||||
@@ -23,6 +23,8 @@ export interface TelemetryEventData {
|
||||
timestamp?: string;
|
||||
client_source: "browser" | "nodejs";
|
||||
client_version: string;
|
||||
/** Set by the sampling layer so PostHog dashboards can extrapolate via 1/sample_rate. */
|
||||
sample_rate?: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/// <reference types="jest" />
|
||||
/**
|
||||
* DeepSeek LLM — unit tests (mocked OpenAI).
|
||||
*/
|
||||
|
||||
import { DeepSeekLLM } from "../src/llms/deepseek";
|
||||
|
||||
const mockCreate = jest.fn();
|
||||
|
||||
jest.mock("openai", () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
chat: { completions: { create: mockCreate } },
|
||||
}));
|
||||
});
|
||||
|
||||
describe("DeepSeekLLM (unit)", () => {
|
||||
beforeEach(() => mockCreate.mockClear());
|
||||
|
||||
it("throws when no API key is provided", () => {
|
||||
const original = process.env.DEEPSEEK_API_KEY;
|
||||
delete process.env.DEEPSEEK_API_KEY;
|
||||
try {
|
||||
expect(() => new DeepSeekLLM({})).toThrow("DeepSeek API key is required");
|
||||
} finally {
|
||||
if (original !== undefined) process.env.DEEPSEEK_API_KEY = original;
|
||||
}
|
||||
});
|
||||
|
||||
it("generateResponse() returns a text response", async () => {
|
||||
mockCreate.mockResolvedValueOnce({
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: "Hello, world!",
|
||||
role: "assistant",
|
||||
tool_calls: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const llm = new DeepSeekLLM({ apiKey: "test-key" });
|
||||
const result = await llm.generateResponse([
|
||||
{ role: "user", content: "Hi" },
|
||||
]);
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledTimes(1);
|
||||
expect(result).toBe("Hello, world!");
|
||||
});
|
||||
|
||||
it("generateResponse() handles tool calls", async () => {
|
||||
mockCreate.mockResolvedValueOnce({
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: "",
|
||||
role: "assistant",
|
||||
tool_calls: [
|
||||
{
|
||||
function: {
|
||||
name: "get_weather",
|
||||
arguments: '{"city": "London"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const llm = new DeepSeekLLM({ apiKey: "test-key" });
|
||||
const result = await llm.generateResponse(
|
||||
[{ role: "user", content: "What is the weather?" }],
|
||||
undefined,
|
||||
[{ type: "function", function: { name: "get_weather" } }],
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
content: "",
|
||||
role: "assistant",
|
||||
toolCalls: [{ name: "get_weather", arguments: '{"city": "London"}' }],
|
||||
});
|
||||
});
|
||||
|
||||
it("generateResponse() wraps API errors with a clear message", async () => {
|
||||
mockCreate.mockRejectedValueOnce(new Error("Connection refused"));
|
||||
|
||||
const llm = new DeepSeekLLM({ apiKey: "test-key" });
|
||||
|
||||
await expect(
|
||||
llm.generateResponse([{ role: "user", content: "Hi" }]),
|
||||
).rejects.toThrow("DeepSeek LLM failed: Connection refused");
|
||||
});
|
||||
|
||||
it("generateChat() returns LLMResponse shape", async () => {
|
||||
mockCreate.mockResolvedValueOnce({
|
||||
choices: [
|
||||
{
|
||||
message: { content: "I can help with that.", role: "assistant" },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const llm = new DeepSeekLLM({ apiKey: "test-key" });
|
||||
const result = await llm.generateChat([
|
||||
{ role: "user", content: "Help me" },
|
||||
]);
|
||||
|
||||
expect(result).toEqual({
|
||||
content: "I can help with that.",
|
||||
role: "assistant",
|
||||
});
|
||||
});
|
||||
|
||||
it("generateChat() wraps API errors with a clear message", async () => {
|
||||
mockCreate.mockRejectedValueOnce(new Error("Timeout"));
|
||||
|
||||
const llm = new DeepSeekLLM({ apiKey: "test-key" });
|
||||
|
||||
await expect(
|
||||
llm.generateChat([{ role: "user", content: "Hi" }]),
|
||||
).rejects.toThrow("DeepSeek LLM failed: Timeout");
|
||||
});
|
||||
});
|
||||
@@ -87,6 +87,11 @@ jest.mock("../src/llms/lmstudio", () => ({
|
||||
.fn()
|
||||
.mockImplementation((config) => ({ type: "lmstudio-llm", config })),
|
||||
}));
|
||||
jest.mock("../src/llms/deepseek", () => ({
|
||||
DeepSeekLLM: jest
|
||||
.fn()
|
||||
.mockImplementation((config) => ({ type: "deepseek-llm", config })),
|
||||
}));
|
||||
|
||||
jest.mock("../src/vector_stores/qdrant", () => ({
|
||||
Qdrant: jest
|
||||
@@ -200,6 +205,7 @@ describe("LLMFactory", () => {
|
||||
["mistral"],
|
||||
["langchain"],
|
||||
["lmstudio"],
|
||||
["deepseek"],
|
||||
])("creates LLM for provider '%s'", (provider) => {
|
||||
expect(() => LLMFactory.create(provider, dummyLLMConfig)).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
/// <reference types="jest" />
|
||||
/**
|
||||
* Telemetry sampling — unit tests.
|
||||
*
|
||||
* Sampling lives inside captureClientEvent in src/utils/telemetry.ts. It uses
|
||||
* Math.random() compared against MEM0_TELEMETRY_SAMPLE_RATE (default 0.1) to
|
||||
* drop hot-path events; lifecycle events ('init', 'reset') always fire.
|
||||
*
|
||||
* These tests target captureClientEvent directly. Existing OSS tests that mock
|
||||
* captureClientEvent (vector-stores-compat, dimension-autodetect, config-manager)
|
||||
* are unaffected because the function signature and contract are unchanged.
|
||||
*/
|
||||
|
||||
import type { TelemetryInstance } from "../src/utils/telemetry.types";
|
||||
|
||||
// Helper to make a fake TelemetryInstance.
|
||||
function makeInstance(
|
||||
overrides: Partial<TelemetryInstance> = {},
|
||||
): TelemetryInstance {
|
||||
return {
|
||||
telemetryId: "test-id",
|
||||
constructor: { name: "Memory" },
|
||||
host: "https://test.example.com",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("telemetry sampling", () => {
|
||||
let originalFetch: typeof global.fetch;
|
||||
let fetchMock: jest.Mock;
|
||||
let randomSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
originalFetch = global.fetch;
|
||||
fetchMock = jest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValue(""),
|
||||
});
|
||||
global.fetch = fetchMock as any;
|
||||
randomSpy = jest.spyOn(Math, "random");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
randomSpy.mockRestore();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
describe("lifecycle events always fire", () => {
|
||||
it("init event fires even at the highest random value", async () => {
|
||||
randomSpy.mockReturnValue(0.999);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("init", makeInstance());
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("reset event fires even at the highest random value", async () => {
|
||||
randomSpy.mockReturnValue(0.999);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("reset", makeInstance());
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("init event payload has sample_rate: 1.0", async () => {
|
||||
randomSpy.mockReturnValue(0.999);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("init", makeInstance());
|
||||
const body = JSON.parse(fetchMock.mock.calls[0][1].body);
|
||||
expect(body.properties.sample_rate).toBe(1.0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hot-path events are sampled", () => {
|
||||
it("add event is dropped when random > sample_rate", async () => {
|
||||
randomSpy.mockReturnValue(0.99); // > 0.1 default rate
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("add", makeInstance());
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("search event is dropped when random > sample_rate", async () => {
|
||||
randomSpy.mockReturnValue(0.99);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("search", makeInstance());
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("add event passes when random < sample_rate", async () => {
|
||||
randomSpy.mockReturnValue(0.05); // < 0.1 default rate
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("add", makeInstance());
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("hot-path event payload has sample_rate: 0.1", async () => {
|
||||
randomSpy.mockReturnValue(0.05);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("add", makeInstance());
|
||||
const body = JSON.parse(fetchMock.mock.calls[0][1].body);
|
||||
expect(body.properties.sample_rate).toBe(0.1);
|
||||
});
|
||||
|
||||
it("sample_rate cannot be overridden by additionalData", async () => {
|
||||
randomSpy.mockReturnValue(0.05);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("add", makeInstance(), { sample_rate: 0.99 });
|
||||
const body = JSON.parse(fetchMock.mock.calls[0][1].body);
|
||||
expect(body.properties.sample_rate).toBe(0.1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("env var override", () => {
|
||||
afterEach(() => {
|
||||
delete process.env.MEM0_TELEMETRY_SAMPLE_RATE;
|
||||
});
|
||||
|
||||
it("rate 1.0 sends every event including hot-path at high random", async () => {
|
||||
process.env.MEM0_TELEMETRY_SAMPLE_RATE = "1.0";
|
||||
jest.resetModules();
|
||||
randomSpy.mockReturnValue(0.999);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("add", makeInstance());
|
||||
// 0.999 > 1.0 is false, so the gate never trips
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("rate 0.0 drops every hot-path event including at random 0", async () => {
|
||||
// The gate is `random >= rate`, so rate=0 drops at random=0 (0 >= 0 is true).
|
||||
process.env.MEM0_TELEMETRY_SAMPLE_RATE = "0.0";
|
||||
jest.resetModules();
|
||||
randomSpy.mockReturnValue(0.0);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("add", makeInstance());
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rate 0.0 drops at any random value", async () => {
|
||||
process.env.MEM0_TELEMETRY_SAMPLE_RATE = "0.0";
|
||||
jest.resetModules();
|
||||
randomSpy.mockReturnValue(0.5);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("add", makeInstance());
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rate 0.0 still passes lifecycle events", async () => {
|
||||
process.env.MEM0_TELEMETRY_SAMPLE_RATE = "0.0";
|
||||
jest.resetModules();
|
||||
randomSpy.mockReturnValue(0.999);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("init", makeInstance());
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("rate 0.5 passes events at random below 0.5", async () => {
|
||||
process.env.MEM0_TELEMETRY_SAMPLE_RATE = "0.5";
|
||||
jest.resetModules();
|
||||
randomSpy.mockReturnValue(0.3);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("add", makeInstance());
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("invalid env var falls back to default rate", async () => {
|
||||
process.env.MEM0_TELEMETRY_SAMPLE_RATE = "not a number";
|
||||
jest.resetModules();
|
||||
randomSpy.mockReturnValue(0.05);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("add", makeInstance());
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("out-of-range env var (negative) falls back to default rate", async () => {
|
||||
process.env.MEM0_TELEMETRY_SAMPLE_RATE = "-0.5";
|
||||
jest.resetModules();
|
||||
randomSpy.mockReturnValue(0.05);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("add", makeInstance());
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("out-of-range env var (> 1) falls back to default rate", async () => {
|
||||
process.env.MEM0_TELEMETRY_SAMPLE_RATE = "5";
|
||||
jest.resetModules();
|
||||
randomSpy.mockReturnValue(0.05);
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("add", makeInstance());
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("contract preservation", () => {
|
||||
it("lifecycle event includes additionalData", async () => {
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("init", makeInstance(), {
|
||||
api_version: "v1.1",
|
||||
client_type: "Memory",
|
||||
});
|
||||
const body = JSON.parse(fetchMock.mock.calls[0][1].body);
|
||||
expect(body.properties.api_version).toBe("v1.1");
|
||||
expect(body.properties.client_type).toBe("Memory");
|
||||
expect(body.properties.sample_rate).toBe(1.0);
|
||||
});
|
||||
|
||||
it("captureClientEvent without telemetryId is a no-op", async () => {
|
||||
const { captureClientEvent } = await import("../src/utils/telemetry");
|
||||
await captureClientEvent("add", makeInstance({ telemetryId: "" }));
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -134,6 +134,8 @@ class AzureOpenAILLM(LLMBase):
|
||||
"messages": messages,
|
||||
})
|
||||
|
||||
if response_format:
|
||||
params["response_format"] = response_format
|
||||
if tools:
|
||||
params["tools"] = tools
|
||||
params["tool_choice"] = tool_choice
|
||||
|
||||
+33
-8
@@ -63,6 +63,19 @@ def _normalize_iso_timestamp_to_utc(timestamp: Optional[str]) -> Optional[str]:
|
||||
return parsed.astimezone(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def _resolve_mapped_id(temp_uuid_mapping, resp, event_type):
|
||||
"""Resolve a temp integer ID from the LLM response to a real UUID.
|
||||
|
||||
Returns the UUID if found, or None (with a warning log) if the LLM
|
||||
hallucinated an ID that doesn't exist in the mapping.
|
||||
"""
|
||||
raw_id = resp.get("id")
|
||||
memory_id = temp_uuid_mapping.get(raw_id)
|
||||
if memory_id is None:
|
||||
logger.warning(f"{event_type} skipped: LLM returned unknown id {raw_id!r}")
|
||||
return memory_id
|
||||
|
||||
|
||||
# Fields that hold runtime auth/connection objects and must be preserved.
|
||||
# These are non-serializable objects (e.g. AWSV4SignerAuth, RequestsHttpConnection)
|
||||
# needed by clients like OpenSearch — not sensitive strings to redact.
|
||||
@@ -639,28 +652,34 @@ class Memory(MemoryBase):
|
||||
)
|
||||
returned_memories.append({"id": memory_id, "memory": action_text, "event": event_type})
|
||||
elif event_type == "UPDATE":
|
||||
memory_id = _resolve_mapped_id(temp_uuid_mapping, resp, "UPDATE")
|
||||
if memory_id is None:
|
||||
continue
|
||||
# Ensure action_text has an embedding cached to avoid redundant API calls
|
||||
if action_text not in new_message_embeddings:
|
||||
new_message_embeddings[action_text] = self.embedding_model.embed(action_text, "update")
|
||||
self._update_memory(
|
||||
memory_id=temp_uuid_mapping[resp.get("id")],
|
||||
memory_id=memory_id,
|
||||
data=action_text,
|
||||
existing_embeddings=new_message_embeddings,
|
||||
metadata=deepcopy(metadata),
|
||||
)
|
||||
returned_memories.append(
|
||||
{
|
||||
"id": temp_uuid_mapping[resp.get("id")],
|
||||
"id": memory_id,
|
||||
"memory": action_text,
|
||||
"event": event_type,
|
||||
"previous_memory": resp.get("old_memory"),
|
||||
}
|
||||
)
|
||||
elif event_type == "DELETE":
|
||||
self._delete_memory(memory_id=temp_uuid_mapping[resp.get("id")])
|
||||
memory_id = _resolve_mapped_id(temp_uuid_mapping, resp, "DELETE")
|
||||
if memory_id is None:
|
||||
continue
|
||||
self._delete_memory(memory_id=memory_id)
|
||||
returned_memories.append(
|
||||
{
|
||||
"id": temp_uuid_mapping[resp.get("id")],
|
||||
"id": memory_id,
|
||||
"memory": action_text,
|
||||
"event": event_type,
|
||||
}
|
||||
@@ -1755,6 +1774,9 @@ class AsyncMemory(MemoryBase):
|
||||
)
|
||||
memory_tasks.append((task, resp, "ADD", None))
|
||||
elif event_type == "UPDATE":
|
||||
memory_id = _resolve_mapped_id(temp_uuid_mapping, resp, "UPDATE")
|
||||
if memory_id is None:
|
||||
continue
|
||||
# Ensure action_text has an embedding cached to avoid redundant API calls
|
||||
if action_text not in new_message_embeddings:
|
||||
new_message_embeddings[action_text] = await asyncio.to_thread(
|
||||
@@ -1762,16 +1784,19 @@ class AsyncMemory(MemoryBase):
|
||||
)
|
||||
task = asyncio.create_task(
|
||||
self._update_memory(
|
||||
memory_id=temp_uuid_mapping[resp["id"]],
|
||||
memory_id=memory_id,
|
||||
data=action_text,
|
||||
existing_embeddings=new_message_embeddings,
|
||||
metadata=deepcopy(metadata),
|
||||
)
|
||||
)
|
||||
memory_tasks.append((task, resp, "UPDATE", temp_uuid_mapping[resp["id"]]))
|
||||
memory_tasks.append((task, resp, "UPDATE", memory_id))
|
||||
elif event_type == "DELETE":
|
||||
task = asyncio.create_task(self._delete_memory(memory_id=temp_uuid_mapping[resp.get("id")]))
|
||||
memory_tasks.append((task, resp, "DELETE", temp_uuid_mapping[resp.get("id")]))
|
||||
memory_id = _resolve_mapped_id(temp_uuid_mapping, resp, "DELETE")
|
||||
if memory_id is None:
|
||||
continue
|
||||
task = asyncio.create_task(self._delete_memory(memory_id=memory_id))
|
||||
memory_tasks.append((task, resp, "DELETE", memory_id))
|
||||
elif event_type == "NONE":
|
||||
# Even if content doesn't need updating, update session IDs if provided
|
||||
memory_id = temp_uuid_mapping.get(resp.get("id"))
|
||||
|
||||
@@ -2,6 +2,7 @@ import atexit
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import random
|
||||
import sys
|
||||
import threading
|
||||
|
||||
@@ -22,16 +23,65 @@ if not isinstance(MEM0_TELEMETRY, bool):
|
||||
|
||||
logging.getLogger("posthog").setLevel(logging.CRITICAL + 1)
|
||||
logging.getLogger("urllib3").setLevel(logging.CRITICAL + 1)
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Default sampling rate for hot-path OSS events. Lifecycle events always fire at 100%.
|
||||
# Override via MEM0_TELEMETRY_SAMPLE_RATE env var.
|
||||
_DEFAULT_SAMPLE_RATE = 0.1
|
||||
|
||||
|
||||
def _parse_sample_rate(raw):
|
||||
"""Parse MEM0_TELEMETRY_SAMPLE_RATE env var. Never raises."""
|
||||
try:
|
||||
value = float(raw)
|
||||
except (TypeError, ValueError):
|
||||
_logger.debug("MEM0_TELEMETRY_SAMPLE_RATE %r is not a number, defaulting to %s", raw, _DEFAULT_SAMPLE_RATE)
|
||||
return _DEFAULT_SAMPLE_RATE
|
||||
if not 0.0 <= value <= 1.0:
|
||||
_logger.debug("MEM0_TELEMETRY_SAMPLE_RATE %s out of [0.0, 1.0], defaulting to %s", value, _DEFAULT_SAMPLE_RATE)
|
||||
return _DEFAULT_SAMPLE_RATE
|
||||
return value
|
||||
|
||||
|
||||
MEM0_TELEMETRY_SAMPLE_RATE = _parse_sample_rate(os.environ.get("MEM0_TELEMETRY_SAMPLE_RATE", str(_DEFAULT_SAMPLE_RATE)))
|
||||
|
||||
# Events that bypass sampling and always fire. Keep this set in sync with the
|
||||
# event names passed to capture_event() in mem0/memory/main.py.
|
||||
_LIFECYCLE_EVENTS = frozenset({"mem0.init", "mem0.reset", "mem0._create_procedural_memory"})
|
||||
|
||||
|
||||
def _sampling_before_send(msg):
|
||||
"""PostHog before_send hook: drop sampled hot-path events, annotate survivors with sample_rate."""
|
||||
if not isinstance(msg, dict):
|
||||
return None
|
||||
|
||||
event_name = msg.get("event", "")
|
||||
is_lifecycle = event_name in _LIFECYCLE_EVENTS
|
||||
|
||||
# >= so that rate=0 drops everything and rate=1 keeps everything (random ∈ [0, 1)).
|
||||
if not is_lifecycle and random.random() >= MEM0_TELEMETRY_SAMPLE_RATE:
|
||||
return None
|
||||
|
||||
# Annotate so PostHog dashboards can extrapolate true counts via 1/sample_rate.
|
||||
properties = msg.setdefault("properties", {})
|
||||
properties["sample_rate"] = 1.0 if is_lifecycle else MEM0_TELEMETRY_SAMPLE_RATE
|
||||
return msg
|
||||
|
||||
|
||||
class AnonymousTelemetry:
|
||||
def __init__(self, vector_store=None):
|
||||
def __init__(self, vector_store=None, before_send=None):
|
||||
if not MEM0_TELEMETRY:
|
||||
self.posthog = None
|
||||
self.user_id = None
|
||||
return
|
||||
|
||||
self.posthog = Posthog(project_api_key=PROJECT_API_KEY, host=HOST)
|
||||
try:
|
||||
self.posthog = Posthog(project_api_key=PROJECT_API_KEY, host=HOST, before_send=before_send)
|
||||
except TypeError:
|
||||
# posthog <4.5.0 does not accept before_send; fall back without sampling.
|
||||
_logger.debug("posthog.Posthog does not accept before_send; upgrade to >=4.5.0 for sampling")
|
||||
self.posthog = Posthog(project_api_key=PROJECT_API_KEY, host=HOST)
|
||||
self.user_id = get_or_create_user_id(vector_store)
|
||||
|
||||
def capture_event(self, event_name, properties=None, user_email=None):
|
||||
@@ -87,7 +137,7 @@ def _get_oss_telemetry():
|
||||
# Double-checked locking
|
||||
if _oss_telemetry_instance is not None:
|
||||
return _oss_telemetry_instance
|
||||
_oss_telemetry_instance = AnonymousTelemetry()
|
||||
_oss_telemetry_instance = AnonymousTelemetry(before_send=_sampling_before_send)
|
||||
atexit.register(_shutdown_oss_telemetry)
|
||||
return _oss_telemetry_instance
|
||||
|
||||
@@ -102,6 +152,7 @@ def _shutdown_oss_telemetry():
|
||||
|
||||
|
||||
# Module-level client telemetry singleton (used by capture_client_event).
|
||||
# No before_send — hosted MemoryClient traffic must never be sampled.
|
||||
client_telemetry = AnonymousTelemetry()
|
||||
atexit.register(client_telemetry.close)
|
||||
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to the `@mem0/openclaw-mem0` plugin will be documented in this file.
|
||||
|
||||
## [1.0.4] - 2026-04-04
|
||||
|
||||
### Added
|
||||
- **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` (renamed from `stats`), `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 (sync read/write/exists/mkdir/unlink) in a separate entry point — keeps file I/O out of the main bundle
|
||||
- **`backend/` module**: `PlatformBackend` with direct HTTP API access for CLI commands
|
||||
- **`cli/config-file.ts`**: Persistent plugin auth storage in `~/.openclaw/openclaw.json`
|
||||
- **Plugin manifest**: Added `contracts.tools`, `configSchema`, and `uiHints` to `openclaw.plugin.json`
|
||||
- **Test suite**: 329 tests across 10 test files covering tools, CLI, config, dream gate, providers, and skill-loader
|
||||
|
||||
### Changed
|
||||
- **Modular architecture**: Extracted tools into `tools/` directory (6 files) and CLI into `cli/commands.ts` — `index.ts` down from ~1700 to ~890 lines
|
||||
- **Code splitting**: tsup builds with `splitting: true` and two entry points (`index.ts`, `fs-safe.ts`), separating filesystem I/O from the main bundle
|
||||
- **Skills updated**: All SKILL.md files reference new tool names (`memory_add`, `memory_delete`) matching the plugin manifest
|
||||
- **WRITE_TOOLS updated**: Dream gate tracks `memory_delete` and `memory_add` instead of `memory_forget` and `memory_store`
|
||||
- **`mem0ai` dependency**: Updated from `2.3.0` to `2.4.5`
|
||||
- **Auto-recall timeout**: Recall wrapped in 8-second `Promise.race` — if the LLM takes too long, recall is skipped instead of stalling the gateway
|
||||
- **Auto-capture fire-and-forget**: `provider.add()` now runs in the background via `.then()/.catch()` — the `agent_end` hook returns immediately, zero event loop blocking
|
||||
- **Auto-capture minimum content gate**: Skips extraction when total user content is <50 chars after filtering — trivial conversations ("ok", "thanks") no longer trigger LLM calls
|
||||
- **CLI search**: Lowered threshold to 0.3 so explicit searches are more permissive than auto-recall
|
||||
- **Init defaults**: Choice defaults to `1` (email login) on Enter, User ID defaults to OS username — no more empty values
|
||||
- **Init no longer stores `baseUrl`**: Uses `https://api.mem0.ai` directly instead of persisting it to config
|
||||
- **Help output reordered**: `openclaw mem0 help` now shows high-value commands first (search, add) instead of alphabetical
|
||||
|
||||
### 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
|
||||
- **`custom_instructions` / `custom_categories` in `buildAddOptions`**: No longer injected into every auto-capture API call. Config fields (`customInstructions`, `customCategories`) remain as user-configurable options.
|
||||
|
||||
## [1.0.3] - 2026-04-03
|
||||
|
||||
### Fixed
|
||||
- **Path traversal vulnerability**: Added `safePath()` containment helper to `readSkillFile` and `readDomainOverlay` in `skill-loader.ts` — prevents directory traversal via `config.domain` or the exported `loadSkill` API
|
||||
- **Noise filter regression**: Reverted incorrect `After-Compaction` regex rename back to `Post-Compaction` so the filter correctly matches real upstream compaction audit messages
|
||||
- **Cosmetic revert**: Restored `// Over-fetch for ranking` comment in `recall.ts` (was changed to work around a false-positive scanner match on the substring `fetch`)
|
||||
|
||||
### Changed
|
||||
- **Supply-chain hardening**: Pinned `mem0ai` dependency to exact `2.3.0` (was `^2.3.0`)
|
||||
|
||||
### Added
|
||||
- **Path traversal tests**: 12 new tests covering `safePath`, `readSkillFile`, `readDomainOverlay`, and `loadSkill` with traversal inputs
|
||||
|
||||
## [1.0.2] - 2026-04-02
|
||||
|
||||
### Fixed
|
||||
- **Security scanner warning**: Removed `resolveEnvVars()` and `resolveEnvVarsDeep()` from `config.ts` — OpenClaw already resolves `${VAR}` in `openclaw.json` before passing config to the plugin, so plugin-side env resolution was redundant and triggered the "credential harvesting" static analysis warning ([#4676](https://github.com/mem0ai/mem0/pull/4676))
|
||||
|
||||
## [1.0.1] - 2026-04-02
|
||||
|
||||
### Added
|
||||
- **CD workflow**: Added continuous deployment workflow for `@mem0/openclaw-mem0` with OIDC trusted publishing ([#4672](https://github.com/mem0ai/mem0/pull/4672))
|
||||
- **Plugin configuration manifest**: Added `compat` and `build` metadata to `package.json` specifying minimum gateway version and OpenClaw SDK compatibility (`>=2026.3.24-beta.2`) ([#4667](https://github.com/mem0ai/mem0/pull/4667))
|
||||
- **LICENSE**: Added Apache-2.0 license file to the package ([#4667](https://github.com/mem0ai/mem0/pull/4667))
|
||||
|
||||
### Fixed
|
||||
- **Dream gate correctness**: Fixed cheap-first ordering, session isolation, and verified completion in the dream gate memory consolidation pipeline ([#4666](https://github.com/mem0ai/mem0/pull/4666))
|
||||
- **Graceful startup without API key**: Plugin now starts gracefully when no API key is configured instead of crashing on init ([#4669](https://github.com/mem0ai/mem0/pull/4669))
|
||||
|
||||
## [1.0.0] - 2026-04-01
|
||||
|
||||
### Added
|
||||
- **Skills-based memory architecture**: New skill-loader and skill-based extraction pipeline with batched extraction for higher quality memory capture ([#4624](https://github.com/mem0ai/mem0/pull/4624))
|
||||
- **Dream gate**: Added `dream-gate.ts` for memory consolidation and dream-cycle processing
|
||||
- **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
|
||||
|
||||
### Changed
|
||||
- Extraction pipeline refactored to use skills-based architecture for more contextual and higher quality memory capture
|
||||
|
||||
## [0.4.1] - 2026-03-26
|
||||
|
||||
### Added
|
||||
- **Improved extraction quality**: Enhanced noise filtering, deduplication, and better extraction instructions for higher-quality memory capture (#4302)
|
||||
|
||||
### Fixed
|
||||
- **Credential detection in extraction**: Improved detection of credentials, API keys, and secrets in extraction instructions to prevent them from being stored as memories (#4552)
|
||||
- **Standalone timestamp extraction**: Prevented extraction of standalone timestamps as memories when no meaningful content accompanies them (#4550)
|
||||
|
||||
## [0.4.0] - 2026-03-16
|
||||
|
||||
### Added
|
||||
- **Non-interactive trigger filtering**: Skips recall and capture for `cron`, `heartbeat`, `automation`, and `schedule` triggers — prevents system-generated noise from polluting memory
|
||||
- **Subagent hallucination prevention**: `isSubagentSession()` detects ephemeral subagent sessions and routes recall to the parent (main user) namespace instead of empty ephemeral namespaces; skips capture to prevent orphaned memories
|
||||
- **Subagent-specific preamble**: Subagents receive "You are a subagent — use these memories for context but do not assume you are this user" to prevent identity assumption
|
||||
- **User identity in recall preamble**: Recalled memories now include `userId` attribution for better context
|
||||
- **User identity in extraction preamble**: Extraction context includes user identity and current date for accurate attribution and temporal anchoring
|
||||
- **User-content guard**: Skips extraction when no meaningful user messages remain after filtering
|
||||
- **Dynamic recall thresholding**: Memories scoring less than 50% of the top result are dropped to filter out the long tail of weak matches
|
||||
- **SQLite resilience for OSS mode**: Init error recovery with automatic retry (history disabled) when native SQLite bindings fail under jiti
|
||||
- **`disableHistory` config option**: New `oss.disableHistory` flag to explicitly skip history DB initialization
|
||||
- **Updated minimum package version of mem0ai package**: Updated minimum package version of mem0ai package to ^2.3.0 to force old users to migrate to better-sqlite3
|
||||
- 78 unit tests covering filtering, isolation, trigger filtering, subagent detection, and SQLite resilience
|
||||
|
||||
### Changed
|
||||
- Auto-recall threshold raised from 0.5 to 0.6 for stricter precision during automatic injection (explicit tool searches remain at 0.5)
|
||||
- Recall candidate pool increased to `topK * 2` for better filtering headroom
|
||||
- Provider init promises now reset on failure, allowing retry on subsequent calls
|
||||
- Relaxed extraction instructions: related facts are kept together to preserve context (removed atomic memory requirement)
|
||||
|
||||
### Fixed
|
||||
- **Concurrent session race condition**: Lifecycle hooks (`before_agent_start`, `agent_end`) now use `ctx.sessionKey` directly from the event context instead of a shared mutable `currentSessionId` variable, preventing cross-session data leaks when multiple sessions run simultaneously
|
||||
|
||||
## [0.3.1] - 2026-03-12
|
||||
|
||||
### Added
|
||||
- **Message filtering pipeline**: Multi-stage noise removal before extraction — drops heartbeats, timestamps, single-word acks, system routing metadata, compaction audit logs, and generic assistant acknowledgments
|
||||
- **Broad recall for new sessions**: Short or new-session prompts trigger a secondary broad search to avoid cold-start blindness
|
||||
- **Client-side threshold filtering**: Safety net that drops low-relevance results even if the API doesn't honor the threshold parameter
|
||||
- **Temporal anchoring**: Extraction instructions now include current date so memories are prefixed with "As of YYYY-MM-DD, ..."
|
||||
- **Summary message inclusion**: Earlier assistant messages containing work summaries are included in extraction context even if outside the recent-message window
|
||||
- 55 unit tests covering filtering and isolation helpers
|
||||
|
||||
### Changed
|
||||
- Default `searchThreshold` remains at 0.5, with client-side filtering as a safety net
|
||||
- Extraction window expanded from last 10 → last 20 messages for richer context
|
||||
- Rewritten custom extraction instructions: conciseness, outcome-over-intent, deduplication guidance, language preservation
|
||||
- **Refactored** monolithic `index.ts` (1772 lines) into 6 focused modules: `types.ts`, `providers.ts`, `config.ts`, `filtering.ts`, `isolation.ts`, `index.ts`
|
||||
|
||||
### Fixed
|
||||
- **README image on npmjs.com**: Changed architecture diagram from relative path to absolute GitHub URL so it renders correctly on the npm registry
|
||||
|
||||
## [0.3.0] - 2026-03-10
|
||||
|
||||
### Fixed
|
||||
- Updated `mem0ai` dependency which includes the sqlite3 to better-sqlite3 migration for native binding resolution (#4270)
|
||||
|
||||
## [0.2.0] - 2026-03-09
|
||||
|
||||
### Added
|
||||
- "Understanding userId" section in docs clarifying that `userId` is user-defined
|
||||
- Per-agent memory isolation for multi-agent setups via `agentId`
|
||||
- Regression tests for per-agent isolation helpers
|
||||
|
||||
### Changed
|
||||
- Updated config examples to use concrete `userId` values instead of placeholders
|
||||
|
||||
### Fixed
|
||||
- Migrated platform search to Mem0 v2 API
|
||||
|
||||
## [0.1.2] - 2026-02-19
|
||||
|
||||
### Added
|
||||
- Source field for openclaw memory entries
|
||||
|
||||
### Fixed
|
||||
- Auto-recall injection and auto-capture message drop
|
||||
|
||||
## [0.1.0] - 2026-02-02
|
||||
|
||||
### Added
|
||||
- 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
|
||||
@@ -149,9 +149,6 @@ openclaw mem0 dream --dry-run
|
||||
| Key | Type | Default | Description |
|
||||
| --- | ---- | ------- | ----------- |
|
||||
| `apiKey` | `string` | — | **Required.** Mem0 API key (supports `${MEM0_API_KEY}`) |
|
||||
| `orgId` | `string` | — | Organization ID |
|
||||
| `projectId` | `string` | — | Project ID |
|
||||
| `enableGraph` | `boolean` | `false` | Entity graph for relationship tracking |
|
||||
| `customInstructions` | `string` | *(built-in)* | Custom extraction rules |
|
||||
| `customCategories` | `object` | *(12 defaults)* | Category name to description map |
|
||||
|
||||
@@ -169,7 +166,6 @@ All fields optional. Defaults: `text-embedding-3-small` embeddings, local SQLite
|
||||
| `oss.llm.provider` | `string` | `"openai"` | LLM provider |
|
||||
| `oss.llm.config` | `object` | — | Provider config (`apiKey`, `model`, `baseURL`) |
|
||||
| `oss.historyDbPath` | `string` | — | SQLite path for edit history |
|
||||
| `oss.disableHistory` | `boolean` | `false` | Skip history DB |
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ export interface AddOptions {
|
||||
infer?: boolean;
|
||||
expires?: string;
|
||||
categories?: string[];
|
||||
enableGraph?: boolean;
|
||||
}
|
||||
|
||||
export interface SearchOptions {
|
||||
@@ -28,7 +27,6 @@ export interface SearchOptions {
|
||||
keyword?: boolean;
|
||||
filters?: Record<string, unknown>;
|
||||
fields?: string[];
|
||||
enableGraph?: boolean;
|
||||
}
|
||||
|
||||
export interface ListOptions {
|
||||
@@ -41,7 +39,6 @@ export interface ListOptions {
|
||||
category?: string;
|
||||
after?: string;
|
||||
before?: string;
|
||||
enableGraph?: boolean;
|
||||
}
|
||||
|
||||
export interface DeleteOptions {
|
||||
|
||||
@@ -111,7 +111,6 @@ export class PlatformBackend implements Backend {
|
||||
if (opts.infer === false) payload.infer = false;
|
||||
if (opts.expires) payload.expiration_date = opts.expires;
|
||||
if (opts.categories) payload.categories = opts.categories;
|
||||
if (opts.enableGraph) payload.enable_graph = true;
|
||||
|
||||
return (await this._request("POST", "/v1/memories/", {
|
||||
json: payload,
|
||||
@@ -171,7 +170,6 @@ export class PlatformBackend implements Backend {
|
||||
if (opts.rerank) payload.rerank = true;
|
||||
if (opts.keyword) payload.keyword_search = true;
|
||||
if (opts.fields) payload.fields = opts.fields;
|
||||
if (opts.enableGraph) payload.enable_graph = true;
|
||||
|
||||
const result = (await this._request("POST", "/v2/memories/search/", {
|
||||
json: payload,
|
||||
@@ -222,7 +220,6 @@ export class PlatformBackend implements Backend {
|
||||
extraFilters: Object.keys(extra).length > 0 ? extra : undefined,
|
||||
});
|
||||
if (apiFilters) payload.filters = apiFilters;
|
||||
if (opts.enableGraph) payload.enable_graph = true;
|
||||
|
||||
const result = (await this._request("POST", "/v2/memories/", {
|
||||
json: payload,
|
||||
|
||||
+13
-18
@@ -384,10 +384,6 @@ export function registerCliCommands(
|
||||
console.log(` User ID: ${existingAuth.userId}`);
|
||||
if (existingAuth.mode)
|
||||
console.log(` Mode: ${existingAuth.mode}`);
|
||||
if (existingAuth.orgId)
|
||||
console.log(` Org ID: ${existingAuth.orgId}`);
|
||||
if (existingAuth.projectId)
|
||||
console.log(` Project: ${existingAuth.projectId}`);
|
||||
console.log("");
|
||||
|
||||
// Validate existing key before asking
|
||||
@@ -408,7 +404,7 @@ export function registerCliCommands(
|
||||
}
|
||||
|
||||
const reuse = await promptInput(
|
||||
" Keep existing configuration? (Y/n): ",
|
||||
" Keep existing configuration? (y/n): ",
|
||||
);
|
||||
if (
|
||||
reuse === "" ||
|
||||
@@ -432,7 +428,7 @@ export function registerCliCommands(
|
||||
console.log(" 2. Enter API key manually");
|
||||
console.log(" 3. Open-source mode (self-hosted)\n");
|
||||
|
||||
const choice = (await promptInput(" Choice: ", "1")) || "1";
|
||||
const choice = (await promptInput(" Choice (1/2/3): ")) || "1";
|
||||
|
||||
if (choice === "1") {
|
||||
// --- Email interactive flow ---
|
||||
@@ -625,7 +621,6 @@ export function registerCliCommands(
|
||||
runId?: string,
|
||||
): SearchOptions => {
|
||||
const base = buildSearchOptions(userIdOverride, lim, runId);
|
||||
delete (base as any).source;
|
||||
base.threshold = 0.3;
|
||||
return base;
|
||||
};
|
||||
@@ -722,7 +717,7 @@ export function registerCliCommands(
|
||||
: effectiveUserId(getCurrentSessionId());
|
||||
const result = await provider.add(
|
||||
[{ role: "user", content: text }],
|
||||
{ user_id: uid },
|
||||
{ user_id: uid, source: "OPENCLAW" },
|
||||
);
|
||||
const count = result.results?.length ?? 0;
|
||||
if (count > 0) {
|
||||
@@ -952,9 +947,6 @@ export function registerCliCommands(
|
||||
email: "userEmail",
|
||||
base_url: "baseUrl",
|
||||
user_id: "userId",
|
||||
org_id: "orgId",
|
||||
project_id: "projectId",
|
||||
enable_graph: "enableGraph",
|
||||
auto_recall: "autoRecall",
|
||||
auto_capture: "autoCapture",
|
||||
top_k: "topK",
|
||||
@@ -969,6 +961,8 @@ export function registerCliCommands(
|
||||
vector_host: "oss.vectorStore.config.host",
|
||||
vector_port: "oss.vectorStore.config.port",
|
||||
collection_name: "oss.vectorStore.config.collectionName",
|
||||
vector_db_name: "oss.vectorStore.config.dbname",
|
||||
vector_db_user: "oss.vectorStore.config.user",
|
||||
vector_db_path: "oss.vectorStore.config.dbPath",
|
||||
history_db_path: "oss.historyDbPath",
|
||||
disable_history: "oss.disableHistory",
|
||||
@@ -979,7 +973,6 @@ export function registerCliCommands(
|
||||
|
||||
// Boolean config fields — coerce "true"/"1"/"yes" on set
|
||||
const BOOLEAN_KEYS = new Set([
|
||||
"enableGraph",
|
||||
"autoRecall",
|
||||
"autoCapture",
|
||||
"oss.disableHistory",
|
||||
@@ -1009,11 +1002,8 @@ export function registerCliCommands(
|
||||
apiKey: auth.apiKey ?? cfg.apiKey,
|
||||
baseUrl: auth.baseUrl ?? cfg.baseUrl ?? "https://api.mem0.ai",
|
||||
userId: auth.userId ?? cfg.userId,
|
||||
orgId: auth.orgId ?? cfg.orgId,
|
||||
projectId: auth.projectId ?? cfg.projectId,
|
||||
mode: auth.mode ?? cfg.mode,
|
||||
userEmail: auth.userEmail,
|
||||
enableGraph: cfg.enableGraph,
|
||||
autoRecall: cfg.autoRecall,
|
||||
autoCapture: cfg.autoCapture,
|
||||
topK: cfg.topK,
|
||||
@@ -1055,9 +1045,6 @@ export function registerCliCommands(
|
||||
entries.push(
|
||||
["api_key", "apiKey"],
|
||||
["email", "userEmail"],
|
||||
["org_id", "orgId"],
|
||||
["project_id", "projectId"],
|
||||
["enable_graph", "enableGraph"],
|
||||
);
|
||||
} else {
|
||||
entries.push(
|
||||
@@ -1238,6 +1225,10 @@ export function registerCliCommands(
|
||||
.description("List recent background events")
|
||||
.action(async () => {
|
||||
try {
|
||||
if (!backend || cfg.mode === "open-source") {
|
||||
console.log("Event tracking is only available in platform mode.");
|
||||
return;
|
||||
}
|
||||
const results = await backend.listEvents();
|
||||
if (!results.length) {
|
||||
console.log("No events found.");
|
||||
@@ -1282,6 +1273,10 @@ export function registerCliCommands(
|
||||
.argument("<event_id>", "Event ID to check")
|
||||
.action(async (eventId: string) => {
|
||||
try {
|
||||
if (!backend || cfg.mode === "open-source") {
|
||||
console.log("Event tracking is only available in platform mode.");
|
||||
return;
|
||||
}
|
||||
const ev = await backend.getEvent(eventId);
|
||||
|
||||
const status = String(ev.status ?? "—");
|
||||
|
||||
@@ -29,14 +29,12 @@ export interface PluginAuthConfig {
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
userId?: string;
|
||||
orgId?: string;
|
||||
projectId?: string;
|
||||
userEmail?: string;
|
||||
mode?: string;
|
||||
enableGraph?: boolean;
|
||||
autoRecall?: boolean;
|
||||
autoCapture?: boolean;
|
||||
topK?: number;
|
||||
anonymousTelemetryId?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -76,14 +74,12 @@ export function readPluginAuth(): PluginAuthConfig {
|
||||
apiKey: (cfg.apiKey ?? cfg.api_key) as string | undefined,
|
||||
baseUrl: (cfg.baseUrl ?? cfg.base_url) as string | undefined,
|
||||
userId: (cfg.userId ?? cfg.user_id) as string | undefined,
|
||||
orgId: (cfg.orgId ?? cfg.org_id) as string | undefined,
|
||||
projectId: (cfg.projectId ?? cfg.project_id) as string | undefined,
|
||||
userEmail: (cfg.userEmail ?? cfg.user_email) as string | undefined,
|
||||
mode: cfg.mode as string | undefined,
|
||||
enableGraph: cfg.enableGraph as boolean | undefined,
|
||||
autoRecall: cfg.autoRecall as boolean | undefined,
|
||||
autoCapture: cfg.autoCapture as boolean | undefined,
|
||||
topK: cfg.topK as number | undefined,
|
||||
anonymousTelemetryId: cfg.anonymousTelemetryId as string | undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+9
-14
@@ -19,8 +19,6 @@ import type { Mem0Config, Mem0Mode } from "./types.ts";
|
||||
export interface FileConfig {
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
orgId?: string;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -152,14 +150,11 @@ const ALLOWED_KEYS = [
|
||||
"baseUrl",
|
||||
"userId",
|
||||
"userEmail",
|
||||
"orgId",
|
||||
"projectId",
|
||||
"autoCapture",
|
||||
"autoRecall",
|
||||
"customInstructions",
|
||||
"customCategories",
|
||||
"customPrompt",
|
||||
"enableGraph",
|
||||
"searchThreshold",
|
||||
"topK",
|
||||
"oss",
|
||||
@@ -185,6 +180,15 @@ export const mem0ConfigSchema = {
|
||||
assertAllowedKeys(cfg, ALLOWED_KEYS, "openclaw-mem0 config");
|
||||
|
||||
// Only two modes: "platform" (default) or "open-source"
|
||||
if (
|
||||
typeof cfg.mode === "string" &&
|
||||
cfg.mode !== "platform" &&
|
||||
cfg.mode !== "open-source"
|
||||
) {
|
||||
console.warn(
|
||||
`[mem0] Unknown mode "${cfg.mode}" — expected "platform" or "open-source". Defaulting to "platform".`,
|
||||
);
|
||||
}
|
||||
const mode: Mem0Mode =
|
||||
cfg.mode === "open-source" ? "open-source" : "platform";
|
||||
|
||||
@@ -193,15 +197,9 @@ export const mem0ConfigSchema = {
|
||||
typeof cfg.apiKey === "string" ? cfg.apiKey : undefined;
|
||||
let resolvedBaseUrl =
|
||||
typeof cfg.baseUrl === "string" ? cfg.baseUrl : undefined;
|
||||
let resolvedOrgId = typeof cfg.orgId === "string" ? cfg.orgId : undefined;
|
||||
let resolvedProjectId =
|
||||
typeof cfg.projectId === "string" ? cfg.projectId : undefined;
|
||||
if (mode === "platform" && !resolvedApiKey && fileConfig) {
|
||||
if (fileConfig.apiKey) resolvedApiKey = fileConfig.apiKey;
|
||||
if (fileConfig.baseUrl) resolvedBaseUrl = fileConfig.baseUrl;
|
||||
if (!resolvedOrgId && fileConfig.orgId) resolvedOrgId = fileConfig.orgId;
|
||||
if (!resolvedProjectId && fileConfig.projectId)
|
||||
resolvedProjectId = fileConfig.projectId;
|
||||
}
|
||||
|
||||
// Platform mode requires apiKey — but don't throw on missing config.
|
||||
@@ -228,8 +226,6 @@ export const mem0ConfigSchema = {
|
||||
return "default";
|
||||
}
|
||||
})(),
|
||||
orgId: resolvedOrgId,
|
||||
projectId: resolvedProjectId,
|
||||
autoCapture: cfg.autoCapture !== false,
|
||||
autoRecall: cfg.autoRecall !== false,
|
||||
customInstructions:
|
||||
@@ -246,7 +242,6 @@ export const mem0ConfigSchema = {
|
||||
typeof cfg.customPrompt === "string"
|
||||
? cfg.customPrompt
|
||||
: DEFAULT_CUSTOM_INSTRUCTIONS,
|
||||
enableGraph: cfg.enableGraph === true,
|
||||
searchThreshold:
|
||||
typeof cfg.searchThreshold === "number" ? cfg.searchThreshold : 0.5,
|
||||
topK: typeof cfg.topK === "number" ? cfg.topK : 5,
|
||||
|
||||
+42
-21
@@ -103,15 +103,21 @@ const memoryPlugin = definePluginEntry({
|
||||
const fileConfig: FileConfig = {
|
||||
apiKey: pluginAuth.apiKey,
|
||||
baseUrl: pluginAuth.baseUrl,
|
||||
orgId: pluginAuth.orgId,
|
||||
projectId: pluginAuth.projectId,
|
||||
};
|
||||
const cfg = mem0ConfigSchema.parse(api.pluginConfig, fileConfig);
|
||||
|
||||
// Telemetry context bound to this plugin instance's config
|
||||
const telemetryCtx = { apiKey: cfg.apiKey, mode: cfg.mode, skillsActive: false };
|
||||
const telemetryCtx = {
|
||||
apiKey: cfg.apiKey,
|
||||
mode: cfg.mode,
|
||||
skillsActive: false,
|
||||
};
|
||||
const _captureEvent = (event: string, props?: Record<string, unknown>) => {
|
||||
try { captureEvent(event, props, telemetryCtx); } catch { /* silently swallow */ }
|
||||
try {
|
||||
captureEvent(event, props, telemetryCtx);
|
||||
} catch {
|
||||
/* silently swallow */
|
||||
}
|
||||
};
|
||||
|
||||
if (cfg.needsSetup) {
|
||||
@@ -134,7 +140,7 @@ const memoryPlugin = definePluginEntry({
|
||||
(id: string) => `${cfg.userId}:agent:${id}`,
|
||||
() => ({ user_id: cfg.userId, top_k: cfg.topK }),
|
||||
() => undefined,
|
||||
(cmd: string) => _captureEvent(`openclaw.${cmd}`, { command: cmd }),
|
||||
(cmd: string) => _captureEvent(`openclaw.cli.${cmd}`, { command: cmd }),
|
||||
);
|
||||
|
||||
api.registerService({
|
||||
@@ -182,7 +188,7 @@ const memoryPlugin = definePluginEntry({
|
||||
});
|
||||
|
||||
api.logger.info(
|
||||
`openclaw-mem0: registered (mode: ${cfg.mode}, user: ${cfg.userId}, graph: ${cfg.enableGraph}, autoRecall: ${cfg.autoRecall}, autoCapture: ${cfg.autoCapture}, skills: ${skillsActive})`,
|
||||
`openclaw-mem0: registered (mode: ${cfg.mode}, user: ${cfg.userId}, autoRecall: ${cfg.autoRecall}, autoCapture: ${cfg.autoCapture}, skills: ${skillsActive})`,
|
||||
);
|
||||
|
||||
// Helper: build add options
|
||||
@@ -197,7 +203,6 @@ const memoryPlugin = definePluginEntry({
|
||||
};
|
||||
if (runId) opts.run_id = runId;
|
||||
if (cfg.mode === "platform") {
|
||||
opts.enable_graph = cfg.enableGraph;
|
||||
opts.output_format = "v1.1";
|
||||
}
|
||||
return opts;
|
||||
@@ -242,7 +247,10 @@ const memoryPlugin = definePluginEntry({
|
||||
getCurrentSessionId: () => currentSessionId,
|
||||
skillsActive,
|
||||
captureToolEvent: (toolName: string, props: Record<string, unknown>) => {
|
||||
_captureEvent(`openclaw.tool.${toolName}`, { tool_name: toolName, ...props });
|
||||
_captureEvent(`openclaw.tool.${toolName}`, {
|
||||
tool_name: toolName,
|
||||
...props,
|
||||
});
|
||||
},
|
||||
};
|
||||
registerAllTools(toolDeps);
|
||||
@@ -328,7 +336,10 @@ function registerHooks(
|
||||
getStateDir: () => string | undefined;
|
||||
},
|
||||
skillsActive: boolean = false,
|
||||
_captureEvent: (event: string, props?: Record<string, unknown>) => void = () => {},
|
||||
_captureEvent: (
|
||||
event: string,
|
||||
props?: Record<string, unknown>,
|
||||
) => void = () => {},
|
||||
) {
|
||||
// ========================================================================
|
||||
// SKILLS MODE: Agentic memory via before_prompt_build
|
||||
@@ -355,15 +366,11 @@ function registerHooks(
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip recall for system/bootstrap prompts. These are OpenClaw internal
|
||||
// commands (/new, /reset) that contain system instructions, not user queries.
|
||||
// Sending them to mem0 search wastes API calls and returns noise.
|
||||
const promptLower = event.prompt.toLowerCase();
|
||||
const isSystemPrompt =
|
||||
promptLower.includes("a new session was started") ||
|
||||
promptLower.includes("session startup sequence") ||
|
||||
promptLower.includes("/new or /reset") ||
|
||||
promptLower.startsWith("system:") ||
|
||||
promptLower.startsWith("run your session");
|
||||
if (isSystemPrompt) {
|
||||
api.logger.info(
|
||||
@@ -472,7 +479,10 @@ function registerHooks(
|
||||
"\n</auto-dream>";
|
||||
// Track which session triggered dream (session-keyed, not global)
|
||||
dreamSessionId = sessionId;
|
||||
_captureEvent("openclaw.hook.dream", { phase: "triggered", memory_count: memCount });
|
||||
_captureEvent("openclaw.hook.dream", {
|
||||
phase: "triggered",
|
||||
memory_count: memCount,
|
||||
});
|
||||
api.logger.info(
|
||||
`openclaw-mem0: auto-dream triggered (${memCount} memories, gate passed)`,
|
||||
);
|
||||
@@ -545,7 +555,10 @@ function registerHooks(
|
||||
if (writeToolUsed) {
|
||||
releaseDreamLock(stateDir);
|
||||
recordDreamCompletion(stateDir);
|
||||
_captureEvent("openclaw.hook.dream", { phase: "completed", write_tools_used: true });
|
||||
_captureEvent("openclaw.hook.dream", {
|
||||
phase: "completed",
|
||||
write_tools_used: true,
|
||||
});
|
||||
api.logger.info(
|
||||
"openclaw-mem0: auto-dream completed (verified write tool usage), lock released",
|
||||
);
|
||||
@@ -599,13 +612,11 @@ function registerHooks(
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip recall for system/bootstrap prompts to save API calls
|
||||
const promptLower = event.prompt.toLowerCase();
|
||||
const isSystemPrompt =
|
||||
promptLower.includes("a new session was started") ||
|
||||
promptLower.includes("session startup sequence") ||
|
||||
promptLower.includes("/new or /reset") ||
|
||||
promptLower.startsWith("system:") ||
|
||||
promptLower.startsWith("run your session");
|
||||
if (isSystemPrompt) {
|
||||
api.logger.info(
|
||||
@@ -775,11 +786,18 @@ function registerHooks(
|
||||
// Update shared state for tools (best-effort — tools don't have ctx)
|
||||
if (sessionId) session.setCurrentSessionId(sessionId);
|
||||
|
||||
const MEMORY_MUTATE_TOOLS = new Set(["memory_add", "memory_update", "memory_delete"]);
|
||||
const MEMORY_MUTATE_TOOLS = new Set([
|
||||
"memory_add",
|
||||
"memory_update",
|
||||
"memory_delete",
|
||||
]);
|
||||
const agentUsedMemoryTool = event.messages.some((msg: any) => {
|
||||
if (msg?.role !== "assistant" || !Array.isArray(msg?.content)) return false;
|
||||
if (msg?.role !== "assistant" || !Array.isArray(msg?.content))
|
||||
return false;
|
||||
return msg.content.some(
|
||||
(block: any) => block?.type === "tool_use" && MEMORY_MUTATE_TOOLS.has(block.name),
|
||||
(block: any) =>
|
||||
(block?.type === "tool_use" || block?.type === "toolCall") &&
|
||||
MEMORY_MUTATE_TOOLS.has(block.name),
|
||||
);
|
||||
});
|
||||
if (agentUsedMemoryTool) {
|
||||
@@ -848,7 +866,10 @@ function registerHooks(
|
||||
if (!textContent) continue;
|
||||
}
|
||||
// Strip OpenClaw sender metadata prefix (prevents storing TUI identity as memory)
|
||||
if (textContent.includes("Sender") && textContent.includes("untrusted metadata")) {
|
||||
if (
|
||||
textContent.includes("Sender") &&
|
||||
textContent.includes("untrusted metadata")
|
||||
) {
|
||||
textContent = textContent
|
||||
.replace(
|
||||
/Sender\s*\(untrusted metadata\):\s*```json[\s\S]*?```\s*/gi,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"id": "openclaw-mem0",
|
||||
"name": "Memory (Mem0)",
|
||||
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source",
|
||||
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source. Injects recalled memories into agent context (auto-recall) and extracts facts after each turn (auto-capture). Both configurable via autoRecall/autoCapture settings.",
|
||||
"version": "1.0.5",
|
||||
"kind": "memory",
|
||||
"skills": ["skills"],
|
||||
"contracts": {
|
||||
@@ -12,9 +13,23 @@
|
||||
},
|
||||
"providerAuthEnvVars": {
|
||||
"mem0": ["MEM0_API_KEY"],
|
||||
"mem0-oss-openai": ["OPENAI_API_KEY"],
|
||||
"mem0-oss-anthropic": ["ANTHROPIC_API_KEY"]
|
||||
"openclaw-mem0-oss": ["OPENAI_API_KEY"]
|
||||
},
|
||||
"providerAuthChoices": [
|
||||
{
|
||||
"provider": "mem0",
|
||||
"method": "api-key",
|
||||
"choiceId": "mem0-api-key",
|
||||
"choiceLabel": "Mem0 API key",
|
||||
"choiceHint": "Required for platform mode. Get your key at https://app.mem0.ai/dashboard/api-keys",
|
||||
"groupId": "mem0",
|
||||
"groupLabel": "Mem0",
|
||||
"optionKey": "apiKey",
|
||||
"cliFlag": "--mem0-api-key",
|
||||
"cliOption": "--mem0-api-key <key>",
|
||||
"cliDescription": "Mem0 platform API key"
|
||||
}
|
||||
],
|
||||
"uiHints": {
|
||||
"mode": {
|
||||
"label": "Mode",
|
||||
@@ -31,16 +46,6 @@
|
||||
"placeholder": "default",
|
||||
"help": "User ID for scoping memories"
|
||||
},
|
||||
"orgId": {
|
||||
"label": "Organization ID",
|
||||
"placeholder": "org-...",
|
||||
"advanced": true
|
||||
},
|
||||
"projectId": {
|
||||
"label": "Project ID",
|
||||
"placeholder": "proj-...",
|
||||
"advanced": true
|
||||
},
|
||||
"autoCapture": {
|
||||
"label": "Auto-Capture",
|
||||
"help": "Automatically store conversation context after each agent turn"
|
||||
@@ -64,10 +69,6 @@
|
||||
"advanced": true,
|
||||
"help": "Custom prompt for open-source mode memory extraction."
|
||||
},
|
||||
"enableGraph": {
|
||||
"label": "Enable Graph Memory",
|
||||
"help": "Enable Mem0 graph memory for entity relationships (platform mode only)"
|
||||
},
|
||||
"searchThreshold": {
|
||||
"label": "Search Threshold",
|
||||
"placeholder": "0.5",
|
||||
@@ -109,12 +110,6 @@
|
||||
"userEmail": {
|
||||
"type": "string"
|
||||
},
|
||||
"orgId": {
|
||||
"type": "string"
|
||||
},
|
||||
"projectId": {
|
||||
"type": "string"
|
||||
},
|
||||
"autoCapture": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -133,9 +128,6 @@
|
||||
"customPrompt": {
|
||||
"type": "string"
|
||||
},
|
||||
"enableGraph": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"searchThreshold": {
|
||||
"type": "number"
|
||||
},
|
||||
@@ -191,7 +183,6 @@
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"importanceThreshold": { "type": "number" },
|
||||
"enableGraph": { "type": "boolean" },
|
||||
"credentialPatterns": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/openclaw-mem0",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.6",
|
||||
"type": "module",
|
||||
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source",
|
||||
"license": "Apache-2.0",
|
||||
@@ -42,12 +42,12 @@
|
||||
"./dist/index.js"
|
||||
],
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.3.24-beta.2",
|
||||
"minGatewayVersion": "2026.3.24-beta.2"
|
||||
"pluginApi": ">=2026.3.28",
|
||||
"minGatewayVersion": ">=2026.3.28"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.3.24-beta.2",
|
||||
"pluginSdkVersion": "2026.3.24-beta.2"
|
||||
"openclawVersion": "2026.4.1",
|
||||
"pluginSdkVersion": "2026.4.1"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
+68
-44
@@ -82,8 +82,6 @@ class PlatformProvider implements Mem0Provider {
|
||||
constructor(
|
||||
private readonly apiKey: string,
|
||||
private readonly baseUrl?: string,
|
||||
private readonly orgId?: string,
|
||||
private readonly projectId?: string,
|
||||
) {}
|
||||
|
||||
private async ensureClient(): Promise<void> {
|
||||
@@ -101,14 +99,10 @@ class PlatformProvider implements Mem0Provider {
|
||||
const opts: {
|
||||
apiKey: string;
|
||||
host?: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
} = {
|
||||
apiKey: this.apiKey,
|
||||
};
|
||||
if (this.baseUrl) opts.host = this.baseUrl;
|
||||
if (this.orgId) opts.organizationId = this.orgId;
|
||||
if (this.projectId) opts.projectId = this.projectId;
|
||||
this.client = new MemoryClient(opts);
|
||||
}
|
||||
|
||||
@@ -123,7 +117,6 @@ class PlatformProvider implements Mem0Provider {
|
||||
opts.custom_instructions = options.custom_instructions;
|
||||
if (options.custom_categories)
|
||||
opts.custom_categories = options.custom_categories;
|
||||
if (options.enable_graph) opts.enable_graph = options.enable_graph;
|
||||
if (options.output_format) opts.output_format = options.output_format;
|
||||
if (options.source) opts.source = options.source;
|
||||
// Agentic harness: direct storage bypass
|
||||
@@ -208,9 +201,7 @@ class PlatformProvider implements Mem0Provider {
|
||||
await this.client.deleteAll({ user_id: userId });
|
||||
}
|
||||
|
||||
async history(
|
||||
memoryId: string,
|
||||
): Promise<
|
||||
async history(memoryId: string): Promise<
|
||||
Array<{
|
||||
id: string;
|
||||
old_memory: string;
|
||||
@@ -249,23 +240,29 @@ class OSSProvider implements Mem0Provider {
|
||||
return this.initPromise;
|
||||
}
|
||||
|
||||
private async _init(): Promise<void> {
|
||||
const { Memory } = await import("mem0ai/oss");
|
||||
|
||||
private _buildConfig(disableHistory = false): Record<string, unknown> {
|
||||
const config: Record<string, unknown> = { version: "v1.1" };
|
||||
|
||||
const defaultEmbedder = { provider: "openai", config: { model: "text-embedding-3-small" } };
|
||||
const defaultEmbedder = {
|
||||
provider: "openai",
|
||||
config: { model: "text-embedding-3-small" },
|
||||
};
|
||||
const defaultLlm = { provider: "openai", config: { model: "gpt-5.4" } };
|
||||
|
||||
// Helper: strip empty-string values so they don't clobber defaults
|
||||
const stripEmpty = (obj: Record<string, unknown>) => {
|
||||
const out = { ...obj };
|
||||
for (const k of Object.keys(out)) { if (out[k] === "") delete out[k]; }
|
||||
for (const k of Object.keys(out)) {
|
||||
if (out[k] === "") delete out[k];
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
if (this.ossConfig?.embedder) {
|
||||
const ec = stripEmpty(this.ossConfig.embedder.config ?? {});
|
||||
if (ec.host && !ec.url) {
|
||||
ec.url = ec.host;
|
||||
delete ec.host;
|
||||
}
|
||||
config.embedder = {
|
||||
provider: this.ossConfig.embedder.provider || defaultEmbedder.provider,
|
||||
config: { ...defaultEmbedder.config, ...ec },
|
||||
@@ -276,6 +273,10 @@ class OSSProvider implements Mem0Provider {
|
||||
|
||||
if (this.ossConfig?.llm) {
|
||||
const lc = stripEmpty(this.ossConfig.llm.config ?? {});
|
||||
if (lc.host && !lc.url) {
|
||||
lc.url = lc.host;
|
||||
delete lc.host;
|
||||
}
|
||||
config.llm = {
|
||||
provider: this.ossConfig.llm.provider || defaultLlm.provider,
|
||||
config: { ...defaultLlm.config, ...lc },
|
||||
@@ -285,7 +286,7 @@ class OSSProvider implements Mem0Provider {
|
||||
}
|
||||
|
||||
if (this.ossConfig?.vectorStore)
|
||||
config.vectorStore = this.ossConfig.vectorStore;
|
||||
config.vectorStore = { ...this.ossConfig.vectorStore };
|
||||
|
||||
if (this.ossConfig?.historyDbPath) {
|
||||
const dbPath = this.resolvePath
|
||||
@@ -294,42 +295,61 @@ class OSSProvider implements Mem0Provider {
|
||||
config.historyDbPath = dbPath;
|
||||
}
|
||||
|
||||
if (this.ossConfig?.disableHistory) {
|
||||
if (disableHistory || this.ossConfig?.disableHistory) {
|
||||
config.disableHistory = true;
|
||||
}
|
||||
|
||||
if (this.customPrompt) config.customPrompt = this.customPrompt;
|
||||
return config;
|
||||
}
|
||||
|
||||
private async _init(): Promise<void> {
|
||||
const mod = await import("mem0ai/oss");
|
||||
const Memory = mod.Memory;
|
||||
for (const cls of ["PGVector", "RedisDB", "Qdrant"]) {
|
||||
const VectorCls = (mod as any)[cls];
|
||||
if (!VectorCls || VectorCls.prototype.__patched) continue;
|
||||
const origInit = VectorCls.prototype.initialize;
|
||||
VectorCls.prototype.initialize = function (this: any) {
|
||||
if (!this.config?.embeddingModelDims && this.config?.dimension) {
|
||||
this.config.embeddingModelDims = this.config.dimension;
|
||||
}
|
||||
// Qdrant reads this.dimension directly
|
||||
if (!this.dimension && this.config?.dimension) {
|
||||
this.dimension = this.config.dimension;
|
||||
}
|
||||
// Skip premature constructor call when dimensions unknown
|
||||
const dims = this.config?.embeddingModelDims ?? this.dimension;
|
||||
if (!dims) return Promise.resolve();
|
||||
// Run the real initialize only once
|
||||
if (!this._initializePromise) {
|
||||
this._initializePromise = origInit.call(this);
|
||||
}
|
||||
return this._initializePromise;
|
||||
};
|
||||
VectorCls.prototype.__patched = true;
|
||||
}
|
||||
|
||||
let mem: any;
|
||||
try {
|
||||
this.memory = new Memory(config);
|
||||
mem = new Memory(this._buildConfig());
|
||||
} catch (err) {
|
||||
// If initialization fails (e.g. native SQLite binding resolution under
|
||||
// jiti), retry with history disabled — the history DB is the most common
|
||||
// source of native-binding failures and is not required for core
|
||||
// memory operations.
|
||||
if (!config.disableHistory) {
|
||||
// If constructor fails (e.g. native SQLite binding under jiti/Docker),
|
||||
// retry with a FRESH config that has history disabled.
|
||||
if (!this.ossConfig?.disableHistory) {
|
||||
console.warn(
|
||||
"[mem0] Memory initialization failed, retrying with history disabled:",
|
||||
err instanceof Error ? err.message : err,
|
||||
);
|
||||
config.disableHistory = true;
|
||||
this.memory = new Memory(config);
|
||||
mem = new Memory(this._buildConfig(true));
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Force the SDK's internal auto-initialization to complete now.
|
||||
// Without this, concurrent method calls (e.g. auto-recall + search)
|
||||
// both trigger _autoInitialize() simultaneously, causing PGVector's
|
||||
// pg client to call connect() twice → "Client has already been
|
||||
// connected" crash. (#4638)
|
||||
try {
|
||||
await this.memory.getAll({ userId: "__mem0_warmup__" });
|
||||
} catch {
|
||||
// Warmup errors are non-fatal — the SDK may still work for
|
||||
// subsequent calls once its internal state settles.
|
||||
}
|
||||
await mem.getAll({ userId: "__mem0_warmup__" });
|
||||
|
||||
this.memory = mem;
|
||||
}
|
||||
|
||||
async add(
|
||||
@@ -423,9 +443,7 @@ class OSSProvider implements Mem0Provider {
|
||||
await this.memory.deleteAll({ userId });
|
||||
}
|
||||
|
||||
async history(
|
||||
memoryId: string,
|
||||
): Promise<
|
||||
async history(memoryId: string): Promise<
|
||||
Array<{
|
||||
id: string;
|
||||
old_memory: string;
|
||||
@@ -438,8 +456,12 @@ class OSSProvider implements Mem0Provider {
|
||||
try {
|
||||
const result = await this.memory.history(memoryId);
|
||||
return Array.isArray(result) ? result : [];
|
||||
} catch {
|
||||
// OSS may not support history depending on config
|
||||
} catch (err) {
|
||||
// OSS may not support history depending on config (e.g. disableHistory)
|
||||
console.warn(
|
||||
"[mem0] OSS history() failed:",
|
||||
err instanceof Error ? err.message : err,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -459,7 +481,7 @@ export function createProvider(
|
||||
);
|
||||
}
|
||||
|
||||
return new PlatformProvider(cfg.apiKey!, cfg.baseUrl, cfg.orgId, cfg.projectId);
|
||||
return new PlatformProvider(cfg.apiKey!, cfg.baseUrl);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -484,12 +506,12 @@ export function providerToBackend(
|
||||
msgs as Array<{ role: string; content: string }>,
|
||||
{
|
||||
user_id: opts.userId ?? userId,
|
||||
source: "OPENCLAW",
|
||||
...(opts.runId && { run_id: opts.runId }),
|
||||
...(opts.metadata && { metadata: opts.metadata }),
|
||||
...(opts.immutable && { immutable: true }),
|
||||
...(opts.infer === false && { infer: false }),
|
||||
...(opts.expires && { expiration_date: opts.expires }),
|
||||
...(opts.enableGraph && { enable_graph: true }),
|
||||
},
|
||||
);
|
||||
return result as unknown as Record<string, unknown>;
|
||||
@@ -503,6 +525,7 @@ export function providerToBackend(
|
||||
keyword_search: opts.keyword,
|
||||
reranking: opts.rerank,
|
||||
filters: opts.filters,
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
return results as unknown as Record<string, unknown>[];
|
||||
},
|
||||
@@ -516,6 +539,7 @@ export function providerToBackend(
|
||||
const items = await provider.getAll({
|
||||
user_id: opts.userId ?? userId,
|
||||
page_size: opts.pageSize,
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
return items as unknown as Record<string, unknown>[];
|
||||
},
|
||||
|
||||
@@ -260,6 +260,7 @@ export async function recall(
|
||||
threshold,
|
||||
keyword_search: recallConfig.keywordSearch !== false, // Default on
|
||||
reranking: recallConfig.rerank !== false, // Default on
|
||||
source: "OPENCLAW",
|
||||
};
|
||||
if (recallConfig.filterMemories) {
|
||||
searchOpts.filter_memories = true;
|
||||
|
||||
@@ -7,7 +7,7 @@ description: >
|
||||
Also triggers automatically after sufficient activity (configurable).
|
||||
user-invocable: true
|
||||
metadata:
|
||||
{"openclaw": {"emoji": "💤"}}
|
||||
{"openclaw": {"emoji": "💤", "requires": {"env": ["MEM0_API_KEY"], "bins": []}}}
|
||||
---
|
||||
|
||||
# Memory Consolidation
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
---
|
||||
name: memory-triage
|
||||
description: >
|
||||
ALWAYS USE THIS SKILL. Persistent long-term memory protocol powered by mem0.
|
||||
After EVERY response, evaluate the conversation for durable facts worth storing
|
||||
via memory_add. Handles identity, preferences, decisions, configurations, rules,
|
||||
projects, and relationships. Use memory_add (NOT workspace files) for all user facts.
|
||||
Read this skill at the start of every session.
|
||||
Persistent long-term memory protocol powered by mem0.
|
||||
Evaluate conversations for durable facts worth storing via memory_add.
|
||||
Handles identity, preferences, decisions, configurations, rules,
|
||||
projects, and relationships. Loaded by the openclaw-mem0 plugin when skills mode is active.
|
||||
user-invocable: false
|
||||
metadata:
|
||||
{"openclaw": {"always": true, "emoji": "🧠"}}
|
||||
{"openclaw": {"always": false, "emoji": "🧠", "requires": {"env": ["MEM0_API_KEY"], "bins": []}}}
|
||||
---
|
||||
|
||||
# Memory Protocol
|
||||
|
||||
@@ -4,9 +4,18 @@
|
||||
* 2. initPromise poisoning fix (retry after failure)
|
||||
* 3. Graceful SQLite fallback in OSSProvider
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { mem0ConfigSchema, createProvider } from "./index.ts";
|
||||
|
||||
/** Stub vector-store classes required by OSSProvider._init's patching loop. */
|
||||
function vectorStubs() {
|
||||
return {
|
||||
PGVector: class { initialize() { return Promise.resolve(); } },
|
||||
RedisDB: class { initialize() { return Promise.resolve(); } },
|
||||
Qdrant: class { initialize() { return Promise.resolve(); } },
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1. Config: disableHistory passthrough
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -60,6 +69,7 @@ describe("OSSProvider — disableHistory passthrough to Memory", () => {
|
||||
beforeEach(() => {
|
||||
capturedConfig = undefined;
|
||||
memoryCallCount = 0;
|
||||
vi.resetModules();
|
||||
|
||||
vi.doMock("mem0ai/oss", () => ({
|
||||
Memory: class MockMemory {
|
||||
@@ -81,9 +91,14 @@ describe("OSSProvider — disableHistory passthrough to Memory", () => {
|
||||
}
|
||||
async delete() {}
|
||||
},
|
||||
...vectorStubs(),
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("passes disableHistory: true to Memory when configured", async () => {
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
@@ -130,6 +145,7 @@ describe("OSSProvider — initPromise retry after failure", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
callCount = 0;
|
||||
vi.resetModules();
|
||||
|
||||
vi.doMock("mem0ai/oss", () => ({
|
||||
Memory: class MockMemory {
|
||||
@@ -154,9 +170,14 @@ describe("OSSProvider — initPromise retry after failure", () => {
|
||||
}
|
||||
async delete() {}
|
||||
},
|
||||
...vectorStubs(),
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("retries initialization after a transient failure", async () => {
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
@@ -184,14 +205,21 @@ describe("OSSProvider — initPromise retry after failure", () => {
|
||||
// ---------------------------------------------------------------------------
|
||||
describe("OSSProvider — graceful SQLite fallback", () => {
|
||||
let capturedConfigs: Record<string, unknown>[];
|
||||
/** When set, the mock Memory constructor always throws with this message. */
|
||||
let forceConstructorError: string | null;
|
||||
|
||||
beforeEach(() => {
|
||||
capturedConfigs = [];
|
||||
forceConstructorError = null;
|
||||
vi.resetModules();
|
||||
|
||||
vi.doMock("mem0ai/oss", () => ({
|
||||
Memory: class MockMemory {
|
||||
constructor(config: Record<string, unknown>) {
|
||||
capturedConfigs.push({ ...config });
|
||||
if (forceConstructorError) {
|
||||
throw new Error(forceConstructorError);
|
||||
}
|
||||
if (!config.disableHistory) {
|
||||
throw new Error("Could not locate the bindings file");
|
||||
}
|
||||
@@ -211,9 +239,14 @@ describe("OSSProvider — graceful SQLite fallback", () => {
|
||||
}
|
||||
async delete() {}
|
||||
},
|
||||
...vectorStubs(),
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("retries with disableHistory: true when initial construction fails", async () => {
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const { createProvider } = await import("./index.ts");
|
||||
@@ -242,14 +275,8 @@ describe("OSSProvider — graceful SQLite fallback", () => {
|
||||
});
|
||||
|
||||
it("does not retry when disableHistory is already true", async () => {
|
||||
vi.doMock("mem0ai/oss", () => ({
|
||||
Memory: class MockMemory {
|
||||
constructor(config: Record<string, unknown>) {
|
||||
// Fail even with disableHistory (e.g. vector store issue)
|
||||
throw new Error("vector store connection refused");
|
||||
}
|
||||
},
|
||||
}));
|
||||
// Force the constructor to always throw, regardless of disableHistory
|
||||
forceConstructorError = "vector store connection refused";
|
||||
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
@@ -274,6 +301,7 @@ describe("PlatformProvider — initPromise retry after failure", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
callCount = 0;
|
||||
vi.resetModules();
|
||||
|
||||
vi.doMock("mem0ai", () => ({
|
||||
default: class MockMemoryClient {
|
||||
@@ -300,6 +328,10 @@ describe("PlatformProvider — initPromise retry after failure", () => {
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("retries initialization after a transient failure", async () => {
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
@@ -320,3 +352,349 @@ describe("PlatformProvider — initPromise retry after failure", () => {
|
||||
expect(callCount).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 6. OSSProvider: _buildConfig covers all branches
|
||||
// ---------------------------------------------------------------------------
|
||||
describe("OSSProvider — _buildConfig branch coverage", () => {
|
||||
let capturedConfig: Record<string, unknown> | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
capturedConfig = undefined;
|
||||
vi.resetModules();
|
||||
|
||||
vi.doMock("mem0ai/oss", () => ({
|
||||
Memory: class MockMemory {
|
||||
constructor(config: Record<string, unknown>) {
|
||||
capturedConfig = { ...config };
|
||||
}
|
||||
async search() { return { results: [] }; }
|
||||
async get() { return {}; }
|
||||
async getAll() { return []; }
|
||||
async add() { return { results: [] }; }
|
||||
async delete() {}
|
||||
},
|
||||
...vectorStubs(),
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("builds config with custom embedder, llm, vectorStore, and historyDbPath", async () => {
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
mode: "open-source",
|
||||
oss: {
|
||||
embedder: { provider: "openai", config: { apiKey: "sk-e", model: "text-embedding-3-small" } },
|
||||
llm: { provider: "openai", config: { apiKey: "sk-l", model: "gpt-4" } },
|
||||
vectorStore: { provider: "qdrant", config: { host: "localhost", port: 6333 } },
|
||||
historyDbPath: "/tmp/history.db",
|
||||
disableHistory: true,
|
||||
},
|
||||
});
|
||||
const api = { resolvePath: (p: string) => `/resolved${p}` } as any;
|
||||
const provider = createProvider(cfg, api);
|
||||
|
||||
await provider.search("test", { user_id: "u1" });
|
||||
|
||||
expect(capturedConfig).toBeDefined();
|
||||
expect(capturedConfig!.embedder).toEqual({
|
||||
provider: "openai",
|
||||
config: { model: "text-embedding-3-small", apiKey: "sk-e" },
|
||||
});
|
||||
expect(capturedConfig!.llm).toEqual({
|
||||
provider: "openai",
|
||||
config: expect.objectContaining({ model: "gpt-4", apiKey: "sk-l" }),
|
||||
});
|
||||
expect(capturedConfig!.vectorStore).toEqual({ provider: "qdrant", config: { host: "localhost", port: 6333 } });
|
||||
expect(capturedConfig!.historyDbPath).toBe("/resolved/tmp/history.db");
|
||||
expect(capturedConfig!.disableHistory).toBe(true);
|
||||
});
|
||||
|
||||
it("strips empty-string values from embedder and llm config", async () => {
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
mode: "open-source",
|
||||
oss: {
|
||||
embedder: { provider: "openai", config: { apiKey: "", model: "custom-model" } },
|
||||
llm: { provider: "openai", config: { apiKey: "", model: "" } },
|
||||
disableHistory: true,
|
||||
},
|
||||
});
|
||||
const api = { resolvePath: (p: string) => p } as any;
|
||||
const provider = createProvider(cfg, api);
|
||||
|
||||
await provider.search("test", { user_id: "u1" });
|
||||
|
||||
expect(capturedConfig).toBeDefined();
|
||||
// Empty apiKey should be stripped, leaving only the non-empty model
|
||||
const embedderCfg = (capturedConfig!.embedder as any).config;
|
||||
expect(embedderCfg.apiKey).toBeUndefined();
|
||||
expect(embedderCfg.model).toBe("custom-model");
|
||||
// Both empty keys in llm should be stripped, defaults applied
|
||||
const llmCfg = (capturedConfig!.llm as any).config;
|
||||
expect(llmCfg.apiKey).toBeUndefined();
|
||||
});
|
||||
|
||||
it("falls back to default provider when embedder/llm provider is empty", async () => {
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
mode: "open-source",
|
||||
oss: {
|
||||
embedder: { provider: "", config: { apiKey: "sk-e" } },
|
||||
llm: { provider: "", config: { apiKey: "sk-l" } },
|
||||
disableHistory: true,
|
||||
},
|
||||
});
|
||||
const api = { resolvePath: (p: string) => p } as any;
|
||||
const provider = createProvider(cfg, api);
|
||||
|
||||
await provider.search("test", { user_id: "u1" });
|
||||
|
||||
expect(capturedConfig).toBeDefined();
|
||||
// Empty provider should fall back to "openai" default
|
||||
expect((capturedConfig!.embedder as any).provider).toBe("openai");
|
||||
expect((capturedConfig!.llm as any).provider).toBe("openai");
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 7. OSSProvider: vector store dimension patching
|
||||
// ---------------------------------------------------------------------------
|
||||
describe("OSSProvider — vector store dimension patching", () => {
|
||||
let capturedModule: any;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
|
||||
vi.doMock("mem0ai/oss", () => {
|
||||
const mod = {
|
||||
Memory: class MockMemory {
|
||||
constructor() {}
|
||||
async search() { return { results: [] }; }
|
||||
async get() { return {}; }
|
||||
async getAll() { return []; }
|
||||
async add() { return { results: [] }; }
|
||||
async delete() {}
|
||||
},
|
||||
PGVector: class {
|
||||
config: any;
|
||||
dimension: any;
|
||||
_initializePromise: any;
|
||||
initialize() { return Promise.resolve("pg-initialized"); }
|
||||
},
|
||||
RedisDB: class {
|
||||
config: any;
|
||||
_initializePromise: any;
|
||||
initialize() { return Promise.resolve("redis-initialized"); }
|
||||
},
|
||||
Qdrant: class {
|
||||
config: any;
|
||||
dimension: any;
|
||||
_initializePromise: any;
|
||||
initialize() { return Promise.resolve("qdrant-initialized"); }
|
||||
},
|
||||
};
|
||||
capturedModule = mod;
|
||||
return mod;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
async function triggerInit() {
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
mode: "open-source",
|
||||
oss: { disableHistory: true },
|
||||
});
|
||||
const provider = createProvider(cfg, { resolvePath: (p: string) => p } as any);
|
||||
await provider.search("test", { user_id: "u1" });
|
||||
}
|
||||
|
||||
it("copies config.dimension to embeddingModelDims and this.dimension", async () => {
|
||||
await triggerInit();
|
||||
|
||||
const pg = new capturedModule.PGVector();
|
||||
pg.config = { dimension: 1536 };
|
||||
await pg.initialize();
|
||||
|
||||
expect(pg.config.embeddingModelDims).toBe(1536);
|
||||
expect(pg.dimension).toBe(1536);
|
||||
});
|
||||
|
||||
it("returns resolved promise when no dimensions are known", async () => {
|
||||
await triggerInit();
|
||||
|
||||
const pg = new capturedModule.PGVector();
|
||||
pg.config = {};
|
||||
const result = await pg.initialize();
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("runs original initialize only once via cached promise", async () => {
|
||||
await triggerInit();
|
||||
|
||||
const q = new capturedModule.Qdrant();
|
||||
q.config = { dimension: 768 };
|
||||
|
||||
const first = await q.initialize();
|
||||
const second = await q.initialize();
|
||||
expect(first).toBe("qdrant-initialized");
|
||||
expect(second).toBe("qdrant-initialized");
|
||||
expect(q._initializePromise).toBeDefined();
|
||||
});
|
||||
|
||||
it("skips missing vector store classes without crashing", async () => {
|
||||
// Override with a mock that omits PGVector entirely
|
||||
vi.resetModules();
|
||||
vi.doMock("mem0ai/oss", () => ({
|
||||
Memory: class {
|
||||
constructor() {}
|
||||
async search() { return { results: [] }; }
|
||||
async get() { return {}; }
|
||||
async getAll() { return []; }
|
||||
async add() { return { results: [] }; }
|
||||
async delete() {}
|
||||
},
|
||||
PGVector: undefined, // explicitly absent — tests the !VectorCls guard
|
||||
RedisDB: class { initialize() { return Promise.resolve(); } },
|
||||
Qdrant: class { initialize() { return Promise.resolve(); } },
|
||||
}));
|
||||
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
mode: "open-source",
|
||||
oss: { disableHistory: true },
|
||||
});
|
||||
const provider = createProvider(cfg, { resolvePath: (p: string) => p } as any);
|
||||
|
||||
// Should not throw even though PGVector is missing
|
||||
const results = await provider.search("test", { user_id: "u1" });
|
||||
expect(results).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 8. OSSProvider: history() error handler
|
||||
// ---------------------------------------------------------------------------
|
||||
describe("OSSProvider — history error handling", () => {
|
||||
/** When set, the mock history() throws this value instead of an Error. */
|
||||
let historyThrowValue: unknown;
|
||||
|
||||
beforeEach(() => {
|
||||
historyThrowValue = new Error("history not available");
|
||||
vi.resetModules();
|
||||
|
||||
vi.doMock("mem0ai/oss", () => ({
|
||||
Memory: class MockMemory {
|
||||
constructor() {}
|
||||
async search() { return { results: [] }; }
|
||||
async get() { return {}; }
|
||||
async getAll() { return []; }
|
||||
async add() { return { results: [] }; }
|
||||
async delete() {}
|
||||
async history() { throw historyThrowValue; }
|
||||
},
|
||||
...vectorStubs(),
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("returns empty array and warns when history() throws an Error", async () => {
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
mode: "open-source",
|
||||
oss: { disableHistory: true },
|
||||
});
|
||||
const api = { resolvePath: (p: string) => p } as any;
|
||||
const provider = createProvider(cfg, api);
|
||||
|
||||
await provider.search("test", { user_id: "u1" });
|
||||
|
||||
const result = await provider.history("mem-123");
|
||||
expect(result).toEqual([]);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
"[mem0] OSS history() failed:",
|
||||
"history not available",
|
||||
);
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("handles non-Error thrown values in history()", async () => {
|
||||
historyThrowValue = "raw string error";
|
||||
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
mode: "open-source",
|
||||
oss: { disableHistory: true },
|
||||
});
|
||||
const api = { resolvePath: (p: string) => p } as any;
|
||||
const provider = createProvider(cfg, api);
|
||||
|
||||
await provider.search("test", { user_id: "u1" });
|
||||
|
||||
const result = await provider.history("mem-456");
|
||||
expect(result).toEqual([]);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
"[mem0] OSS history() failed:",
|
||||
"raw string error",
|
||||
);
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 9. OSSProvider: customPrompt passthrough
|
||||
// ---------------------------------------------------------------------------
|
||||
describe("OSSProvider — customPrompt passthrough", () => {
|
||||
let capturedConfig: Record<string, unknown> | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
capturedConfig = undefined;
|
||||
vi.resetModules();
|
||||
|
||||
vi.doMock("mem0ai/oss", () => ({
|
||||
Memory: class MockMemory {
|
||||
constructor(config: Record<string, unknown>) {
|
||||
capturedConfig = { ...config };
|
||||
}
|
||||
async search() { return { results: [] }; }
|
||||
async get() { return {}; }
|
||||
async getAll() { return []; }
|
||||
async add() { return { results: [] }; }
|
||||
async delete() {}
|
||||
},
|
||||
...vectorStubs(),
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("passes customPrompt to Memory config when provided", async () => {
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
mode: "open-source",
|
||||
oss: { disableHistory: true },
|
||||
customPrompt: "Extract only user preferences.",
|
||||
});
|
||||
const api = { resolvePath: (p: string) => p } as any;
|
||||
const provider = createProvider(cfg, api);
|
||||
|
||||
await provider.search("test", { user_id: "u1" });
|
||||
|
||||
expect(capturedConfig).toBeDefined();
|
||||
expect(capturedConfig!.customPrompt).toBe("Extract only user preferences.");
|
||||
});
|
||||
});
|
||||
|
||||
+186
-5
@@ -8,10 +8,10 @@
|
||||
* Disable with: MEM0_TELEMETRY=false
|
||||
*/
|
||||
|
||||
import { createHash } from "node:crypto";
|
||||
import { readPluginAuth } from "./cli/config-file.ts";
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { readPluginAuth, writePluginAuth, getBaseUrl } from "./cli/config-file.ts";
|
||||
|
||||
export const PLUGIN_VERSION = "1.0.4";
|
||||
export const PLUGIN_VERSION = "1.0.6";
|
||||
|
||||
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
|
||||
const POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/";
|
||||
@@ -22,6 +22,133 @@ const FLUSH_THRESHOLD = 10;
|
||||
let eventQueue: Record<string, unknown>[] = [];
|
||||
let flushTimer: ReturnType<typeof setInterval> | undefined;
|
||||
|
||||
let _cachedAnonymousId: string | undefined;
|
||||
let _aliasCheckDone = false;
|
||||
|
||||
/**
|
||||
* Return a persistent per-machine anonymous ID, generating one if needed.
|
||||
*
|
||||
* Stored in ~/.openclaw/openclaw.json under the plugin's `anonymousTelemetryId`
|
||||
* field so repeat sessions on the same machine share one PostHog identity
|
||||
* instead of collapsing into a single shared fallback string. The result is
|
||||
* cached in module memory after the first read so we don't re-touch disk on
|
||||
* every queued event.
|
||||
*/
|
||||
function getOrCreateAnonymousId(): string {
|
||||
if (_cachedAnonymousId) return _cachedAnonymousId;
|
||||
try {
|
||||
const auth = readPluginAuth();
|
||||
if (auth.anonymousTelemetryId) {
|
||||
_cachedAnonymousId = auth.anonymousTelemetryId;
|
||||
return _cachedAnonymousId;
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const newId = `openclaw-anon-${randomUUID().replace(/-/g, "")}`;
|
||||
try {
|
||||
writePluginAuth({ anonymousTelemetryId: newId });
|
||||
} catch {
|
||||
/* ignore — return generated id anyway */
|
||||
}
|
||||
_cachedAnonymousId = newId;
|
||||
return newId;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we just resolved to a real identity but a stored anonymous id exists,
|
||||
* build a one-shot PostHog $identify event so the pre-signup history gets
|
||||
* stitched onto the authenticated profile. Returns null when no aliasing is
|
||||
* needed (already done, or no anon id on disk, or still anonymous).
|
||||
*
|
||||
* Caller is responsible for pushing the returned event onto eventQueue ahead
|
||||
* of the regular event.
|
||||
*/
|
||||
function maybeBuildIdentifyEvent(
|
||||
distinctId: string,
|
||||
): Record<string, unknown> | null {
|
||||
if (_aliasCheckDone) return null;
|
||||
if (!distinctId || distinctId.startsWith("openclaw-anon-")) return null;
|
||||
try {
|
||||
const auth = readPluginAuth();
|
||||
const storedAnon = auth.anonymousTelemetryId;
|
||||
if (!storedAnon) {
|
||||
_aliasCheckDone = true;
|
||||
return null;
|
||||
}
|
||||
const identifyEvent = {
|
||||
event: "$identify",
|
||||
distinct_id: distinctId,
|
||||
properties: {
|
||||
$anon_distinct_id: storedAnon,
|
||||
$lib: "posthog-node",
|
||||
},
|
||||
};
|
||||
try {
|
||||
writePluginAuth({ anonymousTelemetryId: "" });
|
||||
} catch {
|
||||
/* ignore — alias may double-fire next session, harmless */
|
||||
}
|
||||
_aliasCheckDone = true;
|
||||
_cachedAnonymousId = undefined;
|
||||
return identifyEvent;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let _emailResolutionAttempted = false;
|
||||
|
||||
/**
|
||||
* If we have an apiKey but no cached userEmail, do a one-shot /v1/ping/
|
||||
* call to resolve the email and cache it. This runs async as a side-effect;
|
||||
* the current event ships with md5(apiKey) but subsequent events (including
|
||||
* those flushed by the beforeExit handler in the same process) will use
|
||||
* the resolved email.
|
||||
*/
|
||||
function maybeResolveEmail(apiKey: string): void {
|
||||
if (_emailResolutionAttempted) return;
|
||||
_emailResolutionAttempted = true;
|
||||
|
||||
const baseUrl = getBaseUrl().replace(/\/+$/, "");
|
||||
fetch(`${baseUrl}/v1/ping/`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Token ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
signal: AbortSignal.timeout(5_000),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data: any) => {
|
||||
const email = data?.user_email;
|
||||
if (email) {
|
||||
try {
|
||||
writePluginAuth({ userEmail: email });
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
// Upgrade any already-queued events from md5(apiKey) to email
|
||||
const oldId = createHash("md5").update(apiKey).digest("hex");
|
||||
for (const ev of eventQueue) {
|
||||
if (ev.distinct_id === oldId) {
|
||||
ev.distinct_id = email;
|
||||
}
|
||||
// Also upgrade $identify's distinct_id if present
|
||||
if (
|
||||
ev.event === "$identify" &&
|
||||
ev.distinct_id === oldId
|
||||
) {
|
||||
ev.distinct_id = email;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
/* silently swallow — md5(apiKey) is used as fallback */
|
||||
});
|
||||
}
|
||||
|
||||
let _telemetryEnabled: boolean | undefined;
|
||||
function isTelemetryEnabled(): boolean {
|
||||
if (_telemetryEnabled !== undefined) return _telemetryEnabled;
|
||||
@@ -42,7 +169,8 @@ function isTelemetryEnabled(): boolean {
|
||||
/**
|
||||
* Return a stable anonymous identifier for the current user.
|
||||
*
|
||||
* Priority: cached userEmail (from /v1/ping/) > MD5(apiKey) > fallback.
|
||||
* Priority: cached userEmail (from /v1/ping/) > MD5(apiKey) >
|
||||
* persistent per-machine anonymous ID.
|
||||
*/
|
||||
function getDistinctId(apiKey?: string): string {
|
||||
try {
|
||||
@@ -54,7 +182,7 @@ function getDistinctId(apiKey?: string): string {
|
||||
if (apiKey) {
|
||||
return createHash("md5").update(apiKey).digest("hex");
|
||||
}
|
||||
return "anonymous-openclaw";
|
||||
return getOrCreateAnonymousId();
|
||||
}
|
||||
|
||||
function ensureFlushTimer(): void {
|
||||
@@ -65,6 +193,42 @@ function ensureFlushTimer(): void {
|
||||
}
|
||||
}
|
||||
|
||||
let _exitHandlerInstalled = false;
|
||||
|
||||
/**
|
||||
* Install a one-time `beforeExit` handler that drains queued events on
|
||||
* process exit. Without this, short-lived CLI invocations (e.g. one
|
||||
* `openclaw mem0 status` call) exit before the unref'd flushTimer fires
|
||||
* and before FLUSH_THRESHOLD is hit, dropping every queued event silently.
|
||||
*
|
||||
* Returning a Promise from a `beforeExit` handler keeps the event loop
|
||||
* alive until that Promise resolves, so the awaited fetch actually has
|
||||
* time to land at PostHog.
|
||||
*/
|
||||
function ensureExitHandler(): void {
|
||||
if (_exitHandlerInstalled) return;
|
||||
_exitHandlerInstalled = true;
|
||||
process.on("beforeExit", async () => {
|
||||
if (eventQueue.length === 0) return;
|
||||
const batch = eventQueue;
|
||||
eventQueue = [];
|
||||
const body = JSON.stringify({ api_key: POSTHOG_API_KEY, batch });
|
||||
try {
|
||||
await fetch(POSTHOG_HOST, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": String(Buffer.byteLength(body)),
|
||||
},
|
||||
body,
|
||||
signal: AbortSignal.timeout(3_000),
|
||||
});
|
||||
} catch {
|
||||
/* silently swallow */
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function flushEvents(): void {
|
||||
if (eventQueue.length === 0) return;
|
||||
const batch = eventQueue;
|
||||
@@ -97,6 +261,22 @@ export function captureEvent(
|
||||
try {
|
||||
const distinctId = getDistinctId(ctx?.apiKey);
|
||||
|
||||
// If we resolved to md5(apiKey) instead of email, kick off a background
|
||||
// /v1/ping/ to resolve and cache the email. The current event ships with
|
||||
// the hash, but the async resolution upgrades any still-queued events
|
||||
// (including this one) before the beforeExit flush fires.
|
||||
if (ctx?.apiKey && distinctId && !distinctId.includes("@") && !distinctId.startsWith("openclaw-anon-")) {
|
||||
maybeResolveEmail(ctx.apiKey);
|
||||
}
|
||||
|
||||
// First authenticated event after a previous anonymous session: queue a
|
||||
// $identify ahead of the regular event so PostHog merges the anonymous
|
||||
// history onto the authenticated profile in the same batch flush.
|
||||
const identifyEvent = maybeBuildIdentifyEvent(distinctId);
|
||||
if (identifyEvent) {
|
||||
eventQueue.push(identifyEvent);
|
||||
}
|
||||
|
||||
eventQueue.push({
|
||||
event: eventName,
|
||||
distinct_id: distinctId,
|
||||
@@ -115,6 +295,7 @@ export function captureEvent(
|
||||
});
|
||||
|
||||
ensureFlushTimer();
|
||||
ensureExitHandler();
|
||||
|
||||
if (eventQueue.length >= FLUSH_THRESHOLD) {
|
||||
flushEvents();
|
||||
|
||||
@@ -175,7 +175,6 @@ function createMockCfg() {
|
||||
apiKey: "m0-test-key-1234",
|
||||
baseUrl: "https://api.mem0.ai",
|
||||
topK: 5,
|
||||
enableGraph: false,
|
||||
autoCapture: true,
|
||||
autoRecall: true,
|
||||
searchThreshold: 0.5,
|
||||
@@ -967,7 +966,7 @@ describe("registerCliCommands", () => {
|
||||
const configCmd = findCommand(mem0, "config")!;
|
||||
const getCmd = findCommand(configCmd, "get")!;
|
||||
|
||||
getCmd._action!("org_id");
|
||||
getCmd._action!("email");
|
||||
|
||||
expect(consoleSpy.log).toHaveBeenCalledWith("(not set)");
|
||||
});
|
||||
@@ -1033,18 +1032,6 @@ describe("registerCliCommands", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("coerces 'true' to boolean for boolean keys", () => {
|
||||
const { mem0 } = setup();
|
||||
const configCmd = findCommand(mem0, "config")!;
|
||||
const setCmd = findCommand(configCmd, "set")!;
|
||||
|
||||
setCmd._action!("enable_graph", "true");
|
||||
|
||||
expect(writePluginAuth).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ enableGraph: true }),
|
||||
);
|
||||
});
|
||||
|
||||
it("coerces 'false' to boolean false for boolean keys", () => {
|
||||
const { mem0 } = setup();
|
||||
const configCmd = findCommand(mem0, "config")!;
|
||||
@@ -1069,18 +1056,6 @@ describe("registerCliCommands", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("coerces 'yes' to boolean true for boolean keys", () => {
|
||||
const { mem0 } = setup();
|
||||
const configCmd = findCommand(mem0, "config")!;
|
||||
const setCmd = findCommand(configCmd, "set")!;
|
||||
|
||||
setCmd._action!("enable_graph", "yes");
|
||||
|
||||
expect(writePluginAuth).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ enableGraph: true }),
|
||||
);
|
||||
});
|
||||
|
||||
it("coerces integer string for integer keys", () => {
|
||||
const { mem0 } = setup();
|
||||
const configCmd = findCommand(mem0, "config")!;
|
||||
@@ -1412,5 +1387,73 @@ describe("registerCliCommands", () => {
|
||||
expect.stringContaining("Failed to get event"),
|
||||
);
|
||||
});
|
||||
|
||||
it("event list returns early in open-source mode", async () => {
|
||||
const provider = createMockProvider();
|
||||
const cfg = { ...createMockCfg(), mode: "open-source" as const };
|
||||
const mockApi = {
|
||||
registerCli: vi.fn((cb: any) => {
|
||||
const root = createMockCommand("root");
|
||||
cb({ program: root });
|
||||
const mem0 = findCommand(root, "mem0")!;
|
||||
const eventCmd = findCommand(mem0, "event")!;
|
||||
const listCmd = findCommand(eventCmd, "list")!;
|
||||
listCmd._action!();
|
||||
}),
|
||||
logger: { info: vi.fn(), warn: vi.fn() },
|
||||
} as any;
|
||||
|
||||
registerCliCommands(
|
||||
mockApi,
|
||||
null as any,
|
||||
provider as any,
|
||||
cfg as any,
|
||||
vi.fn().mockReturnValue("testuser"),
|
||||
vi.fn((id: string) => `testuser:agent:${id}`),
|
||||
vi.fn().mockReturnValue({ user_id: "testuser", top_k: 5 }),
|
||||
vi.fn().mockReturnValue(undefined),
|
||||
);
|
||||
|
||||
// Wait for async action
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
|
||||
expect(consoleSpy.log).toHaveBeenCalledWith(
|
||||
"Event tracking is only available in platform mode.",
|
||||
);
|
||||
});
|
||||
|
||||
it("event status returns early in open-source mode", async () => {
|
||||
const provider = createMockProvider();
|
||||
const cfg = { ...createMockCfg(), mode: "open-source" as const };
|
||||
const mockApi = {
|
||||
registerCli: vi.fn((cb: any) => {
|
||||
const root = createMockCommand("root");
|
||||
cb({ program: root });
|
||||
const mem0 = findCommand(root, "mem0")!;
|
||||
const eventCmd = findCommand(mem0, "event")!;
|
||||
const statusCmd = findCommand(eventCmd, "status")!;
|
||||
statusCmd._action!("evt-123");
|
||||
}),
|
||||
logger: { info: vi.fn(), warn: vi.fn() },
|
||||
} as any;
|
||||
|
||||
registerCliCommands(
|
||||
mockApi,
|
||||
null as any,
|
||||
provider as any,
|
||||
cfg as any,
|
||||
vi.fn().mockReturnValue("testuser"),
|
||||
vi.fn((id: string) => `testuser:agent:${id}`),
|
||||
vi.fn().mockReturnValue({ user_id: "testuser", top_k: 5 }),
|
||||
vi.fn().mockReturnValue(undefined),
|
||||
);
|
||||
|
||||
// Wait for async action
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
|
||||
expect(consoleSpy.log).toHaveBeenCalledWith(
|
||||
"Event tracking is only available in platform mode.",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -69,10 +69,7 @@ describe("readPluginAuth", () => {
|
||||
apiKey: "sk-test-123",
|
||||
baseUrl: "https://custom.api.com",
|
||||
userId: "user-1",
|
||||
orgId: "org-1",
|
||||
projectId: "proj-1",
|
||||
mode: "platform",
|
||||
enableGraph: true,
|
||||
autoRecall: true,
|
||||
autoCapture: false,
|
||||
topK: 10,
|
||||
@@ -87,17 +84,14 @@ describe("readPluginAuth", () => {
|
||||
apiKey: "sk-test-123",
|
||||
baseUrl: "https://custom.api.com",
|
||||
userId: "user-1",
|
||||
orgId: "org-1",
|
||||
projectId: "proj-1",
|
||||
mode: "platform",
|
||||
enableGraph: true,
|
||||
autoRecall: true,
|
||||
autoCapture: false,
|
||||
topK: 10,
|
||||
});
|
||||
});
|
||||
|
||||
it("handles snake_case aliases (api_key, base_url, user_id, org_id, project_id)", () => {
|
||||
it("handles snake_case aliases (api_key, base_url, user_id)", () => {
|
||||
setConfigFile({
|
||||
plugins: {
|
||||
entries: {
|
||||
@@ -107,8 +101,6 @@ describe("readPluginAuth", () => {
|
||||
api_key: "sk-snake",
|
||||
base_url: "https://snake.api.com",
|
||||
user_id: "user-snake",
|
||||
org_id: "org-snake",
|
||||
project_id: "proj-snake",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -119,8 +111,6 @@ describe("readPluginAuth", () => {
|
||||
expect(auth.apiKey).toBe("sk-snake");
|
||||
expect(auth.baseUrl).toBe("https://snake.api.com");
|
||||
expect(auth.userId).toBe("user-snake");
|
||||
expect(auth.orgId).toBe("org-snake");
|
||||
expect(auth.projectId).toBe("proj-snake");
|
||||
});
|
||||
|
||||
it("returns empty object when JSON is invalid", () => {
|
||||
|
||||
@@ -64,11 +64,6 @@ describe("mem0ConfigSchema.parse() — defaults", () => {
|
||||
expect(cfg.searchThreshold).toBe(0.5);
|
||||
});
|
||||
|
||||
it("enableGraph defaults to false", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ apiKey: "test-key" });
|
||||
expect(cfg.enableGraph).toBe(false);
|
||||
});
|
||||
|
||||
it("customInstructions defaults to DEFAULT_CUSTOM_INSTRUCTIONS", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ apiKey: "test-key" });
|
||||
expect(cfg.customInstructions).toBe(DEFAULT_CUSTOM_INSTRUCTIONS);
|
||||
@@ -274,14 +269,6 @@ describe("mem0ConfigSchema.parse() — explicit overrides", () => {
|
||||
expect(cfg.autoRecall).toBe(false);
|
||||
});
|
||||
|
||||
it("enableGraph can be set to true", () => {
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
apiKey: "k",
|
||||
enableGraph: true,
|
||||
});
|
||||
expect(cfg.enableGraph).toBe(true);
|
||||
});
|
||||
|
||||
it("custom topK is used when provided", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ apiKey: "k", topK: 20 });
|
||||
expect(cfg.topK).toBe(20);
|
||||
@@ -330,18 +317,6 @@ describe("mem0ConfigSchema.parse() — explicit overrides", () => {
|
||||
expect(cfg.baseUrl).toBe("https://custom.api.com");
|
||||
});
|
||||
|
||||
it("orgId is passed through when provided", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ apiKey: "k", orgId: "org-123" });
|
||||
expect(cfg.orgId).toBe("org-123");
|
||||
});
|
||||
|
||||
it("projectId is passed through when provided", () => {
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
apiKey: "k",
|
||||
projectId: "proj-456",
|
||||
});
|
||||
expect(cfg.projectId).toBe("proj-456");
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -359,22 +334,23 @@ describe("mem0ConfigSchema.parse() — oss config", () => {
|
||||
historyDbPath: "/tmp/history.db",
|
||||
disableHistory: false,
|
||||
};
|
||||
const cfg = mem0ConfigSchema.parse({ mode: "oss", oss: ossConfig });
|
||||
const cfg = mem0ConfigSchema.parse({ mode: "open-source", oss: ossConfig });
|
||||
expect(cfg.mode).toBe("open-source");
|
||||
expect(cfg.oss).toEqual(ossConfig);
|
||||
});
|
||||
|
||||
it("ignores oss when it is not a plain object", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ mode: "oss", oss: "not-an-object" });
|
||||
const cfg = mem0ConfigSchema.parse({ mode: "open-source", oss: "not-an-object" });
|
||||
expect(cfg.oss).toBeUndefined();
|
||||
});
|
||||
|
||||
it("ignores oss when it is an array", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ mode: "oss", oss: [1, 2, 3] });
|
||||
const cfg = mem0ConfigSchema.parse({ mode: "open-source", oss: [1, 2, 3] });
|
||||
expect(cfg.oss).toBeUndefined();
|
||||
});
|
||||
|
||||
it("ignores oss when it is null", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ mode: "oss", oss: null });
|
||||
const cfg = mem0ConfigSchema.parse({ mode: "open-source", oss: null });
|
||||
expect(cfg.oss).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -388,7 +364,6 @@ describe("mem0ConfigSchema.parse() — skills config", () => {
|
||||
triage: {
|
||||
enabled: true,
|
||||
importanceThreshold: 3,
|
||||
enableGraph: false,
|
||||
credentialPatterns: ["sk-", "ghp_"],
|
||||
},
|
||||
recall: {
|
||||
|
||||
@@ -61,6 +61,7 @@ describe("providerToBackend — search", () => {
|
||||
keyword_search: true,
|
||||
reranking: true,
|
||||
filters: { category: "preference" },
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
expect(results).toHaveLength(1);
|
||||
expect((results[0] as any).id).toBe("m1");
|
||||
@@ -79,6 +80,7 @@ describe("providerToBackend — search", () => {
|
||||
keyword_search: undefined,
|
||||
reranking: undefined,
|
||||
filters: undefined,
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -127,7 +129,6 @@ describe("providerToBackend — add", () => {
|
||||
immutable: true,
|
||||
infer: false,
|
||||
expires: "2027-01-01",
|
||||
enableGraph: true,
|
||||
});
|
||||
|
||||
expect(provider.add).toHaveBeenCalledWith(
|
||||
@@ -139,7 +140,6 @@ describe("providerToBackend — add", () => {
|
||||
immutable: true,
|
||||
infer: false,
|
||||
expiration_date: "2027-01-01",
|
||||
enable_graph: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -175,6 +175,7 @@ describe("providerToBackend — listMemories", () => {
|
||||
expect(provider.getAll).toHaveBeenCalledWith({
|
||||
user_id: DEFAULT_USER,
|
||||
page_size: 50,
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
expect(results).toHaveLength(1);
|
||||
});
|
||||
@@ -188,6 +189,7 @@ describe("providerToBackend — listMemories", () => {
|
||||
expect(provider.getAll).toHaveBeenCalledWith({
|
||||
user_id: DEFAULT_USER,
|
||||
page_size: undefined,
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ describe("telemetry", () => {
|
||||
});
|
||||
|
||||
it("exports PLUGIN_VERSION", () => {
|
||||
expect(PLUGIN_VERSION).toBe("1.0.4");
|
||||
expect(PLUGIN_VERSION).toBe("1.0.6");
|
||||
});
|
||||
|
||||
it("captureEvent does not throw", () => {
|
||||
@@ -51,7 +51,7 @@ describe("telemetry", () => {
|
||||
expect(() => captureEvent("test_event")).not.toThrow();
|
||||
});
|
||||
|
||||
it("falls back to anonymous-openclaw when no apiKey", () => {
|
||||
it("falls back to a generated anonymous id when no apiKey", () => {
|
||||
(readPluginAuth as ReturnType<typeof vi.fn>).mockReturnValueOnce({});
|
||||
expect(() => captureEvent("test_event", {}, {})).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -32,7 +32,6 @@ function createMockToolDeps(overrides = {}): ToolDeps {
|
||||
mode: "platform",
|
||||
userId: "testuser",
|
||||
topK: 5,
|
||||
enableGraph: false,
|
||||
autoCapture: true,
|
||||
autoRecall: true,
|
||||
searchThreshold: 0.5,
|
||||
|
||||
@@ -67,7 +67,6 @@ export function createMemoryAddTool(deps: ToolDeps) {
|
||||
if (runId) addOpts.run_id = runId;
|
||||
if (cfg.mode === "platform") {
|
||||
addOpts.output_format = "v1.1";
|
||||
if (cfg.enableGraph || cfg.skills?.triage?.enableGraph) addOpts.enable_graph = true;
|
||||
}
|
||||
|
||||
const result = await provider.add([{ role: "user", content: allFacts.join("\n") }], addOpts);
|
||||
|
||||
@@ -9,11 +9,8 @@ export type Mem0Config = {
|
||||
// Platform-specific
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
orgId?: string;
|
||||
projectId?: string;
|
||||
customInstructions: string;
|
||||
customCategories: Record<string, string>;
|
||||
enableGraph: boolean;
|
||||
// OSS-specific
|
||||
customPrompt?: string;
|
||||
oss?: {
|
||||
@@ -40,7 +37,6 @@ export interface AddOptions {
|
||||
run_id?: string;
|
||||
custom_instructions?: string;
|
||||
custom_categories?: Array<Record<string, string>>;
|
||||
enable_graph?: boolean;
|
||||
output_format?: string;
|
||||
source?: string;
|
||||
// Agentic harness additions
|
||||
@@ -79,7 +75,6 @@ export interface SkillsConfig {
|
||||
triage?: {
|
||||
enabled?: boolean;
|
||||
importanceThreshold?: number;
|
||||
enableGraph?: boolean;
|
||||
credentialPatterns?: string[];
|
||||
};
|
||||
recall?: {
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ dependencies = [
|
||||
"qdrant-client>=1.9.1",
|
||||
"pydantic>=2.7.3",
|
||||
"openai>=1.90.0",
|
||||
"posthog>=3.5.0",
|
||||
"posthog>=4.5.0",
|
||||
"pytz>=2024.1",
|
||||
"sqlalchemy>=2.0.31",
|
||||
"protobuf>=5.29.6,<7.0.0",
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but not
|
||||
limited to compiled object code, generated documentation, and
|
||||
conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work.
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2024 Mem0.ai
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Mem0 CLI Skill for Claude
|
||||
|
||||
Manage memories from the terminal using the [Mem0 CLI](https://docs.mem0.ai/cli). This skill teaches Claude how to use every `mem0` command, flag, and output mode -- for both the Node.js and Python implementations.
|
||||
|
||||
## What This Skill Does
|
||||
|
||||
When installed, Claude can:
|
||||
|
||||
- **Run mem0 commands** correctly in your terminal (add, search, list, get, update, delete, import, config, init, status, entity, event)
|
||||
- **Construct complex invocations** with the right flags, scoping, filters, and output formats
|
||||
- **Pipe and script** mem0 commands in shell workflows, CI/CD pipelines, and agent loops
|
||||
- **Debug issues** like missing API keys, entity scoping conflicts, and async processing delays
|
||||
|
||||
## Installation
|
||||
|
||||
### CLI (Claude Code, OpenCode, OpenClaw, or any tool that supports skills)
|
||||
|
||||
```bash
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-cli
|
||||
```
|
||||
|
||||
### Claude.ai
|
||||
|
||||
1. Download this `skills/mem0-cli` folder as a ZIP
|
||||
2. Go to **Settings > Capabilities > Skills**
|
||||
3. Click **Upload skill** and select the ZIP
|
||||
|
||||
### Claude API (Skills API)
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.anthropic.com/v1/skills \
|
||||
-H "x-api-key: $ANTHROPIC_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "mem0-cli", "source": "https://github.com/mem0ai/mem0/tree/main/skills/mem0-cli"}'
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A Mem0 Platform API key ([Get one here](https://app.mem0.ai/dashboard/api-keys))
|
||||
- **Node.js 18+** or **Python 3.10+**
|
||||
- Install the CLI:
|
||||
|
||||
```bash
|
||||
# Node.js
|
||||
npm install -g @mem0/cli
|
||||
|
||||
# Python
|
||||
pip install mem0-cli
|
||||
```
|
||||
|
||||
- Set the environment variable:
|
||||
|
||||
```bash
|
||||
export MEM0_API_KEY="m0-your-api-key"
|
||||
```
|
||||
|
||||
Or run `mem0 init` for the interactive setup wizard.
|
||||
|
||||
## Quick Start
|
||||
|
||||
After installing, just ask Claude:
|
||||
|
||||
- "Add a memory for user alice that she prefers dark mode"
|
||||
- "Search alice's memories for dietary preferences"
|
||||
- "List all memories and output as JSON"
|
||||
- "Delete all memories for user bob"
|
||||
- "Set up mem0 CLI in my CI pipeline"
|
||||
- "Pipe the output of my script into mem0 add"
|
||||
|
||||
## What's Inside
|
||||
|
||||
```text
|
||||
skills/mem0-cli/
|
||||
├── SKILL.md # Skill definition and instructions
|
||||
├── README.md # This file
|
||||
├── LICENSE # Apache-2.0
|
||||
└── references/ # Documentation (loaded on demand)
|
||||
├── command-reference.md # Every command, flag, option, and example
|
||||
├── configuration.md # Config file, env vars, precedence, init wizard
|
||||
└── workflows.md # Piping, scripting, CI/CD, agent mode recipes
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
- [Mem0 Platform Dashboard](https://app.mem0.ai)
|
||||
- [Mem0 Documentation](https://docs.mem0.ai)
|
||||
- [Mem0 CLI Docs](https://docs.mem0.ai/cli)
|
||||
- [Mem0 GitHub](https://github.com/mem0ai/mem0)
|
||||
|
||||
## Skill Graph
|
||||
|
||||
This skill is part of the **Mem0 skill graph** -- three interconnected skills for different interfaces to the Mem0 platform:
|
||||
|
||||
| Skill | Purpose | Link |
|
||||
|-------|---------|------|
|
||||
| **mem0** | Python/TypeScript SDK, REST API, framework integrations | [local](../mem0/SKILL.md) / [GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0) |
|
||||
| **mem0-cli** (this skill) | Terminal commands for memory operations | [local](./SKILL.md) / [GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0-cli) |
|
||||
| **mem0-vercel-ai-sdk** | Vercel AI SDK provider with automatic memory | [local](../mem0-vercel-ai-sdk/SKILL.md) / [GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk) |
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
@@ -0,0 +1,154 @@
|
||||
---
|
||||
name: mem0-cli
|
||||
description: >
|
||||
Mem0 CLI -- the command-line interface for mem0 memory operations.
|
||||
TRIGGER when: user mentions "mem0 cli", "mem0 command line", "@mem0/cli",
|
||||
"mem0-cli", "pip install mem0-cli", "npm install -g @mem0/cli", or is running
|
||||
mem0 commands in a terminal/shell (mem0 add, mem0 search, mem0 list, mem0 get,
|
||||
mem0 init, mem0 config, mem0 import). Also triggers when query includes CLI flags
|
||||
like --user-id, --output, --json, --agent, or describes bash/zsh/terminal/shell usage.
|
||||
DO NOT TRIGGER when: user asks about programmatic SDK integration in Python/TS
|
||||
code (use mem0 skill), or Vercel AI SDK provider (use mem0-vercel-ai-sdk skill).
|
||||
license: Apache-2.0
|
||||
metadata:
|
||||
author: mem0ai
|
||||
version: "1.0.0"
|
||||
category: ai-memory
|
||||
tags: "cli, terminal, memory, ai, command-line"
|
||||
compatibility: Node.js 18+ (npm install -g @mem0/cli) or Python 3.10+ (pip install mem0-cli), MEM0_API_KEY env var
|
||||
---
|
||||
|
||||
# Mem0 CLI
|
||||
|
||||
The official command-line interface for the Mem0 memory platform. Add, search, list, update, and delete memories from the terminal -- for developers, AI agents, and CI/CD pipelines.
|
||||
|
||||
## Install
|
||||
|
||||
**Node.js (npm):**
|
||||
```bash
|
||||
npm install -g @mem0/cli
|
||||
```
|
||||
|
||||
**Python (pip):**
|
||||
```bash
|
||||
pip install mem0-cli
|
||||
```
|
||||
|
||||
Both packages install a `mem0` binary with identical commands, options, and output formats.
|
||||
|
||||
## Setup
|
||||
|
||||
**Interactive wizard:**
|
||||
```bash
|
||||
mem0 init
|
||||
```
|
||||
|
||||
**Or set the environment variable directly:**
|
||||
```bash
|
||||
export MEM0_API_KEY="m0-xxx"
|
||||
```
|
||||
|
||||
Get an API key at: https://app.mem0.ai/dashboard/api-keys
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Add a memory
|
||||
```bash
|
||||
mem0 add "I prefer dark mode" --user-id alice
|
||||
```
|
||||
|
||||
### Search memories
|
||||
```bash
|
||||
mem0 search "preferences" --user-id alice
|
||||
```
|
||||
|
||||
### List all memories for a user
|
||||
```bash
|
||||
mem0 list --user-id alice
|
||||
```
|
||||
|
||||
### Get a specific memory
|
||||
```bash
|
||||
mem0 get <memory-id>
|
||||
```
|
||||
|
||||
### Update a memory
|
||||
```bash
|
||||
mem0 update <memory-id> "new text"
|
||||
```
|
||||
|
||||
### Delete a single memory
|
||||
```bash
|
||||
mem0 delete <memory-id>
|
||||
```
|
||||
|
||||
### Delete all memories for a user
|
||||
```bash
|
||||
mem0 delete --all --user-id alice --force
|
||||
```
|
||||
|
||||
## Agent / JSON Mode
|
||||
|
||||
Use `--json` or `--agent` to get structured output suitable for LLM consumption. Every command wraps its response in a standard envelope:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"command": "search",
|
||||
"duration_ms": 245,
|
||||
"scope": { "user_id": "alice" },
|
||||
"count": 3,
|
||||
"error": null,
|
||||
"data": [
|
||||
{ "id": "mem-abc", "memory": "User prefers dark mode", "score": 0.92 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
On error:
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"command": "search",
|
||||
"error": "Authentication failed. Your API key may be invalid or expired.",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
The `--agent` flag is an alias for `--json`. Both write spinners and progress to stderr so stdout is always clean, parseable JSON.
|
||||
|
||||
## Node and Python Parity
|
||||
|
||||
Both the Node.js (`@mem0/cli`) and Python (`mem0-cli`) CLIs are implemented from the same specification (`cli-spec.json`). They share:
|
||||
|
||||
- Identical command names, arguments, and flags
|
||||
- Identical output formats (text, json, table, quiet)
|
||||
- Identical entity ID resolution, graph tri-state, filter building
|
||||
- Identical error messages and exit codes
|
||||
|
||||
Choose whichever runtime you already have installed. The behavior is the same.
|
||||
|
||||
## Common Edge Cases
|
||||
|
||||
- **Async processing delay:** After `mem0 add`, memories process asynchronously. Wait 2-3 seconds before searching for newly added content. Use `mem0 event list` to check processing status.
|
||||
- **`--all` vs `--entity` delete modes:** `mem0 delete --all -u alice` deletes all memories for user alice. `mem0 delete --entity -u alice` deletes the entity itself AND all its memories (cascade). These are mutually exclusive modes.
|
||||
- **Entity ID resolution:** If you pass any explicit scope flag (e.g. `--user-id`), the CLI uses ONLY the explicit IDs and ignores config defaults. If no scope flags are given, all configured defaults apply.
|
||||
- **Stdin detection:** When no text argument is provided and input is piped (not a TTY), the CLI reads from stdin. Works with `add`, `search`, and `update`.
|
||||
- **Graph tri-state:** `--no-graph` takes precedence over `--graph`, which takes precedence over the config default (`defaults.enable_graph`).
|
||||
|
||||
## References
|
||||
|
||||
Load these on demand for deeper detail:
|
||||
|
||||
| Topic | File |
|
||||
|-------|------|
|
||||
| Command reference (all commands, flags, options, examples) | [references/command-reference.md](references/command-reference.md) |
|
||||
| Configuration (config file, env vars, precedence, init wizard) | [references/configuration.md](references/configuration.md) |
|
||||
| Workflows (piping, scripting, CI/CD, agent mode recipes) | [references/workflows.md](references/workflows.md) |
|
||||
|
||||
## Related Mem0 Skills
|
||||
|
||||
| Skill | When to use | Link |
|
||||
|-------|-------------|------|
|
||||
| mem0 | Python/TypeScript SDK, REST API, framework integrations | [local](../mem0/SKILL.md) / [GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0) |
|
||||
| mem0-vercel-ai-sdk | Vercel AI SDK provider with automatic memory | [local](../mem0-vercel-ai-sdk/SKILL.md) / [GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk) |
|
||||
@@ -0,0 +1,690 @@
|
||||
# Mem0 CLI Command Reference
|
||||
|
||||
Complete reference for every command, argument, flag, and output mode in the mem0 CLI. Both the Node.js (`@mem0/cli`) and Python (`mem0-cli`) implementations are identical in behavior.
|
||||
|
||||
---
|
||||
|
||||
## Global Options
|
||||
|
||||
These options are available on every command:
|
||||
|
||||
| Flag | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `--json` / `--agent` | boolean | Agent mode: wrap all output in a structured JSON envelope on stdout. Spinners and progress go to stderr. |
|
||||
| `-o, --output <format>` | string | Output format. Supported values vary per command (see matrix below). |
|
||||
| `--api-key <key>` | string | Override the API key for this invocation. Takes precedence over env var and config file. |
|
||||
| `--base-url <url>` | string | Override the API base URL (default: `https://api.mem0.ai`). |
|
||||
| `--version` | boolean | Print version and exit. |
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### `mem0 init`
|
||||
|
||||
Interactive setup wizard. Configures API key and default user ID.
|
||||
|
||||
**Usage:** `mem0 init [OPTIONS]`
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `--api-key <key>` | string | - | API key (skip interactive prompt). |
|
||||
| `-u, --user-id <id>` | string | - | Default user ID (skip interactive prompt). |
|
||||
| `--email <addr>` | string | - | Login via email verification code instead of API key. |
|
||||
| `--code <code>` | string | - | Verification code (use with `--email` for fully non-interactive login). |
|
||||
| `--force` | boolean | false | Overwrite existing config without confirmation. |
|
||||
|
||||
**Behavior:**
|
||||
|
||||
- If `~/.mem0/config.json` already exists with an API key, warns and asks for confirmation (or errors in non-TTY unless `--force` is set).
|
||||
- **Email login flow** (`--email`): sends a 6-digit code to the email via `POST /api/v1/auth/email_code/`. If `--code` is also given, verifies immediately. On success, saves API key, org_id, and project_id. Cannot be combined with `--api-key`.
|
||||
- **API key flow**: if both `--api-key` and `--user-id` are given, runs fully non-interactively. Otherwise prompts for missing values.
|
||||
- In non-TTY without sufficient flags, prints a usage hint and exits with error.
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 init
|
||||
mem0 init --api-key m0-xxx --user-id alice
|
||||
mem0 init --api-key m0-xxx --user-id alice --force
|
||||
mem0 init --email alice@company.com
|
||||
mem0 init --email alice@company.com --code 482901
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 add`
|
||||
|
||||
Add a memory from text, messages, file, or stdin.
|
||||
|
||||
**Usage:** `mem0 add [text] [OPTIONS]`
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `text` | string | No | Text content to add as a memory. |
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `-u, --user-id <id>` | string | - | Scope to user. |
|
||||
| `--agent-id <id>` | string | - | Scope to agent. |
|
||||
| `--app-id <id>` | string | - | Scope to app. |
|
||||
| `--run-id <id>` | string | - | Scope to run. |
|
||||
| `--messages <json>` | string | - | Conversation messages as JSON array (e.g. `'[{"role":"user","content":"..."}]'`). |
|
||||
| `-f, --file <path>` | path | - | Read messages from a JSON file. |
|
||||
| `-m, --metadata <json>` | string | - | Custom metadata as JSON object (e.g. `'{"source":"cli"}'`). |
|
||||
| `--immutable` | boolean | false | Prevent future updates to this memory. |
|
||||
| `--no-infer` | boolean | false | Skip inference; store the text verbatim. |
|
||||
| `--expires <date>` | string | - | Expiration date in `YYYY-MM-DD` format. |
|
||||
| `--categories <cats>` | string | - | Categories as JSON array or comma-separated string. |
|
||||
| `--graph` | boolean | false | Enable graph memory extraction for this call. |
|
||||
| `--no-graph` | boolean | false | Disable graph memory extraction for this call. |
|
||||
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`, `quiet`. |
|
||||
|
||||
**Input priority:** `--file` > `--messages` > text argument > stdin (if piped and no text).
|
||||
|
||||
Text content is wrapped as `[{"role": "user", "content": "<text>"}]` before sending to the API. Messages from `--messages` or `--file` are sent as-is.
|
||||
|
||||
**Output events:** The API returns results with an `event` field per memory:
|
||||
|
||||
| Event | Meaning |
|
||||
|-------|---------|
|
||||
| `ADD` | New memory created |
|
||||
| `UPDATE` | Existing memory updated (deduplication) |
|
||||
| `DELETE` | Existing memory removed (contradiction) |
|
||||
| `NOOP` | No change needed |
|
||||
| `PENDING` | Processing asynchronously in background |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 add "I prefer dark mode" --user-id alice
|
||||
mem0 add "allergic to nuts" -u alice -m '{"source":"onboarding"}'
|
||||
mem0 add --messages '[{"role":"user","content":"I like Python"}]' -u alice
|
||||
mem0 add --file conversation.json -u alice -o json
|
||||
echo "I prefer dark mode" | mem0 add -u alice
|
||||
mem0 add "temporary note" -u alice --expires 2025-12-31
|
||||
mem0 add "important fact" -u alice --immutable
|
||||
mem0 add "uses vim" -u alice --categories "tools,preferences"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 search`
|
||||
|
||||
Search memories by semantic query.
|
||||
|
||||
**Usage:** `mem0 search <query> [OPTIONS]`
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `query` | string | Yes | The search query. Falls back to stdin if piped. |
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `-u, --user-id <id>` | string | - | Filter by user. |
|
||||
| `--agent-id <id>` | string | - | Filter by agent. |
|
||||
| `--app-id <id>` | string | - | Filter by app. |
|
||||
| `--run-id <id>` | string | - | Filter by run. |
|
||||
| `-k, --top-k, --limit <n>` | integer | 10 | Maximum number of results to return. |
|
||||
| `--threshold <score>` | float | 0.3 | Minimum similarity score (0.0 to 1.0). |
|
||||
| `--rerank` | boolean | false | Enable reranking for improved relevance (Platform only). |
|
||||
| `--keyword` | boolean | false | Use keyword search instead of semantic. |
|
||||
| `--filter <json>` | string | - | Advanced filter expression as JSON (AND/OR operators). |
|
||||
| `--fields <list>` | string | - | Comma-separated list of fields to return. |
|
||||
| `--graph` | boolean | false | Enable graph in search. |
|
||||
| `--no-graph` | boolean | false | Disable graph in search. |
|
||||
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`, `table`. |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 search "preferences" --user-id alice
|
||||
mem0 search "tools" -u alice -o json -k 5
|
||||
mem0 search "dietary restrictions" -u alice --threshold 0.5
|
||||
mem0 search "project setup" -u alice --rerank
|
||||
mem0 search "preferences" -u alice --filter '{"categories":{"contains":"food"}}'
|
||||
echo "preferences" | mem0 search -u alice
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 get`
|
||||
|
||||
Get a specific memory by ID.
|
||||
|
||||
**Usage:** `mem0 get <memory_id> [OPTIONS]`
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `memory_id` | string | Yes | The UUID of the memory to retrieve. |
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`. |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 get abc-123-def-456
|
||||
mem0 get abc-123-def-456 -o json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 list`
|
||||
|
||||
List memories with optional filters and pagination.
|
||||
|
||||
**Usage:** `mem0 list [OPTIONS]`
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `-u, --user-id <id>` | string | - | Filter by user. |
|
||||
| `--agent-id <id>` | string | - | Filter by agent. |
|
||||
| `--app-id <id>` | string | - | Filter by app. |
|
||||
| `--run-id <id>` | string | - | Filter by run. |
|
||||
| `--page <n>` | integer | 1 | Page number. |
|
||||
| `--page-size <n>` | integer | 100 | Results per page. |
|
||||
| `--category <name>` | string | - | Filter by category. |
|
||||
| `--after <date>` | string | - | Created after (YYYY-MM-DD). |
|
||||
| `--before <date>` | string | - | Created before (YYYY-MM-DD). |
|
||||
| `--graph` | boolean | false | Enable graph in listing. |
|
||||
| `--no-graph` | boolean | false | Disable graph in listing. |
|
||||
| `-o, --output <fmt>` | string | `table` | Output format: `text`, `json`, `table`. |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 list -u alice
|
||||
mem0 list --category prefs --after 2024-01-01 -o json
|
||||
mem0 list -u alice --page 2 --page-size 50
|
||||
mem0 list --before 2024-06-01 -o table
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 update`
|
||||
|
||||
Update a memory's text or metadata.
|
||||
|
||||
**Usage:** `mem0 update <memory_id> [text] [OPTIONS]`
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `memory_id` | string | Yes | The UUID of the memory to update. |
|
||||
| `text` | string | No | New memory text. Falls back to stdin if piped and no `--metadata`. |
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `-m, --metadata <json>` | string | - | Update metadata as JSON object. |
|
||||
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`, `quiet`. |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 update abc-123 "new text"
|
||||
mem0 update abc-123 --metadata '{"priority":"high"}'
|
||||
mem0 update abc-123 "new text" -m '{"priority":"high"}'
|
||||
echo "new text" | mem0 update abc-123
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 delete`
|
||||
|
||||
Delete a memory, all memories matching a scope, or an entity. This command has three mutually exclusive modes.
|
||||
|
||||
**Usage:** `mem0 delete [memory_id] [OPTIONS]`
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `memory_id` | string | No | Memory ID to delete (omit when using `--all` or `--entity`). |
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `--all` | boolean | false | Delete all memories matching scope filters. |
|
||||
| `--entity` | boolean | false | Delete the entity itself and all its memories (cascade). |
|
||||
| `--project` | boolean | false | With `--all`: delete ALL memories project-wide (sends wildcard IDs). |
|
||||
| `--dry-run` | boolean | false | Show what would be deleted without actually deleting. |
|
||||
| `--force` | boolean | false | Skip confirmation prompt. |
|
||||
| `-u, --user-id <id>` | string | - | Scope to user. |
|
||||
| `--agent-id <id>` | string | - | Scope to agent. |
|
||||
| `--app-id <id>` | string | - | Scope to app. |
|
||||
| `--run-id <id>` | string | - | Scope to run. |
|
||||
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`, `quiet`. |
|
||||
|
||||
**Three modes (mutually exclusive):**
|
||||
|
||||
1. **Single memory:** `mem0 delete <memory_id>` -- deletes one memory by its UUID.
|
||||
2. **Bulk delete:** `mem0 delete --all [scope flags]` -- deletes all memories matching the scope. Add `--project` to wipe all memories project-wide (sends wildcard `*` entity IDs).
|
||||
3. **Entity cascade:** `mem0 delete --entity [scope flags]` -- deletes the entity itself AND all its memories.
|
||||
|
||||
You cannot combine `<memory_id>` with `--all` or `--entity`, and you cannot combine `--all` with `--entity`. If none of these are provided, the command prints a usage hint and exits with an error.
|
||||
|
||||
**Dry-run behavior:**
|
||||
- Single: fetches the memory, displays it, prints "No changes made."
|
||||
- `--all`: lists matching memories with count, prints "No changes made."
|
||||
- `--entity`: shows the affected scope without deleting.
|
||||
|
||||
**Confirmation:** Without `--force`, all destructive modes prompt `[y/N]`. With `--all --project`, the prompt explicitly warns about project-wide deletion.
|
||||
|
||||
**`--all --project` behavior:** Sends `DELETE /v1/memories/` with `user_id=*&agent_id=*&app_id=*&run_id=*`. The API returns an async response. The CLI prints "Deletion started. Memories will be removed in the background."
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 delete abc-123-def-456
|
||||
mem0 delete --all -u alice --force
|
||||
mem0 delete --all --project --force
|
||||
mem0 delete --entity -u alice --force
|
||||
mem0 delete abc-123 --dry-run
|
||||
mem0 delete --all -u alice --dry-run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 import`
|
||||
|
||||
Import memories from a JSON file.
|
||||
|
||||
**Usage:** `mem0 import <file_path> [OPTIONS]`
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `file_path` | string | Yes | Path to a JSON file containing memories. |
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `-u, --user-id <id>` | string | - | Override user ID for all imported items. |
|
||||
| `--agent-id <id>` | string | - | Override agent ID for all imported items. |
|
||||
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`. |
|
||||
|
||||
**File format:** A JSON array (or single object) where each item has a `memory`, `text`, or `content` field for the text, plus optional `user_id`, `agent_id`, and `metadata` fields. CLI-provided `--user-id` and `--agent-id` override per-item values.
|
||||
|
||||
**Import format example:**
|
||||
```json
|
||||
[
|
||||
{ "memory": "Prefers dark mode", "user_id": "alice" },
|
||||
{ "text": "Allergic to nuts", "metadata": { "source": "intake" } },
|
||||
{ "content": "Uses VS Code" }
|
||||
]
|
||||
```
|
||||
|
||||
**Behavior:** Iterates through items, calling the add API for each. Displays progress and reports `added` and `failed` counts on completion.
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 import memories.json --user-id alice
|
||||
mem0 import data.json -u alice -o json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 config show`
|
||||
|
||||
Display current configuration with secrets redacted.
|
||||
|
||||
**Usage:** `mem0 config show [OPTIONS]`
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`. |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 config show
|
||||
mem0 config show -o json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 config get`
|
||||
|
||||
Get a single configuration value.
|
||||
|
||||
**Usage:** `mem0 config get <key>`
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `key` | string | Yes | Dotted config key (e.g. `platform.api_key`, `defaults.user_id`). |
|
||||
|
||||
**Valid keys:** `platform.api_key`, `platform.base_url`, `defaults.user_id`, `defaults.agent_id`, `defaults.app_id`, `defaults.run_id`, `defaults.enable_graph`.
|
||||
|
||||
API key values are always redacted in output.
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 config get platform.api_key
|
||||
mem0 config get defaults.user_id
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 config set`
|
||||
|
||||
Set a configuration value.
|
||||
|
||||
**Usage:** `mem0 config set <key> <value>`
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `key` | string | Yes | Dotted config key (e.g. `defaults.user_id`). |
|
||||
| `value` | string | Yes | Value to set. |
|
||||
|
||||
**Type coercion:** Boolean fields accept `true`/`1`/`yes` (case-insensitive) as true, anything else as false.
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 config set defaults.user_id alice
|
||||
mem0 config set platform.base_url https://api.mem0.ai
|
||||
mem0 config set defaults.enable_graph true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 config clear`
|
||||
|
||||
Clear the configuration file. Removes `~/.mem0/config.json`.
|
||||
|
||||
**Usage:** `mem0 config clear`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 config clear
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 entity list`
|
||||
|
||||
List all entities of a given type.
|
||||
|
||||
**Usage:** `mem0 entity list <entity_type> [OPTIONS]`
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Name | Type | Required | Choices | Description |
|
||||
|------|------|----------|---------|-------------|
|
||||
| `entity_type` | string | Yes | `users`, `agents`, `apps`, `runs` | Entity type to list. |
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `-o, --output <fmt>` | string | `table` | Output format: `table`, `json`. |
|
||||
|
||||
**Behavior:** Calls `GET /v1/entities/` (returns all types), then filters client-side using the type map (`users` -> `user`, `agents` -> `agent`, etc.). Displays a table with "Name / ID" and "Created" columns.
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 entity list users
|
||||
mem0 entity list agents -o json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 entity delete`
|
||||
|
||||
Delete an entity and ALL its memories (cascade).
|
||||
|
||||
**Usage:** `mem0 entity delete [OPTIONS]`
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `-u, --user-id <id>` | string | - | User ID of the entity to delete. |
|
||||
| `--agent-id <id>` | string | - | Agent ID of the entity to delete. |
|
||||
| `--app-id <id>` | string | - | App ID of the entity to delete. |
|
||||
| `--run-id <id>` | string | - | Run ID of the entity to delete. |
|
||||
| `--dry-run` | boolean | false | Show what would be deleted without deleting. |
|
||||
| `--force` | boolean | false | Skip confirmation prompt. |
|
||||
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`, `quiet`. |
|
||||
|
||||
At least one entity ID is required. Errors if none provided.
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 entity delete --user-id alice --force
|
||||
mem0 entity delete --user-id alice --dry-run
|
||||
mem0 entity delete --agent-id bot1 --force
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 event list`
|
||||
|
||||
List recent background processing events.
|
||||
|
||||
**Usage:** `mem0 event list [OPTIONS]`
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `-o, --output <fmt>` | string | `table` | Output format: `text` (table), `json`. |
|
||||
|
||||
**Behavior:** Fetches all events for the project. Displays a table with columns: Event ID (first 8 chars), Type, Status (color-coded), Latency, Created. Status values: `PENDING`, `SUCCEEDED`, `FAILED`, `PROCESSING`.
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 event list
|
||||
mem0 event list --output json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 event status`
|
||||
|
||||
Get the status and results of a specific background event.
|
||||
|
||||
**Usage:** `mem0 event status <event_id> [OPTIONS]`
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `event_id` | string | Yes | Event ID to inspect. |
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`. |
|
||||
|
||||
**Behavior:** Fetches the event by ID. Displays: Event ID, Type, Status, Latency, Created, Updated, and a list of result memories.
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 event status evt-abc-123
|
||||
mem0 event status evt-abc-123 --output json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mem0 status`
|
||||
|
||||
Check connectivity and authentication.
|
||||
|
||||
**Usage:** `mem0 status [OPTIONS]`
|
||||
|
||||
**Options:**
|
||||
|
||||
| Flag | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `-o, --output <fmt>` | string | `text` | Output format: `text`, `json`. |
|
||||
|
||||
**Behavior:** Calls `GET /v1/ping/` to validate connectivity and authentication. Displays connection status, backend type, and base URL.
|
||||
|
||||
**JSON output:**
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"command": "status",
|
||||
"duration_ms": 112,
|
||||
"data": {
|
||||
"connected": true,
|
||||
"backend": "platform",
|
||||
"base_url": "https://api.mem0.ai"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
mem0 status
|
||||
mem0 status -o json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Agent Mode Envelope Format
|
||||
|
||||
When `--json` or `--agent` is passed, every command wraps its output in a consistent JSON envelope on stdout:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"command": "<command_name>",
|
||||
"duration_ms": 245,
|
||||
"scope": { "user_id": "alice", "agent_id": null },
|
||||
"count": 10,
|
||||
"error": null,
|
||||
"data": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**Fields:**
|
||||
- `status`: `"success"` or `"error"`.
|
||||
- `command`: The command name (e.g. `"search"`, `"add"`, `"list"`).
|
||||
- `duration_ms`: Elapsed time in milliseconds (optional).
|
||||
- `scope`: Active entity scope, omitted if empty (optional).
|
||||
- `count`: Number of results, where applicable (optional).
|
||||
- `error`: Error message string, or `null` on success.
|
||||
- `data`: Command-specific response data, or `null` on error.
|
||||
|
||||
**Sanitized data fields per command in agent mode:**
|
||||
|
||||
| Command | `data` shape |
|
||||
|---------|-------------|
|
||||
| `add` | `[{id, memory, event}]` or `[{status, event_id}]` for PENDING |
|
||||
| `search` | `[{id, memory, score, created_at, categories}]` |
|
||||
| `list` | `[{id, memory, created_at, categories}]` |
|
||||
| `get` | `{id, memory, created_at, updated_at, categories, metadata}` |
|
||||
| `update` | `{id, memory}` |
|
||||
| `delete` | Raw API response |
|
||||
| `entity list` | `[{name, type, count}]` |
|
||||
| `event list` | `[{id, event_type, status, latency, created_at}]` |
|
||||
| `event status` | `{id, event_type, status, latency, created_at, updated_at, results}` |
|
||||
| `status` | `{connected, backend, base_url}` |
|
||||
| `config show` | Config object (keys redacted) |
|
||||
| `import` | `{added, failed, duration_s}` |
|
||||
|
||||
**Error envelope:**
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"command": "search",
|
||||
"error": "Authentication failed. Your API key may be invalid or expired.",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entity ID Resolution
|
||||
|
||||
**Rule:** If **any** explicit entity ID is provided via CLI flags (`--user-id`, `--agent-id`, `--app-id`, `--run-id`), the CLI uses only the explicitly provided IDs. It does NOT mix in defaults from config for the other entity types.
|
||||
|
||||
If **no** explicit IDs are given, all configured defaults from config file and env vars apply.
|
||||
|
||||
**Rationale:** If a user passes `--user-id alice` and the config also has `agent_id=bot1`, they want only Alice's memories -- not the intersection of Alice AND bot1.
|
||||
|
||||
```
|
||||
if any(user_id, agent_id, app_id, run_id) were passed as flags:
|
||||
use only the explicitly provided IDs (others = null)
|
||||
else:
|
||||
use all configured defaults
|
||||
```
|
||||
|
||||
This applies to commands with `resolveIds: true`: `add`, `search`, `list`, `delete`, `import`.
|
||||
|
||||
---
|
||||
|
||||
## Graph Tri-State
|
||||
|
||||
The `enable_graph` parameter follows a three-level precedence:
|
||||
|
||||
```
|
||||
--no-graph (explicit disable) > --graph (explicit enable) > config default
|
||||
```
|
||||
|
||||
If `--no-graph` is passed, graph is disabled regardless of other settings. If `--graph` is passed (without `--no-graph`), graph is enabled. If neither is passed, the config value `defaults.enable_graph` is used.
|
||||
|
||||
This applies to commands with `resolveGraph: true`: `add`, `search`, `list`.
|
||||
|
||||
---
|
||||
|
||||
## Filter Building
|
||||
|
||||
For `search` and `list`, entity IDs and additional filters are composed into the API filter structure:
|
||||
|
||||
1. If the user provides a pre-built filter via `--filter` containing `AND` or `OR` keys, it is passed through to the API as-is.
|
||||
2. Otherwise, the CLI builds an array of AND conditions:
|
||||
- Each entity ID becomes a condition: `{"user_id": "alice"}`, etc.
|
||||
- Category filters: `{"categories": {"contains": "<category>"}}`.
|
||||
- Date filters: `{"created_at": {"gte": "YYYY-MM-DD"}}` and/or `{"created_at": {"lte": "YYYY-MM-DD"}}`.
|
||||
3. If exactly 1 condition: sent as a single object (no wrapping).
|
||||
4. If 2+ conditions: wrapped as `{"AND": [condition1, condition2, ...]}`.
|
||||
5. If 0 conditions: no filter sent.
|
||||
|
||||
---
|
||||
|
||||
## Output Mode Support Matrix
|
||||
|
||||
| Command | `text` | `json` | `table` | `quiet` | Default |
|
||||
|---------|--------|--------|---------|---------|---------|
|
||||
| `add` | Y | Y | - | Y | `text` |
|
||||
| `search` | Y | Y | Y | - | `text` |
|
||||
| `get` | Y | Y | - | - | `text` |
|
||||
| `list` | Y | Y | Y | - | `table` |
|
||||
| `update` | Y | Y | - | Y | `text` |
|
||||
| `delete` | Y | Y | - | Y | `text` |
|
||||
| `import` | Y | Y | - | - | `text` |
|
||||
| `config show` | Y | Y | - | - | `text` |
|
||||
| `config get` | raw | - | - | - | raw |
|
||||
| `config set` | msg | - | - | - | msg |
|
||||
| `entity list` | - | Y | Y | - | `table` |
|
||||
| `entity delete` | Y | Y | - | Y | `text` |
|
||||
| `event list` | Y (table) | Y | - | - | `table` |
|
||||
| `event status` | Y | Y | - | - | `text` |
|
||||
| `status` | Y | Y | - | - | `text` |
|
||||
|
||||
All commands additionally support agent mode (`--json`/`--agent`) which overrides the output format with the JSON envelope.
|
||||
@@ -0,0 +1,244 @@
|
||||
# Mem0 CLI Configuration
|
||||
|
||||
Everything about configuring the mem0 CLI: config file format, environment variables, the init wizard, and precedence rules.
|
||||
|
||||
---
|
||||
|
||||
## Config File Location
|
||||
|
||||
| Path | Permissions | Description |
|
||||
|------|-------------|-------------|
|
||||
| `~/.mem0/` | `0700` (owner rwx) | Config directory. Created automatically by `mem0 init`. |
|
||||
| `~/.mem0/config.json` | `0600` (owner rw) | Config file. Contains API key, defaults, and platform settings. |
|
||||
|
||||
The restricted permissions ensure API keys are not world-readable.
|
||||
|
||||
---
|
||||
|
||||
## Config File Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"defaults": {
|
||||
"user_id": "",
|
||||
"agent_id": "",
|
||||
"app_id": "",
|
||||
"run_id": "",
|
||||
"enable_graph": false
|
||||
},
|
||||
"platform": {
|
||||
"api_key": "",
|
||||
"base_url": "https://api.mem0.ai"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Field Reference
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `version` | integer | `1` | Config schema version. |
|
||||
| `defaults.user_id` | string | `""` | Default user ID for scoping commands. |
|
||||
| `defaults.agent_id` | string | `""` | Default agent ID for scoping commands. |
|
||||
| `defaults.app_id` | string | `""` | Default app ID for scoping commands. |
|
||||
| `defaults.run_id` | string | `""` | Default run ID for scoping commands. |
|
||||
| `defaults.enable_graph` | boolean | `false` | Default graph memory extraction toggle. |
|
||||
| `platform.api_key` | string | `""` | API key for the Mem0 Platform. |
|
||||
| `platform.base_url` | string | `"https://api.mem0.ai"` | Base URL for API requests. |
|
||||
|
||||
---
|
||||
|
||||
## `mem0 init` Wizard
|
||||
|
||||
The `init` command provides two authentication flows:
|
||||
|
||||
### API Key Flow (default)
|
||||
|
||||
```bash
|
||||
# Fully interactive:
|
||||
mem0 init
|
||||
|
||||
# Fully non-interactive:
|
||||
mem0 init --api-key m0-xxx --user-id alice
|
||||
```
|
||||
|
||||
**Interactive mode steps:**
|
||||
|
||||
1. Displays the mem0 banner.
|
||||
2. Checks for existing config. If found with an API key, asks for confirmation to overwrite.
|
||||
3. Prompts for API key (input masked with `*` characters; supports backspace and Ctrl+U to clear).
|
||||
4. Prompts for default user ID (default value: `mem0-cli`).
|
||||
5. Validates the connection by calling the status endpoint.
|
||||
6. Saves config to `~/.mem0/config.json` with `0600` permissions.
|
||||
7. Prints success message.
|
||||
|
||||
**Non-interactive mode:** When both `--api-key` and `--user-id` are provided, skips all prompts and saves directly. When running in a non-TTY without both flags, prints an error:
|
||||
|
||||
```
|
||||
Non-interactive terminal detected and missing required flags.
|
||||
Usage: mem0 init --api-key <key> --user-id <id>
|
||||
```
|
||||
|
||||
### Email Login Flow
|
||||
|
||||
```bash
|
||||
# Interactive (prompts for code):
|
||||
mem0 init --email alice@company.com
|
||||
|
||||
# Fully non-interactive:
|
||||
mem0 init --email alice@company.com --code 482901
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Sends a 6-digit verification code to the email via `POST /api/v1/auth/email_code/`.
|
||||
2. If `--code` is provided, verifies immediately. Otherwise prompts for the code.
|
||||
3. On success: receives API key, org_id, and project_id from the server.
|
||||
4. Saves to config. Creates a new account if the email is not registered.
|
||||
|
||||
Cannot be combined with `--api-key`.
|
||||
|
||||
### Force Overwrite
|
||||
|
||||
If `~/.mem0/config.json` already exists with an API key, `mem0 init` warns and asks for confirmation. Use `--force` to skip:
|
||||
|
||||
```bash
|
||||
mem0 init --api-key m0-new-key --user-id alice --force
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `mem0 config` Subcommands
|
||||
|
||||
### `mem0 config show`
|
||||
|
||||
Displays the current configuration as a formatted table (text mode) or JSON envelope (json mode). API keys are always redacted.
|
||||
|
||||
```bash
|
||||
mem0 config show
|
||||
mem0 config show -o json
|
||||
```
|
||||
|
||||
### `mem0 config get <key>`
|
||||
|
||||
Reads a single configuration value. The key uses dotted notation.
|
||||
|
||||
```bash
|
||||
mem0 config get platform.api_key # prints: m0-x...xxxx (redacted)
|
||||
mem0 config get defaults.user_id # prints: alice
|
||||
mem0 config get defaults.enable_graph # prints: false
|
||||
```
|
||||
|
||||
**Valid keys:**
|
||||
- `platform.api_key`
|
||||
- `platform.base_url`
|
||||
- `defaults.user_id`
|
||||
- `defaults.agent_id`
|
||||
- `defaults.app_id`
|
||||
- `defaults.run_id`
|
||||
- `defaults.enable_graph`
|
||||
|
||||
Unknown keys print an error message.
|
||||
|
||||
### `mem0 config set <key> <value>`
|
||||
|
||||
Sets a configuration value and saves the config file.
|
||||
|
||||
```bash
|
||||
mem0 config set defaults.user_id alice
|
||||
mem0 config set platform.base_url https://api.mem0.ai
|
||||
mem0 config set defaults.enable_graph true
|
||||
```
|
||||
|
||||
**Type coercion:**
|
||||
- Boolean fields accept `true`, `1`, `yes` (case-insensitive) as true. Anything else is false.
|
||||
- Integer fields are parsed with `parseInt`.
|
||||
- String fields are stored as-is.
|
||||
|
||||
### `mem0 config clear`
|
||||
|
||||
Removes the config file (`~/.mem0/config.json`).
|
||||
|
||||
```bash
|
||||
mem0 config clear
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Environment variables override config file values but are overridden by CLI flags.
|
||||
|
||||
| Variable | Config Path | Type | Default |
|
||||
|----------|-------------|------|---------|
|
||||
| `MEM0_API_KEY` | `platform.api_key` | string | `""` |
|
||||
| `MEM0_BASE_URL` | `platform.base_url` | string | `"https://api.mem0.ai"` |
|
||||
| `MEM0_USER_ID` | `defaults.user_id` | string | `""` |
|
||||
| `MEM0_AGENT_ID` | `defaults.agent_id` | string | `""` |
|
||||
| `MEM0_APP_ID` | `defaults.app_id` | string | `""` |
|
||||
| `MEM0_RUN_ID` | `defaults.run_id` | string | `""` |
|
||||
| `MEM0_ENABLE_GRAPH` | `defaults.enable_graph` | boolean | `false` |
|
||||
|
||||
### Boolean Parsing for `MEM0_ENABLE_GRAPH`
|
||||
|
||||
Accepted truthy values (case-insensitive): `"true"`, `"1"`, `"yes"`. Everything else is treated as `false`.
|
||||
|
||||
```bash
|
||||
export MEM0_ENABLE_GRAPH=true # enabled
|
||||
export MEM0_ENABLE_GRAPH=1 # enabled
|
||||
export MEM0_ENABLE_GRAPH=yes # enabled
|
||||
export MEM0_ENABLE_GRAPH=false # disabled
|
||||
export MEM0_ENABLE_GRAPH=0 # disabled
|
||||
export MEM0_ENABLE_GRAPH="" # disabled
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Precedence
|
||||
|
||||
Configuration values are resolved in this order (highest priority first):
|
||||
|
||||
```
|
||||
1. CLI flags --api-key, --user-id, --base-url, --graph, --no-graph, etc.
|
||||
2. Environment vars MEM0_API_KEY, MEM0_USER_ID, MEM0_ENABLE_GRAPH, etc.
|
||||
3. Config file ~/.mem0/config.json
|
||||
4. Defaults Hardcoded defaults (empty strings, false, https://api.mem0.ai)
|
||||
```
|
||||
|
||||
**Example:** If your config file has `user_id: "bob"`, the env var `MEM0_USER_ID=charlie` is set, and you pass `--user-id alice` on the command line, the effective user_id is `alice`.
|
||||
|
||||
---
|
||||
|
||||
## API Key Redaction Rules
|
||||
|
||||
Whenever an API key is displayed (in `config show`, `config get`, status output, etc.), it is redacted:
|
||||
|
||||
| Condition | Output |
|
||||
|-----------|--------|
|
||||
| Empty string | `(not set)` |
|
||||
| Length <= 8 | First 2 characters + `***` |
|
||||
| Length > 8 | First 4 characters + `...` + last 4 characters |
|
||||
|
||||
**Examples:**
|
||||
- `""` -> `(not set)`
|
||||
- `"m0-abc"` -> `m0***`
|
||||
- `"m0-abcdefghijklmnop"` -> `m0-a...mnop`
|
||||
|
||||
The redaction function is named `redact_key` (Python) / `redactKey` (Node).
|
||||
|
||||
---
|
||||
|
||||
## Dotted Key Map
|
||||
|
||||
The `config get` and `config set` commands use dotted key paths. Here is the full mapping:
|
||||
|
||||
| Dotted Key | Section | Field |
|
||||
|------------|---------|-------|
|
||||
| `platform.api_key` | platform | api_key |
|
||||
| `platform.base_url` | platform | base_url |
|
||||
| `defaults.user_id` | defaults | user_id |
|
||||
| `defaults.agent_id` | defaults | agent_id |
|
||||
| `defaults.app_id` | defaults | app_id |
|
||||
| `defaults.run_id` | defaults | run_id |
|
||||
| `defaults.enable_graph` | defaults | enable_graph |
|
||||
@@ -0,0 +1,439 @@
|
||||
# Mem0 CLI Workflows
|
||||
|
||||
Practical recipes for using the mem0 CLI in scripts, pipelines, and agent loops.
|
||||
|
||||
---
|
||||
|
||||
## Piping Content via Stdin
|
||||
|
||||
The CLI reads from stdin when no text argument is provided and input is piped (not a TTY). This works with `add`, `search`, and `update`.
|
||||
|
||||
**Stdin detection method:**
|
||||
- Python: `not sys.stdin.isatty()`
|
||||
- Node: `!process.stdin.isTTY`
|
||||
|
||||
### Add from pipe
|
||||
|
||||
```bash
|
||||
echo "I prefer dark mode" | mem0 add --user-id alice
|
||||
```
|
||||
|
||||
### Pipe multi-line content
|
||||
|
||||
```bash
|
||||
cat <<EOF | mem0 add --user-id alice
|
||||
The user prefers dark mode in all applications.
|
||||
They also like monospace fonts for code editing.
|
||||
EOF
|
||||
```
|
||||
|
||||
### Pipe from another command
|
||||
|
||||
```bash
|
||||
git log --oneline -5 | mem0 add --user-id ci-bot --metadata '{"source":"git"}'
|
||||
```
|
||||
|
||||
### Search from pipe
|
||||
|
||||
```bash
|
||||
echo "preferences" | mem0 search --user-id alice
|
||||
```
|
||||
|
||||
### Update from pipe
|
||||
|
||||
```bash
|
||||
echo "Updated: prefers dark mode AND high contrast" | mem0 update abc-123-def-456
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Import
|
||||
|
||||
Use `mem0 import` to bulk-load memories from a JSON file.
|
||||
|
||||
### Basic import
|
||||
|
||||
```bash
|
||||
mem0 import memories.json --user-id alice
|
||||
```
|
||||
|
||||
### File format
|
||||
|
||||
The file should be a JSON array where each item has a `memory`, `text`, or `content` field:
|
||||
|
||||
```json
|
||||
[
|
||||
{ "memory": "Prefers dark mode" },
|
||||
{ "text": "Allergic to nuts", "metadata": { "source": "intake-form" } },
|
||||
{ "content": "Uses VS Code", "user_id": "bob" }
|
||||
]
|
||||
```
|
||||
|
||||
CLI-provided `--user-id` overrides per-item `user_id` values.
|
||||
|
||||
### Import with JSON output
|
||||
|
||||
```bash
|
||||
mem0 import data.json --user-id alice -o json
|
||||
```
|
||||
|
||||
Output:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"command": "import",
|
||||
"data": { "added": 42, "failed": 0, "duration_s": 3.14 },
|
||||
"duration_ms": 3140
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Agent Mode for LLM Consumption
|
||||
|
||||
Use `--json` or `--agent` to get structured JSON output suitable for LLM tool calling or agent frameworks. Spinners and progress always go to stderr, keeping stdout clean.
|
||||
|
||||
### Search with agent mode
|
||||
|
||||
```bash
|
||||
mem0 search "preferences" --user-id alice --agent
|
||||
```
|
||||
|
||||
Output (stdout):
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"command": "search",
|
||||
"duration_ms": 187,
|
||||
"scope": { "user_id": "alice" },
|
||||
"count": 2,
|
||||
"error": null,
|
||||
"data": [
|
||||
{ "id": "mem-abc", "memory": "User prefers dark mode", "score": 0.95, "created_at": "2025-01-15T10:00:00Z", "categories": ["preferences"] },
|
||||
{ "id": "mem-def", "memory": "User likes monospace fonts", "score": 0.82, "created_at": "2025-01-15T10:01:00Z", "categories": ["preferences"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Add with agent mode
|
||||
|
||||
```bash
|
||||
mem0 add "Uses Python 3.12" --user-id alice --json
|
||||
```
|
||||
|
||||
### Error handling in agent mode
|
||||
|
||||
Errors also return valid JSON with `"status": "error"`:
|
||||
|
||||
```bash
|
||||
mem0 search "test" --user-id alice --api-key invalid --agent
|
||||
```
|
||||
|
||||
Output:
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"command": "search",
|
||||
"error": "Authentication failed. Your API key may be invalid or expired.",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## JSON Output + jq
|
||||
|
||||
Use `--output json` (or `-o json`) for raw JSON output, then pipe to `jq` for processing.
|
||||
|
||||
### Extract just memory text
|
||||
|
||||
```bash
|
||||
mem0 list --user-id alice --output json | jq '.[] | .memory'
|
||||
```
|
||||
|
||||
### Get memory IDs
|
||||
|
||||
```bash
|
||||
mem0 list --user-id alice -o json | jq '.[].id'
|
||||
```
|
||||
|
||||
### Count memories
|
||||
|
||||
```bash
|
||||
mem0 list --user-id alice -o json | jq 'length'
|
||||
```
|
||||
|
||||
### Filter by category in jq
|
||||
|
||||
```bash
|
||||
mem0 list --user-id alice -o json | jq '[.[] | select(.categories[]? == "preferences")]'
|
||||
```
|
||||
|
||||
### Extract search scores
|
||||
|
||||
```bash
|
||||
mem0 search "tools" --user-id alice -o json | jq '.[] | {memory, score}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bulk Operations
|
||||
|
||||
### Delete multiple memories by ID
|
||||
|
||||
```bash
|
||||
# Get IDs, then delete each one
|
||||
mem0 list --user-id alice -o json | jq -r '.[].id' | while read id; do
|
||||
mem0 delete "$id" --force
|
||||
done
|
||||
```
|
||||
|
||||
### Bulk add from a text file (one memory per line)
|
||||
|
||||
```bash
|
||||
while IFS= read -r line; do
|
||||
mem0 add "$line" --user-id alice
|
||||
done < memories.txt
|
||||
```
|
||||
|
||||
### Copy memories between users
|
||||
|
||||
```bash
|
||||
mem0 list --user-id alice -o json | jq -r '.[].memory' | while IFS= read -r mem; do
|
||||
mem0 add "$mem" --user-id bob
|
||||
done
|
||||
```
|
||||
|
||||
### Export all memories to a file
|
||||
|
||||
```bash
|
||||
mem0 list --user-id alice -o json > alice_memories.json
|
||||
```
|
||||
|
||||
### Paginate through all results
|
||||
|
||||
```bash
|
||||
page=1
|
||||
while true; do
|
||||
result=$(mem0 list --user-id alice -o json --page "$page" --page-size 100)
|
||||
count=$(echo "$result" | jq 'length')
|
||||
if [ "$count" -eq 0 ]; then
|
||||
break
|
||||
fi
|
||||
echo "$result"
|
||||
page=$((page + 1))
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI/CD Patterns
|
||||
|
||||
### Store build context as a memory
|
||||
|
||||
```bash
|
||||
mem0 add "Build #${BUILD_NUMBER} deployed ${APP_VERSION} to ${ENVIRONMENT} at $(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
--agent-id "ci-bot" \
|
||||
--metadata "{\"build_number\":\"${BUILD_NUMBER}\",\"version\":\"${APP_VERSION}\",\"env\":\"${ENVIRONMENT}\"}"
|
||||
```
|
||||
|
||||
### Retrieve deployment history
|
||||
|
||||
```bash
|
||||
mem0 search "deployment to production" --agent-id ci-bot -o json -k 10
|
||||
```
|
||||
|
||||
### Check CLI connectivity in CI
|
||||
|
||||
```bash
|
||||
if mem0 status -o json | jq -e '.data.connected' > /dev/null 2>&1; then
|
||||
echo "mem0 is connected"
|
||||
else
|
||||
echo "mem0 connection failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Non-interactive init in CI
|
||||
|
||||
```bash
|
||||
mem0 init --api-key "$MEM0_API_KEY" --user-id ci-bot --force
|
||||
```
|
||||
|
||||
Or simply use the environment variable (no init needed):
|
||||
|
||||
```bash
|
||||
export MEM0_API_KEY="$MEM0_API_KEY"
|
||||
mem0 add "CI run started" --user-id ci-bot
|
||||
```
|
||||
|
||||
### Store test results
|
||||
|
||||
```bash
|
||||
test_summary=$(cat test-results.txt | head -20)
|
||||
mem0 add "$test_summary" --agent-id ci-bot --metadata '{"type":"test-results"}' --categories "ci,testing"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stdin Detection Details
|
||||
|
||||
The CLI reads from stdin only when ALL of these conditions are met:
|
||||
|
||||
1. No text argument was provided on the command line.
|
||||
2. For `add`: no `--messages` and no `--file` flag.
|
||||
3. For `update`: no `--metadata` flag.
|
||||
4. stdin is piped (not a TTY).
|
||||
|
||||
**This means:**
|
||||
- `mem0 add --user-id alice` in an interactive terminal will NOT hang waiting for input. It will print a usage error.
|
||||
- `echo "text" | mem0 add --user-id alice` will read "text" from stdin.
|
||||
- `mem0 add "explicit text" --user-id alice` will use the explicit text, even if stdin is piped.
|
||||
|
||||
**Reading method:**
|
||||
- Python: `sys.stdin.read().strip()`
|
||||
- Node: `fs.readFileSync(0, "utf-8").trim()`
|
||||
|
||||
---
|
||||
|
||||
## Common Shell Patterns
|
||||
|
||||
### Error handling with exit codes
|
||||
|
||||
```bash
|
||||
set -e # Exit on error
|
||||
|
||||
# This will exit the script if the API key is invalid
|
||||
mem0 status > /dev/null 2>&1
|
||||
|
||||
# Add with error check
|
||||
if mem0 add "test memory" --user-id alice 2>/dev/null; then
|
||||
echo "Memory added successfully"
|
||||
else
|
||||
echo "Failed to add memory" >&2
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Capture memory ID from add
|
||||
|
||||
```bash
|
||||
# Use agent mode to get structured output
|
||||
result=$(mem0 add "new fact" --user-id alice --agent 2>/dev/null)
|
||||
memory_id=$(echo "$result" | jq -r '.data[0].id // empty')
|
||||
if [ -n "$memory_id" ]; then
|
||||
echo "Created memory: $memory_id"
|
||||
fi
|
||||
```
|
||||
|
||||
### Conditional memory addition
|
||||
|
||||
```bash
|
||||
# Only add if search returns no results
|
||||
count=$(mem0 search "dark mode" --user-id alice --agent 2>/dev/null | jq '.count // 0')
|
||||
if [ "$count" -eq 0 ]; then
|
||||
mem0 add "User prefers dark mode" --user-id alice
|
||||
fi
|
||||
```
|
||||
|
||||
### Quiet mode for scripts
|
||||
|
||||
```bash
|
||||
# Suppress all output except errors
|
||||
mem0 add "background note" --user-id alice --output quiet 2>/dev/null
|
||||
mem0 delete --all --user-id temp-user --force --output quiet 2>/dev/null
|
||||
```
|
||||
|
||||
### Using environment variables for scope
|
||||
|
||||
```bash
|
||||
export MEM0_USER_ID="alice"
|
||||
export MEM0_API_KEY="m0-xxx"
|
||||
|
||||
# All commands now default to user alice, no --user-id needed
|
||||
mem0 add "prefers dark mode"
|
||||
mem0 search "preferences"
|
||||
mem0 list
|
||||
```
|
||||
|
||||
### Timeout handling
|
||||
|
||||
The CLI uses a 30-second timeout for all API requests. For long-running scripts, handle timeouts:
|
||||
|
||||
```bash
|
||||
if ! mem0 search "query" --user-id alice -o json 2>/dev/null; then
|
||||
echo "Request failed or timed out" >&2
|
||||
fi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Processing Delay Workaround
|
||||
|
||||
Memories are processed asynchronously after `mem0 add`. If you need to search for a newly added memory immediately, add a short delay:
|
||||
|
||||
```bash
|
||||
mem0 add "new preference" --user-id alice
|
||||
sleep 3
|
||||
mem0 search "new preference" --user-id alice
|
||||
```
|
||||
|
||||
Or use the event system to poll for completion:
|
||||
|
||||
```bash
|
||||
# Add and capture event ID from agent output
|
||||
result=$(mem0 add "new preference" --user-id alice --agent 2>/dev/null)
|
||||
event_id=$(echo "$result" | jq -r '.data[0].event_id // empty')
|
||||
|
||||
if [ -n "$event_id" ]; then
|
||||
# Poll until processing completes
|
||||
while true; do
|
||||
status=$(mem0 event status "$event_id" --agent 2>/dev/null | jq -r '.data.status')
|
||||
if [ "$status" = "SUCCEEDED" ] || [ "$status" = "FAILED" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Multi-User Agent Pattern
|
||||
|
||||
For AI agents managing memories across multiple users:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# agent_memory.sh -- manage memories for the current conversation
|
||||
|
||||
USER_ID="$1"
|
||||
ACTION="$2"
|
||||
shift 2
|
||||
|
||||
case "$ACTION" in
|
||||
recall)
|
||||
mem0 search "$*" --user-id "$USER_ID" --agent 2>/dev/null
|
||||
;;
|
||||
remember)
|
||||
mem0 add "$*" --user-id "$USER_ID" --agent 2>/dev/null
|
||||
;;
|
||||
forget)
|
||||
mem0 delete --all --user-id "$USER_ID" --force --agent 2>/dev/null
|
||||
;;
|
||||
history)
|
||||
mem0 list --user-id "$USER_ID" --agent 2>/dev/null
|
||||
;;
|
||||
*)
|
||||
echo '{"status":"error","error":"Unknown action: '"$ACTION"'"}' >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
```
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
./agent_memory.sh alice recall "dietary preferences"
|
||||
./agent_memory.sh alice remember "allergic to shellfish"
|
||||
./agent_memory.sh alice history
|
||||
```
|
||||
@@ -0,0 +1,189 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but not
|
||||
limited to compiled object code, generated documentation, and
|
||||
conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work.
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2024 Mem0.ai
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user