diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index ce959d3bec..bad5fbb9b4 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "mem0", "source": "./mem0-plugin", "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.", - "version": "0.2.6" + "version": "0.2.7" } ] } diff --git a/.codex-plugin/marketplace.json b/.codex-plugin/marketplace.json new file mode 100644 index 0000000000..6e0e240a1e --- /dev/null +++ b/.codex-plugin/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "mem0-plugins", + "interface": { + "displayName": "Mem0 Plugins" + }, + "plugins": [ + { + "name": "mem0", + "source": { + "source": "local", + "path": "./mem0-plugin" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" + } + ] +} diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index baa8b0e295..78541d76f9 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "mem0", "source": "./mem0-plugin", "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.", - "version": "0.2.6" + "version": "0.2.7" } ] } diff --git a/docs/changelog/sdk.mdx b/docs/changelog/sdk.mdx index feebc74ca7..566ce2415d 100644 --- a/docs/changelog/sdk.mdx +++ b/docs/changelog/sdk.mdx @@ -7,6 +7,13 @@ mode: "wide" + + +**New Features:** +- **Client:** `delete()` and async `delete()` accept `delete_linked` (default `False`). When `True`, deleting a memory also removes the older memories it superseded (the v3 `linked_memory_ids` chain), transitively — the delete-side counterpart of `latest_only`, so a superseded memory does not resurface after the current one is deleted ([#5270](https://github.com/mem0ai/mem0/pull/5270)) + + + **Bug Fixes:** @@ -932,6 +939,13 @@ See the [OSS v1 to v2 migration guide](https://docs.mem0.ai/migration/oss-v1-to- + + +**New Features:** +- **Client:** `delete()` accepts an options object with `deleteLinked` (serialized as `delete_linked`, default `false`). When `true`, deleting a memory also removes the older memories it superseded (the v3 linked chain), transitively — the delete-side counterpart of `latestOnly`, so a superseded memory does not resurface after the current one is deleted ([#5270](https://github.com/mem0ai/mem0/pull/5270)) + + + **Bug Fixes:** diff --git a/docs/docs.json b/docs/docs.json index 30acad4258..8a4c958654 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -452,7 +452,9 @@ "pages": [ "integrations/claude-code", "integrations/cursor", - "integrations/codex" + "integrations/codex", + "integrations/opencode", + "integrations/antigravity" ] }, { diff --git a/docs/integrations/antigravity.mdx b/docs/integrations/antigravity.mdx new file mode 100644 index 0000000000..0738666d7a --- /dev/null +++ b/docs/integrations/antigravity.mdx @@ -0,0 +1,105 @@ +--- +title: Antigravity +description: "Add persistent memory to Google Antigravity with the Mem0 plugin — MCP server, lifecycle hooks, and slash commands." +--- + +Add persistent memory to [**Google Antigravity**](https://antigravity.google) (`agy` CLI and Desktop IDE) with the Mem0 plugin. Your agent forgets everything between sessions — Mem0 fixes that by storing decisions, preferences, and learnings so they carry over automatically. + +## Prerequisites + +1. A Mem0 API key (starts with `m0-`): + - Get your API key (free sign-up at app.mem0.ai) + +2. Add it to your shell profile so it persists across sessions: + + +```bash zsh +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.zshrc && source ~/.zshrc +``` + +```bash bash +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.bashrc && source ~/.bashrc +``` + + +## Installation + +**Option A — degit** (one command, no clone needed): + +```bash +npx degit mem0ai/mem0/mem0-plugin/.antigravity ~/.gemini/config/plugins/mem0 +``` + +**Option B — agy CLI** (if you have the repo cloned): + +```bash +agy plugin install /path/to/mem0/mem0-plugin/.antigravity +``` + +**Option C — MCP only** (no hooks or skills): + +Create `~/.gemini/config/plugins/mem0/plugin.json`: + +```json +{ + "name": "mem0", + "mcpServers": { + "mem0": { + "serverUrl": "https://mcp.mem0.ai/mcp/", + "headers": { "Authorization": "Token ${MEM0_API_KEY}" } + } + } +} +``` + +Restart Antigravity. The full plugin auto-registers the MCP server, lifecycle hooks, scripts, and all `/mem0:` skills. It reads `MEM0_API_KEY` from your environment automatically — no extra config needed. + + + +## What's Included + +| Component | Full Plugin (A/B) | MCP Only (C) | +|-----------|:-----------------:|:------------:| +| MCP Server (9 memory tools) | Yes | Yes | +| Lifecycle Hooks | Yes | No | +| 16 Slash Commands | Yes | No | + +## Available MCP Tools + +| Tool | Description | +|------|-------------| +| `add_memory` | Save text or conversation history for a user/agent | +| `search_memories` | Semantic search across memories with filters | +| `get_memories` | List memories with filters and pagination | +| `get_memory` | Retrieve a specific memory by ID | +| `update_memory` | Overwrite a memory's text by ID | +| `delete_memory` | Delete a single memory by ID | +| `delete_all_memories` | Bulk delete all memories in scope | +| `delete_entities` | Delete a user/agent/app/run entity and its memories | +| `list_entities` | List users/agents/apps/runs stored in Mem0 | + +## Lifecycle Hooks + +The plugin uses the same shell scripts as Claude Code, Cursor, and Codex — hooks bridge environment variables using `${extensionPath}` (Antigravity's plugin-root token). + +| Hook | Event | What it does | +|------|-------|-------------| +| **Session start** | `SessionStart` | Loads prior memories and displays status banner | +| **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message | +| **Pre-tool** | `PreToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tools | +| **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories | + +## Troubleshooting + +- **No tools appearing** — Restart your Antigravity session after installation +- **"Connection failed"** — Verify your key is set: `echo $MEM0_API_KEY` +- **MCP 401 Unauthorized** — If `${MEM0_API_KEY}` interpolation doesn't work in your `agy` version, replace with your literal key in `mcp_config.json` + + + + Detailed MCP configuration for all clients + + + Add Mem0 memory to OpenCode workflows + + diff --git a/docs/integrations/claude-code.mdx b/docs/integrations/claude-code.mdx index be78a33754..3a3516725e 100644 --- a/docs/integrations/claude-code.mdx +++ b/docs/integrations/claude-code.mdx @@ -5,13 +5,6 @@ description: "Add persistent memory to Claude Code and Claude Cowork with the Me Add persistent memory to [**Claude Code**](https://docs.anthropic.com/en/docs/claude-code) (CLI) and **Claude Cowork** (desktop app) with the Mem0 plugin. Your agent forgets everything between sessions — this plugin fixes that by connecting to Mem0's cloud memory layer via MCP, automatically capturing learnings at key lifecycle points, and retrieving relevant context before every response. -## Overview - -1. **MCP Server** — Connect to Mem0's remote MCP server for memory tools (add, search, update, delete) -2. **Lifecycle Hooks** — Automatic memory capture at session start, context compaction, task completion, and session end -3. **SDK Skill** — Teaches the agent how to integrate the Mem0 SDK into your applications -4. **Zero local dependencies** — Cloud-hosted MCP server, no local setup required - ## Prerequisites Before setting up Mem0 with Claude Code, ensure you have: @@ -115,23 +108,6 @@ This runs the setup wizard which: The onboarding is idempotent — safe to re-run anytime. It auto-triggers on first session in a new project, but you can always invoke it manually. -## Available Skills - -The plugin includes 17 skills accessible via `/mem0:` commands: - -| Command | Description | -|---------|-------------| -| `/mem0:remember` | Store a memory verbatim — decisions, preferences, conventions | -| `/mem0:tour` | Browse all memories grouped by category | -| `/mem0:peek` | Quick search with compact one-liner results | -| `/mem0:stats` | Session and project memory statistics | -| `/mem0:dream` | Consolidate memories — merge duplicates, resolve contradictions | -| `/mem0:pin` | Protect critical memories from pruning | -| `/mem0:forget` | Delete memories by search or ID | -| `/mem0:health` | Diagnose connectivity, API key, and read/write | -| `/mem0:export` | Export memories to portable Markdown | -| `/mem0:import` | Import memories from export file or MEMORY.md | - ## What's Included | Component | Plugin Install | MCP Only | @@ -160,20 +136,13 @@ Once installed, the following tools are available in every Claude Code session: When installed via the plugin marketplace, Mem0 hooks into Claude Code's lifecycle to automatically manage memory: -### Session Start -On every new session, the plugin prompts Claude to call `search_memories` to load relevant context from prior sessions. On resumed or post-compaction sessions, it adjusts the prompt accordingly. - -### User Prompt -Before processing each user message, the plugin searches Mem0 for memories relevant to the current prompt and injects them into context. Short prompts (< 20 characters) are skipped to minimize latency. - -### Pre-Compaction -Before context compaction, the plugin prompts Claude to store a comprehensive session summary — including goals, accomplishments, decisions, modified files, and current state — so nothing is lost. - -### Task Completed -After each task completion, the plugin prompts Claude to extract and store key learnings: successful strategies, failed approaches, architectural decisions, and new conventions. - -### Session End -When Claude finishes responding, the plugin prompts for any unstored learnings and captures transcript state via the Mem0 REST API as a background safety net. +| Hook | Event | What it does | +|------|-------|-------------| +| **Session start** | `SessionStart` | Loads prior memories and displays status banner | +| **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message; skips short prompts | +| **Pre-tool** | `PreToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tool calls | +| **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories | +| **Pre-compact** | `PreCompact` | Stores a session summary before context compaction | ## Example Workflow diff --git a/docs/integrations/codex.mdx b/docs/integrations/codex.mdx index 5203825ac0..f7f1226fe2 100644 --- a/docs/integrations/codex.mdx +++ b/docs/integrations/codex.mdx @@ -1,16 +1,9 @@ --- title: Codex -description: "Add persistent memory to OpenAI Codex with the Mem0 plugin — MCP server, memory protocol skill, and plugin marketplace support." +description: "Add persistent memory to OpenAI Codex with the Mem0 plugin — MCP server, lifecycle hooks, and SDK skill." --- -Add persistent memory to [**OpenAI Codex**](https://openai.com/index/codex/) with the Mem0 plugin. Codex forgets everything between tasks — this plugin fixes that by connecting to Mem0's cloud memory layer via MCP and using a skill-based memory protocol to automatically retrieve context and store learnings. - -## Overview - -1. **MCP Server** — Connect to Mem0's remote MCP server for memory tools (add, search, update, delete) -2. **Memory Protocol Skill** — Instructs the agent to retrieve memories at task start, store learnings on completion, and capture session state before context loss -3. **Plugin Marketplace** — Install via Codex's repo-level or personal plugin marketplace -4. **Zero local dependencies** — Cloud-hosted MCP server, no local setup required +Add persistent memory to [**OpenAI Codex**](https://openai.com/index/codex/) with the Mem0 plugin. Codex forgets everything between tasks — this plugin fixes that by connecting to Mem0's cloud memory layer via MCP, automatically capturing learnings at key lifecycle points, and retrieving relevant context before every response. ## Prerequisites @@ -38,87 +31,48 @@ source ~/.bashrc ## Installation -### Option A — Direct MCP (Recommended) - -The fastest way to connect Codex to Mem0 — no downloads, no marketplace. Codex reads MCP servers from `~/.codex/config.toml` as TOML. Add: - -```toml -[mcp_servers.mem0] -url = "https://mcp.mem0.ai/mcp" -bearer_token_env_var = "MEM0_API_KEY" -``` +### Option A — Plugin Marketplace (Recommended) -Make sure `MEM0_API_KEY` is exported in the shell you launch Codex from, then restart Codex. +Install the full plugin including MCP server, lifecycle hooks, and SDK skill. - - Codex's `codex mcp add` CLI only supports stdio MCP servers. Because Mem0's MCP is HTTP/streamable, you configure it by editing `config.toml` directly (or via the **Plugins → Connect to a custom MCP → Streamable HTTP** UI in the Codex app). - +1. Add the Mem0 marketplace: -### Option B — Sideload the Plugin (Advanced) + ```bash + codex plugin marketplace add mem0ai/mem0 + ``` -For the full plugin experience — MCP server **plus** the Mem0 SDK skill, memory protocol skill, and opt-in lifecycle hooks — sideload the plugin from a local clone. The Mem0 repo already ships a marketplace manifest at [`.agents/plugins/marketplace.json`](https://github.com/mem0ai/mem0/blob/main/.agents/plugins/marketplace.json), so there's no JSON to author by hand. This follows the Codex [build-plugins](https://developers.openai.com/codex/plugins/build) local-testing workflow. +2. Restart Codex, open the Plugin Directory, browse the **Mem0 Plugins** marketplace, and install **Mem0**. - Don't combine Option B with Option A. The plugin manifest declares its MCP server via [`.codex-mcp.json`](https://github.com/mem0ai/mem0/blob/main/mem0-plugin/.codex-mcp.json), so Codex auto-registers the `mem0` MCP server when the plugin loads. Adding the same `[mcp_servers.mem0]` block to `~/.codex/config.toml` will create a duplicate registration. + Do not combine with Option B. The plugin manifest auto-registers the `mem0` MCP server, so adding both will create a duplicate registration. -**Step 1.** Clone the Mem0 repository anywhere on disk: - -```bash -git clone https://github.com/mem0ai/mem0.git ~/codex-plugins/mem0-source -``` +### Option B — Direct MCP -**Step 2.** Register the bundled marketplace with Codex's CLI: +The fastest way to connect Codex to Mem0 — no plugin, no marketplace. Add to `~/.codex/config.toml`: -```bash -codex plugin marketplace add ~/codex-plugins/mem0-source +```toml +[mcp_servers.mem0] +url = "https://mcp.mem0.ai/mcp" +bearer_token_env_var = "MEM0_API_KEY" ``` -This points Codex at the repo's `.agents/plugins/marketplace.json`. The bundled file uses `path: "./mem0-plugin"`, which Codex resolves relative to the clone root. +Make sure `MEM0_API_KEY` is exported in the shell you launch Codex from, then restart Codex. - **Why we recommend this over hand-authoring `~/.agents/plugins/marketplace.json`:** Codex requires `source.path` in any marketplace manifest to be **relative** (starting with `./`) and **inside the marketplace root**. The repo's bundled manifest already satisfies this — the marketplace root is the clone directory, and `mem0-plugin/` lives inside it. With a personal `~/.agents/plugins/marketplace.json`, the root is `~/` and the clone has to live under `~/` too. The CLI form sidesteps that constraint. + Codex's `codex mcp add` CLI only supports stdio MCP servers. Because Mem0's MCP is HTTP/streamable, you configure it by editing `config.toml` directly (or via the **Plugins → Connect to a custom MCP → Streamable HTTP** UI in the Codex app). -**Step 3.** Restart Codex, run `/plugins`, browse the `Mem0 Plugins` marketplace, and install **Mem0**. - -**Step 4 (optional) — enable lifecycle hooks.** Codex doesn't auto-wire hooks from plugin manifests; it only reads them from `~/.codex/hooks.json` (or `/.codex/hooks.json`). Run the bundled installer once to merge the Mem0 entries into your global hooks file: - -```bash -python3 ~/codex-plugins/mem0-source/mem0-plugin/scripts/install_codex_hooks.py -``` - -Then enable the hooks feature flag in `~/.codex/config.toml`: - -```toml -[features] -codex_hooks = true -``` - -Restart Codex. The installer registers three hooks pointing at scripts inside your clone: - -| Event | Behavior | -|-------|----------| -| `SessionStart` | Loads prior memories as bootstrap context | -| `UserPromptSubmit` | Injects relevant memories before each prompt | -| `Stop` | Reminds the agent to persist learnings at turn end | - -Re-running the installer is idempotent. To remove the hooks: `python3 ~/codex-plugins/mem0-source/mem0-plugin/scripts/install_codex_hooks.py --uninstall`. - - - The hooks file stores absolute paths into your clone (e.g. `~/codex-plugins/mem0-source/mem0-plugin/scripts/...`). If you move or delete the clone, the hooks will break silently — re-run the installer from the new location, or run `--uninstall` first. - +This gives you the MCP tools but not the lifecycle hooks or SDK skill. ### Managing the Plugin -Codex provides CLI commands for managing marketplaces after install: - ```bash codex plugin marketplace upgrade # pull latest plugin versions codex plugin marketplace remove mem0-plugins # unregister the marketplace ``` -To pull updates to the plugin source itself, `git pull` inside your clone (`~/codex-plugins/mem0-source`) and then run `codex plugin marketplace upgrade` to refresh Codex's plugin cache. Plugins are cached at `~/.codex/plugins/cache////`. +To update, run `codex plugin marketplace upgrade` to pull the latest from the Mem0 repo. After either option, start a new Codex task and ask: *"List my mem0 entities"* or *"Search my memories for hello"*. If the `mem0` tools appear and respond, you're all set. @@ -126,12 +80,11 @@ To pull updates to the plugin source itself, `git pull` inside your clone (`~/co ## What's Included -| Component | Sideloaded Plugin | Direct MCP | -|-----------|:-----------------:|:----------:| +| Component | Plugin Install | MCP Only | +|-----------|:--------------:|:--------:| | MCP Server (9 memory tools) | Yes | Yes | -| Memory Protocol Skill | Yes | No | +| Lifecycle Hooks | Yes | No | | Mem0 SDK Skill | Yes | No | -| Lifecycle Hooks (opt-in) | Yes | No | ## Available MCP Tools @@ -149,49 +102,17 @@ Once installed, the following tools are available in every Codex session: | `delete_entities` | Delete a user/agent/app/run entity and its memories | | `list_entities` | List users/agents/apps/runs stored in Mem0 | -## Memory Protocol Skill - -When the plugin is sideloaded, the memory protocol skill instructs the agent to: - -### On Every New Task -1. Call `search_memories` with a query related to the current task to load relevant context -2. Review returned memories to understand what was learned in prior sessions -3. Optionally call `get_memories` to browse all stored memories - -### After Completing Significant Work -Store key learnings using `add_memory` with structured metadata: - -| What to store | Metadata type | -|--------------|---------------| -| Architectural decisions | `{"type": "decision"}` | -| Strategies that worked | `{"type": "task_learning"}` | -| Failed approaches | `{"type": "anti_pattern"}` | -| User preferences observed | `{"type": "user_preference"}` | -| Environment discoveries | `{"type": "environmental"}` | -| Conventions established | `{"type": "convention"}` | - -### Before Losing Context -Store a comprehensive session summary including goals, accomplishments, decisions, files modified, and current state with metadata `{"type": "session_state"}`. - -## Plugin Manifest - -The Codex plugin manifest (`.codex-plugin/plugin.json`) follows the Codex plugin specification: - -```json -{ - "name": "mem0", - "version": "0.2.5", - "description": "Mem0 memory layer for AI applications.", - "skills": "./skills/", - "mcpServers": "./.codex-mcp.json", - "interface": { - "displayName": "Mem0", - "shortDescription": "Persistent memory layer for AI coding workflows", - "category": "Productivity", - "capabilities": ["Read", "Write"] - } -} -``` +## Lifecycle Hooks + +When installed via the plugin marketplace, Mem0 hooks into Codex's lifecycle to automatically manage memory: + +| Hook | Event | What it does | +|------|-------|-------------| +| **Session start** | `SessionStart` | Loads prior memories and displays status banner | +| **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message | +| **Pre-tool** | `PreToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tool calls | +| **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories | +| **Pre-compact** | `PreCompact` | Stores a session summary before context compaction | ## Example Workflow @@ -214,14 +135,10 @@ You: Add WebSocket support for real-time notification delivery. ## Troubleshooting -- **"Connection failed"** — Verify `MEM0_API_KEY` is set in your shell: `echo $MEM0_API_KEY` -- **No tools appearing** — Restart your Codex session after plugin installation -- **Duplicate `mem0` MCP server / "tool collision" errors** — You combined Option A (Direct MCP) with Option B (sideload). The sideloaded plugin auto-registers `mem0` from `.codex-mcp.json`, so remove the `[mcp_servers.mem0]` block from `~/.codex/config.toml`. -- **`plugin/read failed in TUI`** — Codex can't find the plugin directory the marketplace points at. If you used `codex plugin marketplace add `, confirm the path is your clone root and that `/.agents/plugins/marketplace.json` exists. If you hand-authored `~/.agents/plugins/marketplace.json`, `source.path` must be relative (start with `./`), inside the marketplace root (`~/` for personal installs), and end in `mem0-plugin` — e.g. `"./codex-plugins/mem0-source/mem0-plugin"`. -- **Plugin not found in `/plugins`** — Run `codex plugin marketplace add ~/path/to/clone` again, or confirm the marketplace was registered with `codex plugin marketplace remove mem0-plugins` then re-add. -- **Skills not loading** — Verify the `skills` field in `plugin.json` points to a valid directory containing `SKILL.md` files. -- **Hooks not firing** — Confirm `codex_hooks = true` is in `~/.codex/config.toml` under `[features]`, and that `~/.codex/hooks.json` contains the Mem0 entries (re-run the installer if not). Restart Codex after enabling the flag. -- **Hooks broke after moving the clone** — The installer bakes absolute paths into `~/.codex/hooks.json` pointing at scripts inside your clone. If you moved or renamed the clone directory, run `python3 /mem0-plugin/scripts/install_codex_hooks.py` from the new location — the installer is idempotent and replaces the old entries. +- **"Connection failed"** — Verify `MEM0_API_KEY` is set: `echo $MEM0_API_KEY` +- **No tools appearing** — Restart your Codex session after installation +- **Duplicate `mem0` MCP / "tool collision" errors** — You combined Option A with Option B. Remove the `[mcp_servers.mem0]` block from `~/.codex/config.toml`; the plugin registers it automatically +- **Hooks not firing** — Ensure the plugin is installed via the marketplace (Option A). MCP-only installs do not include hooks diff --git a/docs/integrations/cursor.mdx b/docs/integrations/cursor.mdx index 462dc424cf..1e516af72b 100644 --- a/docs/integrations/cursor.mdx +++ b/docs/integrations/cursor.mdx @@ -5,13 +5,6 @@ description: "Add persistent memory to Cursor with the Mem0 plugin — MCP serve Add persistent memory to [**Cursor**](https://cursor.com) with the Mem0 plugin. Your AI assistant forgets everything between sessions — this plugin fixes that by connecting to Mem0's cloud memory layer via MCP, automatically capturing learnings at key lifecycle points, and retrieving relevant context before every response. -## Overview - -1. **MCP Server** — Connect to Mem0's remote MCP server for memory tools (add, search, update, delete) -2. **Lifecycle Hooks** — Automatic memory capture at session start, compaction, and user prompts (Marketplace install) -3. **SDK Skill** — Teaches the agent how to integrate the Mem0 SDK into your applications -4. **Zero local dependencies** — Cloud-hosted MCP server, no local setup required - ## Prerequisites Before setting up Mem0 with Cursor, ensure you have: @@ -111,14 +104,13 @@ Once installed, the following tools are available in every Cursor session: When installed via the Cursor Marketplace, Mem0 hooks into Cursor's lifecycle: -### Session Start -On every new session, the plugin prompts the agent to call `search_memories` to load relevant context from prior sessions. - -### User Prompt -Before processing each user message, the plugin searches Mem0 for relevant memories and injects them into context. Short prompts are skipped to minimize latency. - -### Pre-Compaction -Before context compaction, the plugin captures a comprehensive session summary so nothing is lost when the context window resets. +| Hook | Event | What it does | +|------|-------|-------------| +| **Session start** | `sessionStart` | Loads prior memories and displays status banner | +| **User prompt** | `beforeSubmitPrompt` | Searches relevant memories before each message; skips short prompts | +| **Pre-tool (2 handlers)** | `preToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tool calls | +| **Post-tool (2 handlers)** | `postToolUse` | Tracks stats, scans bash errors for related memories | +| **Pre-compact** | `preCompact` | Stores a session summary before context compaction | ## Example Workflow diff --git a/docs/integrations/opencode.mdx b/docs/integrations/opencode.mdx new file mode 100644 index 0000000000..78b57fdd5a --- /dev/null +++ b/docs/integrations/opencode.mdx @@ -0,0 +1,123 @@ +--- +title: OpenCode +description: "Add persistent memory to OpenCode with the Mem0 plugin — MCP server, lifecycle hooks, and slash commands." +--- + +Add persistent memory to [**OpenCode**](https://opencode.ai) with the Mem0 plugin. Your agent forgets everything between sessions — Mem0 fixes that by storing decisions, preferences, and learnings so they carry over automatically. + +## Prerequisites + +1. A Mem0 API key (starts with `m0-`): + - Get your API key (free sign-up at app.mem0.ai) + +2. Add it to your shell profile so it persists across sessions: + + +```bash zsh +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.zshrc && source ~/.zshrc +``` + +```bash bash +echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.bashrc && source ~/.bashrc +``` + + +## Installation + +### Option A — Plugin Install (Recommended) + +```bash +opencode plugin @mem0/opencode-plugin +``` + + +Or using this command which does the same thing: + + +```bash +bunx @mem0/opencode-plugin@latest install +``` + + + +**Or let your agent do it** — paste this into OpenCode: + +``` +Install @mem0/opencode-plugin by following https://raw.githubusercontent.com/mem0ai/mem0/main/mem0-plugin/.opencode-plugin/README.md +``` + +All commands auto-add the plugin and MCP server to your `~/.config/opencode/opencode.json`. Restart OpenCode — you get the MCP server, lifecycle hooks, and all `/mem0:` slash commands. + +### Option B — MCP Only + +If you only need the memory tools without hooks or skills, add this to your `opencode.json` (project-level or global at `~/.config/opencode/opencode.json`): + +```json +{ + "mcp": { + "mem0": { + "type": "remote", + "url": "https://mcp.mem0.ai/mcp/", + "headers": { + "Authorization": "Token {env:MEM0_API_KEY}" + }, + "oauth": false + } + } +} +``` + + + Start a new OpenCode session and ask: *"Search my memories for hello"*. If the `mem0` tools appear and respond, you're all set. + + +## What's Included + +| Component | Plugin (A) | MCP Only (B) | +|-----------|:----------:|:------------:| +| MCP Server (9 memory tools) | Yes | Yes | +| Lifecycle Hooks | Yes | No | +| 16 Slash Commands | Yes | No | + +## Available MCP Tools + +| Tool | Description | +|------|-------------| +| `add_memory` | Save text or conversation history for a user/agent | +| `search_memories` | Semantic search across memories with filters | +| `get_memories` | List memories with filters and pagination | +| `get_memory` | Retrieve a specific memory by ID | +| `update_memory` | Overwrite a memory's text by ID | +| `delete_memory` | Delete a single memory by ID | +| `delete_all_memories` | Bulk delete all memories in scope | +| `delete_entities` | Delete a user/agent/app/run entity and its memories | +| `list_entities` | List users/agents/apps/runs stored in Mem0 | + +## Lifecycle Hooks + +The plugin uses the [mem0ai](https://www.npmjs.com/package/mem0ai) TypeScript SDK directly — pure TypeScript, no Python, no shell scripts. + +| OpenCode Event | Hook | What happens | +|----------------|------|-------------| +| `chat.message` | **Chat message** | Searches prior memories on session start, searches relevant memories before each prompt, auto-captures learnings periodically | +| `tool.execute.before` | **Pre-tool** | Blocks MEMORY.md writes, injects `user_id`/`app_id` on mem0 tool calls | +| `tool.execute.after` | **Post-tool** | Tracks stats, scans Bash errors and pre-fetches related error memories | +| `experimental.chat.system.transform` | **System transform** | Injects memory context (session memories, search results, error lookups) into the system prompt | +| `experimental.session.compacting` | **Compaction** | Stores session state memory, then injects prior memories into compaction context so nothing is lost | +| `shell.env` | **Shell env** | Exports `MEM0_USER_ID`, `MEM0_APP_ID`, `MEM0_SESSION_ID`, and `MEM0_BRANCH` to all shell executions | + +## Troubleshooting + +- **No tools appearing** — Restart OpenCode after installing +- **"Connection failed"** — Verify your key is set: `echo $MEM0_API_KEY` +- **Plugin not loading** — Run `opencode plugin @mem0/opencode-plugin` again, then restart +- **Hooks not firing** — Hooks require the plugin install (Option A). MCP-only installs don't include hooks. + + + + Detailed MCP configuration for all clients + + + Add Mem0 memory to Google Antigravity + + diff --git a/docs/llms.txt b/docs/llms.txt index 9e8490c0f6..947d76f709 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -268,6 +268,8 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f - [Claude Code](https://docs.mem0.ai/integrations/claude-code) [Both]: Use when wiring memory into Claude Code. - [Cursor](https://docs.mem0.ai/integrations/cursor) [Both]: Use when wiring memory into Cursor. - [Codex](https://docs.mem0.ai/integrations/codex) [Both]: Use when wiring memory into Codex / other editor assistants. +- [OpenCode](https://docs.mem0.ai/integrations/opencode) [Both]: Use when wiring memory into OpenCode. +- [Antigravity](https://docs.mem0.ai/integrations/antigravity) [Both]: Use when wiring memory into Google Antigravity. ### Voice & Real-time - [LiveKit](https://docs.mem0.ai/integrations/livekit) [Both]: Use when building real-time voice/video with memory. @@ -396,13 +398,15 @@ Each subdirectory is a Claude Code Skill (`SKILL.md` + supporting assets). Load Source: https://github.com/mem0ai/mem0/tree/main/mem0-plugin -The `mem0-plugin/` directory provides MCP server connection, lifecycle hooks, and skill bundling for Claude Code, Cursor, and Codex. It exposes 9 MCP tools: `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`. +The `mem0-plugin/` directory provides MCP server connection, lifecycle hooks, and skill bundling for Claude Code, Cursor, Codex, OpenCode, and Antigravity. It exposes 9 MCP tools: `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`. Editor-specific setup docs (already listed above under `## Integrations > AI Coding Tools`): - `integrations/claude-code` [Both] - `integrations/cursor` [Both] - `integrations/codex` [Both] +- `integrations/opencode` [Both] +- `integrations/antigravity` [Both] - `integrations/openclaw` [Both] ### MCP Endpoints diff --git a/marketplace.json b/marketplace.json new file mode 100644 index 0000000000..6e0e240a1e --- /dev/null +++ b/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "mem0-plugins", + "interface": { + "displayName": "Mem0 Plugins" + }, + "plugins": [ + { + "name": "mem0", + "source": { + "source": "local", + "path": "./mem0-plugin" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" + } + ] +} diff --git a/mem0-plugin/.antigravity-mcp.json b/mem0-plugin/.antigravity-mcp.json new file mode 100644 index 0000000000..86e2428d5f --- /dev/null +++ b/mem0-plugin/.antigravity-mcp.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "mem0": { + "serverUrl": "https://mcp.mem0.ai/mcp/", + "headers": { + "Authorization": "Token ${MEM0_API_KEY}" + } + } + } +} diff --git a/mem0-plugin/.antigravity/hooks/hooks.json b/mem0-plugin/.antigravity/hooks/hooks.json new file mode 100644 index 0000000000..ddf38b9003 --- /dev/null +++ b/mem0-plugin/.antigravity/hooks/hooks.json @@ -0,0 +1,81 @@ +{ + "hooks": { + "SessionStart": [ + { + "matcher": "*", + "hooks": [ + { + "name": "mem0-ensure-deps", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/ensure_deps.sh 2>/dev/null || true", + "timeout": 60 + }, + { + "name": "mem0-session-start", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/on_session_start.sh 2>/dev/null || true" + } + ] + } + ], + "UserPromptSubmit": [ + { + "hooks": [ + { + "name": "mem0-user-prompt", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/on_user_prompt.sh 2>/dev/null || true", + "timeout": 8 + } + ] + } + ], + "PreToolUse": [ + { + "matcher": "Write|Edit|MultiEdit", + "hooks": [ + { + "name": "mem0-block-memory-write", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/block_memory_write.sh" + } + ] + }, + { + "matcher": "mcp__mem0__.*|mcp__plugin_mem0_mem0__.*", + "hooks": [ + { + "name": "mem0-enforce-metadata", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/enforce_metadata_defaults.sh", + "timeout": 3 + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "mcp__mem0__.*|mcp__plugin_mem0_mem0__.*", + "hooks": [ + { + "name": "mem0-post-tool", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/on_post_tool_use.sh", + "timeout": 3 + } + ] + }, + { + "matcher": "Bash", + "hooks": [ + { + "name": "mem0-bash-output", + "type": "command", + "command": "ANTIGRAVITY_PLUGIN_ROOT=${extensionPath} CLAUDE_PLUGIN_ROOT=${extensionPath} bash ${extensionPath}/scripts/on_bash_output.sh", + "timeout": 5 + } + ] + } + ] + } +} diff --git a/mem0-plugin/.antigravity/mcp_config.json b/mem0-plugin/.antigravity/mcp_config.json new file mode 100644 index 0000000000..86e2428d5f --- /dev/null +++ b/mem0-plugin/.antigravity/mcp_config.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "mem0": { + "serverUrl": "https://mcp.mem0.ai/mcp/", + "headers": { + "Authorization": "Token ${MEM0_API_KEY}" + } + } + } +} diff --git a/mem0-plugin/.antigravity/plugin.json b/mem0-plugin/.antigravity/plugin.json new file mode 100644 index 0000000000..2f9086c5a8 --- /dev/null +++ b/mem0-plugin/.antigravity/plugin.json @@ -0,0 +1,13 @@ +{ + "id": "mem0", + "name": "mem0", + "version": "0.1.0", + "description": "Persistent semantic memory for Antigravity agents. Cross-session, user-level recall via the Mem0 Platform MCP server. 16 slash commands, lifecycle hooks for auto-capture and metadata enforcement.", + "author": { "name": "Mem0", "email": "support@mem0.ai" }, + "publisher": "mem0ai", + "homepage": "https://mem0.ai", + "repository": "https://github.com/mem0ai/mem0", + "license": "Apache-2.0", + "keywords": ["memory", "persistence", "personalization", "mcp", "semantic-search"], + "contextFileName": "AGENTS.md" +} diff --git a/mem0-plugin/.antigravity/scripts b/mem0-plugin/.antigravity/scripts new file mode 120000 index 0000000000..a339954dff --- /dev/null +++ b/mem0-plugin/.antigravity/scripts @@ -0,0 +1 @@ +../scripts \ No newline at end of file diff --git a/mem0-plugin/.antigravity/skills b/mem0-plugin/.antigravity/skills new file mode 120000 index 0000000000..42c5394a18 --- /dev/null +++ b/mem0-plugin/.antigravity/skills @@ -0,0 +1 @@ +../skills \ No newline at end of file diff --git a/mem0-plugin/.claude-plugin/plugin.json b/mem0-plugin/.claude-plugin/plugin.json index 1bd3778b91..30add9c393 100644 --- a/mem0-plugin/.claude-plugin/plugin.json +++ b/mem0-plugin/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mem0", - "version": "0.2.6", + "version": "0.2.7", "description": "Persistent memory for Claude Code. Remembers decisions, patterns, and preferences across sessions.", "author": { "name": "Mem0", @@ -14,7 +14,7 @@ "api_key": { "type": "string", "title": "Mem0 API Key", - "description": "Your Mem0 Platform API key. Get one at https://app.mem0.ai/dashboard/api-keys. Alternative: export MEM0_API_KEY in your shell profile.", + "description": "Your Mem0 Platform API key (starts with m0-). Get one at https://app.mem0.ai/dashboard/api-keys", "sensitive": true, "required": true } diff --git a/mem0-plugin/.codex-plugin/plugin.json b/mem0-plugin/.codex-plugin/plugin.json index 2a5d4d368d..9de0e2f048 100644 --- a/mem0-plugin/.codex-plugin/plugin.json +++ b/mem0-plugin/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mem0", - "version": "0.2.6", + "version": "0.2.7", "description": "Persistent memory for Codex. Remembers decisions, patterns, and preferences across sessions.", "author": { "name": "Mem0", diff --git a/mem0-plugin/.cursor-plugin/plugin.json b/mem0-plugin/.cursor-plugin/plugin.json index 90b1b4802d..e9d3616008 100644 --- a/mem0-plugin/.cursor-plugin/plugin.json +++ b/mem0-plugin/.cursor-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mem0", - "version": "0.2.6", + "version": "0.2.7", "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search using the Mem0 Platform MCP server.", "author": { "name": "Mem0", diff --git a/mem0-plugin/.mcp.json b/mem0-plugin/.mcp.json index a99e7ba07a..dd091bc0cf 100644 --- a/mem0-plugin/.mcp.json +++ b/mem0-plugin/.mcp.json @@ -5,8 +5,7 @@ "url": "https://mcp.mem0.ai/mcp/", "headers": { "Authorization": "Token ${MEM0_API_KEY}" - }, - "authorizationUrl": "https://mcp.mem0.ai/authorize" + } } } } diff --git a/mem0-plugin/.opencode-plugin/LICENSE b/mem0-plugin/.opencode-plugin/LICENSE new file mode 100644 index 0000000000..bfa36bdd4e --- /dev/null +++ b/mem0-plugin/.opencode-plugin/LICENSE @@ -0,0 +1,201 @@ + 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 + (an example is provided in the Appendix below). + + "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 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 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 those 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2026] [Taranjeet Singh] + + 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. diff --git a/mem0-plugin/.opencode-plugin/README.md b/mem0-plugin/.opencode-plugin/README.md new file mode 100644 index 0000000000..fac980ad70 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/README.md @@ -0,0 +1,84 @@ +# @mem0/opencode-plugin + +Persistent memory for [OpenCode](https://opencode.ai). Your agent remembers decisions, preferences, and learnings across sessions automatically. + +## Install + +```bash +bunx @mem0/opencode-plugin@latest install +``` + +Or using OpenCode's built-in CLI: + +```bash +opencode plugin @mem0/opencode-plugin +``` + +**Or let your agent do it** — paste this into OpenCode: + +``` +Install @mem0/opencode-plugin by following https://raw.githubusercontent.com/mem0ai/mem0/main/mem0-plugin/.opencode-plugin/README.md +``` + +All commands auto-add the plugin and MCP server to your `~/.config/opencode/opencode.json`. No manual config needed. + +Get your API key (free): [app.mem0.ai/dashboard/api-keys](https://app.mem0.ai/dashboard/api-keys) + +```bash +echo 'export MEM0_API_KEY="m0-your-key"' >> ~/.zshrc && source ~/.zshrc +``` + +Restart OpenCode. + +## What's included + +| Component | Description | +|-----------|-------------| +| **MCP Server** | 9 memory tools — add, search, get, update, delete memories | +| **Lifecycle Hooks** | Auto-search on session start and every prompt, metadata enforcement, error memory lookup, compaction context | +| **16 Slash Commands** | `/mem0:remember`, `/mem0:tour`, `/mem0:stats`, `/mem0:health`, `/mem0:dream`, and more | + +## Hooks + +Pure TypeScript — no Python, no shell scripts. Uses the [mem0ai](https://www.npmjs.com/package/mem0ai) SDK directly. + +| Hook | Event | What it does | +|------|-------|-------------| +| **Chat message** | `chat.message` | Loads prior memories on session start, searches relevant memories before each prompt, auto-captures learnings periodically | +| **Pre-tool** | `tool.execute.before` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tools | +| **Post-tool** | `tool.execute.after` | Tracks stats, scans bash errors for related memories | +| **System transform** | `experimental.chat.system.transform` | Injects memory context (session memories, search results, error lookups) into system prompt | +| **Compaction** | `experimental.session.compacting` | Stores session state memory, then injects prior memories into compaction context so nothing is lost | +| **Shell env** | `shell.env` | Exports `MEM0_USER_ID`, `MEM0_APP_ID`, `MEM0_SESSION_ID`, and `MEM0_BRANCH` to shell | + +## MCP Tools + +| Tool | Description | +|------|-------------| +| `add_memory` | Save text or conversation history | +| `search_memories` | Semantic search across memories | +| `get_memories` | List memories with filters and pagination | +| `get_memory` | Retrieve a specific memory by ID | +| `update_memory` | Overwrite a memory's text by ID | +| `delete_memory` | Delete a single memory by ID | +| `delete_all_memories` | Bulk delete all memories in scope | +| `delete_entities` | Delete an entity and its memories | +| `list_entities` | List users/agents/apps stored in Mem0 | + +## Verify + +Start OpenCode and ask: *"Search my memories for recent decisions"* + +If the `mem0` tools respond, you're all set. + +## Troubleshooting + +| Problem | Fix | +|---------|-----| +| No tools appearing | Restart OpenCode after installing | +| 401 Unauthorized | `echo $MEM0_API_KEY` must print your `m0-` key | +| Plugin not loading | Run `opencode plugin @mem0/opencode-plugin` again | + +## License + +Apache-2.0 diff --git a/mem0-plugin/.opencode-plugin/cli.ts b/mem0-plugin/.opencode-plugin/cli.ts new file mode 100644 index 0000000000..6d02f21782 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/cli.ts @@ -0,0 +1,254 @@ +#!/usr/bin/env bun +import { + readFileSync, + writeFileSync, + existsSync, + mkdirSync, + copyFileSync, + readdirSync, + rmSync, + statSync, +} from "fs"; +import { join, dirname } from "path"; +import { homedir } from "os"; + +const PLUGIN_NAME = "@mem0/opencode-plugin"; +const MCP_CONFIG = { + mem0: { + type: "remote", + url: "https://mcp.mem0.ai/mcp/", + headers: { + Authorization: "Token {env:MEM0_API_KEY}", + }, + oauth: false, + }, +}; + +const SKILLS_NAMESPACE = "mem0"; + +function getConfigDir(): string { + const dir = join(homedir(), ".config", "opencode"); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + return dir; +} + +function getConfigPath(): string { + const configDir = getConfigDir(); + const jsonc = join(configDir, "opencode.jsonc"); + if (existsSync(jsonc)) return jsonc; + return join(configDir, "opencode.json"); +} + +function stripJsonComments(text: string): string { + let result = ""; + let i = 0; + let inString = false; + let escape = false; + while (i < text.length) { + const ch = text[i]; + if (escape) { + result += ch; + escape = false; + i++; + continue; + } + if (inString) { + if (ch === "\\") escape = true; + else if (ch === '"') inString = false; + result += ch; + i++; + continue; + } + if (ch === '"') { + inString = true; + result += ch; + i++; + continue; + } + if (ch === "/" && text[i + 1] === "/") { + while (i < text.length && text[i] !== "\n") i++; + continue; + } + if (ch === "/" && text[i + 1] === "*") { + i += 2; + while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++; + i += 2; + continue; + } + result += ch; + i++; + } + return result; +} + +function resolvePluginDir(): string { + try { + return dirname(new URL(import.meta.url).pathname); + } catch {} + return __dirname ?? process.cwd(); +} + +function findSkillsDir(): string { + const base = resolvePluginDir(); + const candidates = [ + join(base, "opencode-skills"), + join(dirname(base), "opencode-skills"), + join(base, "..", "opencode-skills"), + ]; + for (const c of candidates) { + try { + if (existsSync(c) && statSync(c).isDirectory()) return c; + } catch {} + } + return ""; +} + +function installSkills(): number { + const skillsSource = findSkillsDir(); + if (!skillsSource) { + console.log(" ! Skills directory not found — skipping slash command install"); + return 0; + } + + const skillsTarget = join(getConfigDir(), "skills"); + if (!existsSync(skillsTarget)) mkdirSync(skillsTarget, { recursive: true }); + + let count = 0; + const entries = readdirSync(skillsSource); + + for (const name of entries) { + const skillDir = join(skillsSource, name); + try { + if (!statSync(skillDir).isDirectory()) continue; + } catch { + continue; + } + + const skillFile = join(skillDir, "SKILL.md"); + if (!existsSync(skillFile)) continue; + + const targetDir = join(skillsTarget, `${SKILLS_NAMESPACE}-${name}`); + if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true }); + + copyFileSync(skillFile, join(targetDir, "SKILL.md")); + count++; + } + + return count; +} + +function uninstallSkills(): number { + const skillsTarget = join(getConfigDir(), "skills"); + if (!existsSync(skillsTarget)) return 0; + + let count = 0; + const entries = readdirSync(skillsTarget); + + for (const name of entries) { + if (!name.startsWith(`${SKILLS_NAMESPACE}-`)) continue; + const fullPath = join(skillsTarget, name); + try { + if (!statSync(fullPath).isDirectory()) continue; + rmSync(fullPath, { recursive: true }); + count++; + } catch {} + } + + return count; +} + +function install() { + console.log("Installing Mem0 plugin for OpenCode...\n"); + + const configPath = getConfigPath(); + let config: any = {}; + + if (existsSync(configPath)) { + try { + const raw = readFileSync(configPath, "utf-8"); + config = JSON.parse(stripJsonComments(raw)); + } catch { + console.log(` ! Could not parse ${configPath}, creating fresh config`); + config = {}; + } + } + + if (!Array.isArray(config.plugin)) config.plugin = []; + if (!config.plugin.includes(PLUGIN_NAME)) { + config.plugin.push(PLUGIN_NAME); + console.log(` + Added "${PLUGIN_NAME}" to plugin array`); + } else { + console.log(` ~ "${PLUGIN_NAME}" already in plugin array`); + } + + if (!config.mcp) config.mcp = {}; + if (!config.mcp.mem0) { + config.mcp.mem0 = MCP_CONFIG.mem0; + console.log(" + Added mem0 MCP server config"); + } else { + console.log(" ~ mem0 MCP server already configured"); + } + + writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n"); + console.log(`\n Wrote ${configPath}`); + + const skillCount = installSkills(); + if (skillCount > 0) { + console.log(` + Installed ${skillCount} slash commands to ~/.config/opencode/skills/`); + } + + console.log(""); + + if (!process.env.MEM0_API_KEY) { + console.log(" ! MEM0_API_KEY is not set in your environment."); + console.log( + ' Run: echo \'export MEM0_API_KEY="m0-your-key"\' >> ~/.zshrc && source ~/.zshrc', + ); + console.log( + " Get a free key at: https://app.mem0.ai/dashboard/api-keys\n", + ); + } else { + console.log(" MEM0_API_KEY detected\n"); + } + + console.log("Done! Restart OpenCode to activate Mem0."); + console.log(" Then run /mem0-onboard in the TUI to complete setup.\n"); +} + +function uninstall() { + const configPath = getConfigPath(); + if (!existsSync(configPath)) { + console.log("No OpenCode config found. Nothing to remove."); + return; + } + + const raw = readFileSync(configPath, "utf-8"); + const config = JSON.parse(stripJsonComments(raw)); + + if (Array.isArray(config.plugin)) { + config.plugin = config.plugin.filter((p: string) => p !== PLUGIN_NAME); + if (config.plugin.length === 0) delete config.plugin; + } + + if (config.mcp?.mem0) { + delete config.mcp.mem0; + if (Object.keys(config.mcp).length === 0) delete config.mcp; + } + + writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n"); + console.log(` Removed plugin + MCP from ${configPath}`); + + const skillCount = uninstallSkills(); + if (skillCount > 0) { + console.log(` Removed ${skillCount} slash commands from ~/.config/opencode/skills/`); + } + + console.log("Restart OpenCode to complete removal."); +} + +const cmd = process.argv[2]; +if (cmd === "uninstall" || cmd === "remove") { + uninstall(); +} else { + install(); +} diff --git a/mem0-plugin/.opencode-plugin/opencode-mem0.ts b/mem0-plugin/.opencode-plugin/opencode-mem0.ts new file mode 100644 index 0000000000..dce4fc01ba --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-mem0.ts @@ -0,0 +1,577 @@ +import type { Plugin } from "@opencode-ai/plugin"; +import { MemoryClient } from "mem0ai"; +import { userInfo } from "os"; +import { basename, resolve, dirname } from "path"; +import { randomBytes } from "crypto"; +import { existsSync, readdirSync, cpSync } from "fs"; + +async function getUserId(): Promise { + if (process.env.MEM0_USER_ID) return process.env.MEM0_USER_ID; + try { + return userInfo().username; + } catch {} + return process.env.USER || process.env.USERNAME || "unknown"; +} + +async function getProjectId($: any): Promise { + if (process.env.MEM0_APP_ID) return process.env.MEM0_APP_ID; + try { + const r = await $`git remote get-url origin`.quiet(); + const remote = r.stdout.toString().trim(); + const m = remote.match(/[:/]([^/]+\/[^/]+?)(?:\.git)?$/); + if (m) return m[1].replace("/", "-"); + } catch {} + return basename(process.cwd()); +} + +async function getBranch($: any): Promise { + try { + const r = await $`git branch --show-current`.quiet(); + return r.stdout.toString().trim() || "main"; + } catch {} + return "main"; +} + +function extractMemories(res: any): Array<{ memory: string; id: string }> { + const arr = res?.results ?? res; + if (!Array.isArray(arr)) return []; + return arr.map((m: any) => ({ memory: m.memory ?? "", id: m.id ?? "" })); +} + +function generateSessionId(): string { + const ts = Math.floor(Date.now() / 1000); + const rnd = randomBytes(3).toString("hex"); + return `ses_${ts}_${rnd}`; +} + +const SECRET_PATTERNS = [ + /sk-[A-Za-z0-9]{20,}/g, + /m0-[A-Za-z0-9]{20,}/g, + /AKIA[0-9A-Z]{16}/g, + /xox[baprs]-[A-Za-z0-9-]{20,}/g, + /ghp_[A-Za-z0-9]{36,}/g, + /gho_[A-Za-z0-9]{36,}/g, +]; + +function redact(text: string): string { + let out = text; + for (const re of SECRET_PATTERNS) { + out = out.replace(re, "[REDACTED]"); + } + return out; +} + +const NUDGE_RE = + /\b(remember\s+(this|that)|memorize|save\s+this|note\s+(this|that)|don'?t\s+forget|always\s+remember|never\s+forget|keep\s+(this|that)\s+in\s+(mind|memory)|store\s+(this|that))\b/i; + +const RESUME_RE = + /where\s+(did\s+)?(we|I)\s+(leave|left)\s+off|continue\s+(from\s+)?(where|last)|what\s+were\s+we\s+(working|doing)|pick\s+up\s+where|resume\s+(from\s+|where\s+)|what.s\s+the\s+(current|latest)\s+(state|status)|catch\s+me\s+up|where\s+are\s+we/i; + +const ERROR_STRONG_RE = + /Traceback \(most recent call last\)|panic: |FATAL:|error\[E\d+\]/; +const ERROR_MULTI_RE = /(Error:|Exception:)/g; + +const MEM0_MCP_RE = /mem0.*(?:add_memory|search_memories|get_memor|delete_memor|update_memory|delete_entities|list_entities)/i; +const WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "write", "edit", "multiEdit"]); + +function isMem0Tool(name: string): boolean { + return MEM0_MCP_RE.test(name); +} + +function isMem0AddMemory(name: string): boolean { + return /mem0.*add_memory/i.test(name); +} + +function isMem0SearchOrGet(name: string): boolean { + return /mem0.*(search_memories|get_memories)/i.test(name); +} + +function isMem0DeleteAll(name: string): boolean { + return /mem0.*delete_all_memories/i.test(name); +} + +function extractUserText(input: any, output: any): string { + const parts: any[] = output?.parts; + if (Array.isArray(parts)) { + return parts + .filter((p: any) => p.type === "text" && !p.synthetic) + .map((p: any) => p.text ?? "") + .join("\n"); + } + const msg = output?.message ?? input?.message; + if (typeof msg?.content === "string") return msg.content; + if (typeof msg?.text === "string") return msg.text; + return ""; +} + +function installSkills(projectDir: string): void { + const pluginDir = dirname(dirname(import.meta.filename)); + const srcSkills = resolve(pluginDir, "opencode-skills"); + if (!existsSync(srcSkills)) return; + + const destSkills = resolve(projectDir, ".opencode", "skills"); + const destCommands = resolve(projectDir, ".opencode", "commands"); + try { + const skills = readdirSync(srcSkills, { withFileTypes: true }); + for (const entry of skills) { + if (!entry.isDirectory()) continue; + const dest = resolve(destSkills, entry.name); + if (existsSync(dest)) continue; + cpSync(resolve(srcSkills, entry.name), dest, { recursive: true }); + } + + for (const entry of skills) { + if (!entry.isDirectory()) continue; + const cmdFile = resolve(destCommands, `mem0-${entry.name}.md`); + if (existsSync(cmdFile)) continue; + const skillMd = resolve(srcSkills, entry.name, "SKILL.md"); + if (!existsSync(skillMd)) continue; + let desc = "Mem0 " + entry.name + " skill"; + try { + const content = require("fs").readFileSync(skillMd, "utf8"); + const m = content.match(/^description:\s*(.+)$/m); + if (m) desc = m[1].trim(); + } catch {} + const cmdContent = `---\ndescription: ${desc}\n---\nLoad and follow the skill at .opencode/skills/${entry.name}/SKILL.md\n\nUse the mem0 MCP tools (search_memories, get_memories, add_memory, delete_memory, update_memory, list_entities, delete_entities, get_event_status) to execute the skill instructions.\n\nIdentity context (from environment):\n- user_id: Use MEM0_USER_ID env var, or fall back to $USER\n- app_id: Use MEM0_APP_ID env var\n- session_id: Use MEM0_SESSION_ID env var\n- branch: Use MEM0_BRANCH env var\n`; + try { + require("fs").mkdirSync(destCommands, { recursive: true }); + require("fs").writeFileSync(cmdFile, cmdContent, "utf8"); + } catch {} + } + } catch {} +} + +export const Mem0Plugin: Plugin = async (ctx) => { + const { $, client } = ctx; + + try { + const projectDir = (ctx as any).directory ?? (ctx as any).worktree ?? process.cwd(); + installSkills(projectDir); + } catch {} + + const apiKey = process.env.MEM0_API_KEY; + + if (!apiKey) { + try { + await client.app.log({ + body: { + service: "mem0", + level: "warn", + message: + "MEM0_API_KEY not set. Get one at https://app.mem0.ai/dashboard/api-keys", + }, + }); + } catch {} + return {}; + } + + const mem0 = new MemoryClient({ apiKey }); + const userId = await getUserId(); + const appId = await getProjectId($); + const branch = await getBranch($); + const stats = { adds: 0, searches: 0, messages: 0 }; + const sessionId = generateSessionId(); + + let initialized = false; + let memoryCount = 0; + let msgCount = 0; + + const systemContext: string[] = []; + + return { + "chat.message": async (input: any, output: any) => { + const userText = extractUserText(input, output); + if (!userText || userText.length < 10) return; + + const safeText = redact(userText); + msgCount++; + stats.messages++; + + if (!initialized) { + initialized = true; + + try { + const all = await mem0.getAll({ + filters: { + AND: [{ user_id: userId }, { app_id: appId }], + }, + page: 1, + pageSize: 1, + }); + memoryCount = + (all as any)?.count ?? + (all as any)?.results?.length ?? + 0; + + systemContext.push( + `Mem0 Active | user=${userId} | project=${appId} | branch=${branch} | memories=${memoryCount}`, + ); + systemContext.push( + `Always include user_id="${userId}" and app_id="${appId}" in every search_memories filter and add_memory call.`, + ); + + if (memoryCount === 0) { + systemContext.push( + "New project with 0 memories. Suggest running /mem0:onboard to import project files and install coding categories.", + ); + } + + if (memoryCount > 0) { + systemContext.push( + "Search mem0 for recent decisions and task learnings before responding. Run 2 parallel searches: one for decision type, one for task_learning type.", + ); + try { + const res = await mem0.search( + "recent session state decisions and learnings", + { + filters: { + AND: [{ user_id: userId }, { app_id: appId }], + }, + topK: 5, + }, + ); + stats.searches++; + const memories = extractMemories(res); + if (memories.length > 0) { + const memLines = memories + .map((m) => `- ${m.memory}`) + .join("\n"); + systemContext.push(`Prior context from mem0:\n${memLines}`); + } + } catch {} + } + + systemContext.push( + "Mem0 searches apply when user references past work, decision questions, errors, or non-trivial tasks. Queries use noun-phrases, 2-4 parallel calls with different metadata.type filters, and include user_id + app_id.", + ); + } catch (err: any) { + try { + await client.app.log({ + body: { + service: "mem0", + level: "error", + message: `Session init error: ${err?.message}`, + }, + }); + } catch {} + } + } + + if (NUDGE_RE.test(safeText)) { + systemContext.push( + "[MEMORY TRIGGER] User asked to remember something. Call add_memory with the user's statement, confidence=1.0, infer=false.", + ); + } + + const hasResume = RESUME_RE.test(safeText); + if (hasResume) { + try { + const [stateRes, decisionsRes] = await Promise.all([ + mem0.search("session state current task", { + filters: { + AND: [ + { user_id: userId }, + { app_id: appId }, + { metadata: { type: "session_state" } }, + ], + }, + topK: 3, + }), + mem0.search("recent decisions and learnings", { + filters: { + AND: [ + { user_id: userId }, + { app_id: appId }, + { metadata: { type: "decision" } }, + ], + }, + topK: 3, + }), + ]); + stats.searches += 2; + const all = [ + ...extractMemories(stateRes), + ...extractMemories(decisionsRes), + ]; + const seen = new Set(); + const unique = all.filter((m) => { + if (seen.has(m.id)) return false; + seen.add(m.id); + return true; + }); + if (unique.length > 0) { + const memLines = unique.map((m) => `- ${m.memory}`).join("\n"); + systemContext.push( + `Session resume context:\n${memLines}\n\nThese memories provide context for resuming work.`, + ); + } + } catch {} + } + + if (!hasResume && memoryCount > 0) { + try { + const res = await mem0.search(safeText, { + filters: { AND: [{ user_id: userId }, { app_id: appId }] }, + topK: 5, + }); + stats.searches++; + const memories = extractMemories(res); + if (memories.length > 0) { + const memLines = memories.map((m) => `- ${m.memory}`).join("\n"); + systemContext.push(`Relevant memories:\n${memLines}`); + } + } catch {} + } + + if (msgCount % 3 === 0) { + Promise.resolve().then(async () => { + try { + await mem0.add([{ role: "user", content: safeText }], { + user_id: userId, + app_id: appId, + metadata: { + type: "auto_capture", + source: "opencode", + confidence: 0.7, + session_id: sessionId, + branch, + }, + infer: true, + } as any); + stats.adds++; + } catch {} + }); + } + + if (msgCount % 5 === 0 && stats.adds < Math.floor(msgCount / 3)) { + systemContext.push( + "After responding, store any new decisions, learnings, or preferences from this exchange via add_memory. Keep it to 1 sentence per memory.", + ); + } + }, + + "experimental.chat.system.transform": async ( + _input: any, + output: { system: string[] }, + ) => { + if (systemContext.length > 0 && output?.system) { + const block = `## Mem0 Memory Context\n\n${systemContext.join("\n\n")}`; + output.system.push(block); + } + }, + + "tool.execute.before": async (input: any, output: any) => { + const toolName: string = input?.tool ?? ""; + + if (WRITE_TOOLS.has(toolName)) { + const fp = String( + output?.args?.file_path ?? output?.args?.filePath ?? "", + ); + if (/MEMORY\.md|\.claude\/memory/i.test(fp)) { + throw new Error( + "Use the add_memory MCP tool instead of writing to MEMORY.md", + ); + } + } + + if (isMem0Tool(toolName) && output?.args) { + if (!output.args.user_id) output.args.user_id = userId; + if (!output.args.app_id) output.args.app_id = appId; + + if (isMem0AddMemory(toolName)) { + if (!output.args.metadata) output.args.metadata = {}; + const meta = output.args.metadata; + if (meta.confidence === undefined) meta.confidence = 0.7; + if (!meta.source) meta.source = "opencode"; + if (!meta.type) meta.type = "task_learning"; + if (!meta.session_id) meta.session_id = sessionId; + if (!meta.files) meta.files = ["*"]; + if (!meta.branch) meta.branch = branch; + if (meta.confidence >= 1.0 && output.args.infer === undefined) { + output.args.infer = false; + } + } + + if (isMem0SearchOrGet(toolName)) { + const existingFilters = output.args.filters; + if (existingFilters === undefined || existingFilters === null) { + output.args.filters = { + AND: [{ user_id: userId }, { app_id: appId }], + }; + } else if (typeof existingFilters === "object") { + const andClauses: any[] = existingFilters.AND; + if (Array.isArray(andClauses)) { + const hasUid = andClauses.some( + (c: any) => c && typeof c === "object" && "user_id" in c, + ); + const hasAid = andClauses.some( + (c: any) => c && typeof c === "object" && "app_id" in c, + ); + if (!hasUid) andClauses.push({ user_id: userId }); + if (!hasAid) andClauses.push({ app_id: appId }); + } else if (andClauses === undefined) { + const hasUid = "user_id" in existingFilters; + const hasAid = "app_id" in existingFilters; + if (!hasUid || !hasAid) { + const existing = Object.entries(existingFilters).map( + ([k, v]) => ({ [k]: v }), + ); + if (!hasUid) existing.push({ user_id: userId }); + if (!hasAid) existing.push({ app_id: appId }); + output.args.filters = { AND: existing }; + } + } + } + } + + if (isMem0DeleteAll(toolName)) { + if (!output.args.user_id) output.args.user_id = userId; + if (!output.args.app_id) output.args.app_id = appId; + } + + if (!isMem0AddMemory(toolName) && !output.args.metadata) { + output.args.metadata = { source: "opencode", branch }; + } + } + }, + + "tool.execute.after": async (input: any, _output: any) => { + const toolName: string = input?.tool ?? ""; + const toolOutput: string = input?.output ?? _output?.output ?? ""; + + if (MEM0_MCP_RE.test(toolName)) { + if (toolName.includes("add_memory")) stats.adds++; + if (toolName.includes("search")) stats.searches++; + } + + if (toolName === "bash" && toolOutput.length >= 50) { + const command: string = input?.args?.command ?? ""; + if (/git\s+(commit|merge|rebase)/.test(command)) return; + + const hasStrongError = ERROR_STRONG_RE.test(toolOutput); + const multiErrors = (toolOutput.match(ERROR_MULTI_RE) ?? []).length; + if (!hasStrongError && multiErrors < 2) return; + + try { + const errorLine = + toolOutput + .split("\n") + .find((l: string) => + /Error:|Exception:|panic:|FAIL:|fatal:/i.test(l), + ) + ?.replace(/^\s+/, "") + .slice(0, 120) ?? ""; + + const traceFiles = [ + ...new Set( + toolOutput.match( + /[a-zA-Z0-9_./-]+\.(py|ts|tsx|js|jsx|rs|go|rb|java|sh)(:\d+)?/g, + ) ?? [], + ), + ].slice(0, 5); + + const errorQuery = errorLine.slice(0, 80); + if (errorQuery.length < 10) return; + + const [antiPatternRes, bugFixRes] = await Promise.all([ + mem0.search(`error: ${errorQuery}`, { + filters: { + AND: [ + { user_id: userId }, + { app_id: appId }, + { metadata: { type: "anti_pattern" } }, + ], + }, + topK: 3, + }), + mem0.search(`error: ${errorQuery}`, { + filters: { + AND: [ + { user_id: userId }, + { app_id: appId }, + { metadata: { type: "bug_fix" } }, + ], + }, + topK: 3, + }), + ]); + stats.searches += 2; + + const allResults = [ + ...extractMemories(antiPatternRes), + ...extractMemories(bugFixRes), + ]; + const seen = new Set(); + const unique = allResults.filter((m) => { + if (seen.has(m.id)) return false; + seen.add(m.id); + return true; + }); + + let ctx = `Error detected: \`${command.slice(0, 100)}\` produced:\n> ${errorLine}`; + if (traceFiles.length > 0) { + ctx += `\nFiles in stack trace: ${traceFiles.join(", ")}`; + } + if (unique.length > 0) { + const lines = unique.map((m) => `- ${m.memory}`).join("\n"); + ctx += `\nPrior error memories:\n${lines}`; + } + ctx += + "\nStore resolved errors as anti_pattern or bug_fix memories for future reference."; + systemContext.push(ctx); + } catch {} + } + }, + + "experimental.session.compacting": async ( + input: { sessionID?: string }, + output: { context: string[]; prompt?: string }, + ) => { + try { + const compactSessionId = input?.sessionID ?? sessionId; + const summaryContent = `Session compacting. Project: ${appId}. Branch: ${branch}. Session: ${compactSessionId}. Stats: ${stats.adds} memories stored, ${stats.searches} searches, ${stats.messages} messages.`; + Promise.resolve().then(async () => { + try { + await mem0.add([{ role: "user", content: summaryContent }], { + user_id: userId, + app_id: appId, + metadata: { + type: "session_state", + source: "pre-compaction", + session_id: compactSessionId, + branch, + }, + infer: true, + } as any); + } catch {} + }); + + const res = await mem0.search("session state decisions learnings", { + filters: { AND: [{ user_id: userId }, { app_id: appId }] }, + topK: 10, + }); + const memories = extractMemories(res); + if (memories.length > 0 && output?.context) { + const lines = memories.map((m) => `- ${m.memory}`).join("\n"); + output.context.push( + `## Mem0 Memories (preserve across compaction)\n\n${lines}\n\nIMPORTANT: After compaction, store any key decisions or learnings using the add_memory MCP tool.`, + ); + } + } catch {} + }, + + "shell.env": async ( + _input: { cwd: string; sessionID?: string }, + output: { env: Record }, + ) => { + if (output?.env) { + output.env.MEM0_USER_ID = userId; + output.env.MEM0_APP_ID = appId; + output.env.MEM0_SESSION_ID = sessionId; + output.env.MEM0_BRANCH = branch; + } + }, + }; +}; + +export default Mem0Plugin; diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/context-loader/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/context-loader/SKILL.md new file mode 100644 index 0000000000..312b905845 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/context-loader/SKILL.md @@ -0,0 +1,52 @@ +--- +name: context-loader +description: Searches and injects relevant memories into context before starting work on a task. Use when beginning a new task, switching context, or when project history, past decisions, or coding conventions need to be loaded. +--- + +# Context Loader + +Pre-fetches relevant memories to prime context before working on a task. + +## When to use + +- Session start (invoke manually or auto-triggered by skill description matching) +- User starts work on a specific feature or file set +- Complex multi-step task begins +- User says "what do we know about X" or "context for X" + +## Steps + +1. **Extract topics** from current message/task. Identify: file paths, module names, feature areas, error patterns. + +2. **Run 2-4 parallel `search_memories` calls** with different angles: + + | Query angle | Filter | Purpose | + |---|---|---| + | Feature/module name | `{"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}` | Architecture decisions | + | File paths mentioned | `{"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "convention"}}]}` | Coding patterns | + | Error keywords (if any) | `{"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "anti_pattern"}}]}` | Known pitfalls | + | Broad project context | `{"AND": [{"user_id": ""}, {"app_id": ""}]}` | Catch-all | + +3. **Deduplicate** results by memory ID across all search responses. + +4. **Output compact context block** (max 10 memories): + +``` +context-loader: loaded memories for "" + - [decision] [mem0:] + - [convention] [mem0:] + - [anti_pattern] [mem0:] +``` + +5. If **zero results**: output nothing. Don't announce empty context. + +## Constraints + +- **Read-only** — never modify or delete memories +- **Max 10 memories** returned (most relevant only) +- **Silent on empty** — only surfaces findings if relevant context exists +- Skip memories already visible in current session context + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/dream/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/dream/SKILL.md new file mode 100644 index 0000000000..83d9cb92f0 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/dream/SKILL.md @@ -0,0 +1,237 @@ +--- +name: dream +description: Consolidates stored memories by merging duplicates, resolving contradictions, and pruning stale entries. Use when memory count is high, search results feel noisy or repetitive, or periodic cleanup is needed to maintain memory quality. +--- + +# Mem0 Dream — Memory Consolidation + +This skill performs a memory consolidation pass: it fetches all project memories, +identifies near-duplicates, flags contradictions, and prunes stale entries based on +configured retention policies. All proposed changes are shown as a diff for user +approval before anything is modified. + +**IMPORTANT: Execute steps strictly in order (1 → 2 → 3 → 4 → 5 → 6). Each step depends on the previous one. Do NOT run steps in parallel or skip ahead.** + +## Step 1: Load Retention Policies + +Check for a project config file in the project root (current working directory): + +1. Look for `.mem0.json` first. If it exists, parse it as JSON and read the + `retention` field (a dict of `category → days | null`). +2. If `.mem0.json` is not present, look for `.mem0.md`. If it exists, scan it + for a `retention:` section or YAML front matter with retention settings and + parse what you find. +3. If neither file exists, skip config loading entirely. + +If no config is found or the config contains no retention settings, fall back to +these built-in defaults: + +| `metadata.type` | Default retention | +|---|---| +| `session_state` | 90 days | +| `compact_summary` | 90 days | +| all others | no pruning | + +Store the resolved policies for use in Step 3. + +--- + +## Step 2: Fetch ALL Project Memories + +Call `get_memories` to retrieve every memory for the active project: + +```python +get_memories( + filters={"AND": [{"user_id": ""}, {"app_id": ""}]}, + page_size=200, +) +``` + +If the response indicates more pages exist, paginate until all memories are fetched. +Collect the full list before proceeding. If zero memories are found, print: + +``` +No memories found for project . Nothing to consolidate. +``` + +…and stop. + +--- + +## Step 3: Analyze — Find Issues + +Work entirely in-memory; do not modify anything yet. + +Group memories by `metadata.type` (use `"unknown"` when the field is absent). +For each group, identify the following: + +### 3a. Near-duplicate pairs (merge candidates) + +Two memories are near-duplicates when they express the same fact or decision but +phrased differently (e.g., "Use PostgreSQL for auth" and "Auth DB is PostgreSQL"). + +Heuristics — two memories are near-duplicates if **all** of these hold: +- Similarity threshold: estimated cosine similarity > 0.9 (use noun/keyword overlap as proxy — if >60% of significant nouns overlap, treat as >0.9 similarity). +- Same `metadata.type`. +- Neither memory is pinned (`metadata.pinned != true`). + +For each qualifying pair, draft a merged version that is more complete and specific +than either original. + +### 3b. Contradictions + +Two memories contradict when they assert opposing facts about the same topic +(e.g., "Deploy to ECS" vs. "Deploy to Vercel"). + +Identify the likely winner: the more recent memory with higher confidence wins. +Store both IDs and their content for user review. + +### 3c. Prune candidates + +A memory is a prune candidate when **any** of the following is true: + +1. Its `metadata.type` has a retention policy and the memory is older than the + configured number of days (compare `created_at` to today). +2. Its confidence score is below 0.3 AND it contains no information unique to + this project (no file paths, identifiers, or domain-specific nouns). + +**Always skip memories where `metadata.pinned == true`**, regardless of age or +confidence. + +--- + +## Step 4: Print Diff Report + +Print a structured diff to the terminal before making any changes. Use exactly +this format: + +``` +## dream — consolidation report + +Merges (): + [mem0:] + [mem0:] → "" + +Conflicts (): + [mem0:] vs [mem0:] — "" [A/B/skip] + +Prune (): + [mem0:] — , d old + +Proposed: merges, prunes, conflicts. Apply? [Y/n] +``` + +If there are zero items in any category, omit that section entirely. + +If there are zero total proposals (no merges, no prunes, no conflicts), print: + +``` +Dream complete. No duplicate, contradictory, or stale memories found. +``` + +…and stop. + +--- + +## Step 5: Wait for User Input and Apply + +### 5a. Contradictions + +For each `CONFLICT` pair in the report, wait for the user to type `A`, `B`, or +`skip` (case-insensitive). If they enter nothing (empty), treat as `skip`. + +Record the winner for each pair before proceeding to the final apply confirmation. + +### 5b. Final confirmation + +After all conflict resolutions are collected, prompt: + +``` +Apply? [Y/n] +``` + +If the user types `n` or `no` (case-insensitive), print `Cancelled. No changes made.` +and stop. + +If the user confirms (`Y`, `yes`, or empty / Enter), apply all changes in this order: + +#### Merges + +For each approved merge pair: +1. `delete_memory()` +2. `delete_memory()` +3. `add_memory` with: + - `text=""` + - `user_id=` + - `app_id=` (top-level, not in metadata) + - `metadata={"type": "", "branch": "", "confidence": , "source": "mem0-dream"}` + - `infer=False` + +#### Contradictions (resolved) + +For each resolved conflict where the user chose A or B: +- Delete the loser (the non-chosen memory): `delete_memory(memory_id=)` + +Contradictions where the user chose `skip` are left untouched. + +#### Prunes + +For each prune candidate: +- `delete_memory()` + +--- + +## Step 6: Print Summary + +After all changes are applied, print: + +``` +Dream complete — merged: , pruned: , conflicts resolved: , skipped: +``` + +--- + +## Auto mode + +When invoked with `--auto` (e.g., `/mem0:dream --auto`), run non-interactively: + +- **Merges**: applied automatically (no contradiction, both are compatible). +- **Prunes**: applied automatically (age/confidence-based, no ambiguity). +- **Contradictions**: skipped — they require human judgment. + +### Concurrency guard + +Before doing any work, check for a lock file at `/tmp/mem0_dream_auto.lock`: +- If the lock file exists and is less than 10 minutes old, print `[mem0-dream --auto] Another run in progress — skipping.` and stop. +- Otherwise, create the lock file (write the current timestamp). Delete it when done (in all exit paths). + +### Execution + +In auto mode: +1. Load policies and fetch memories (Steps 1–3) as normal. +2. Apply merges and prunes silently without printing the diff or prompting. +3. Print a compact summary: + ``` + [mem0-dream --auto] project= merged= pruned= conflicts_skipped= + ``` +4. If contradictions were detected but skipped, check if a `mem0-dream-auto` reminder already exists before storing one: + - Search for existing reminders: `search_memories(query="mem0-dream contradictions manual review", filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"source": "mem0-dream-auto"}}]}, top_k=1)` + - If a result exists with similarity > 0.9, skip storing the reminder (one already exists). + - If no match, store the reminder: + ```python + add_memory( + text="mem0-dream detected contradiction(s) requiring manual review. Run /mem0:dream to resolve them interactively.", + user_id="", + app_id="", + metadata={"type": "task_learning", "source": "mem0-dream-auto", "branch": ""}, + infer=False, + ) + ``` + +## See also + +- `/mem0:forget` — targeted deletion of specific memories (search + confirm + delete) +- `/mem0:health --deep` — quick quality scan without applying changes + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/export/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/export/SKILL.md new file mode 100644 index 0000000000..96460cae66 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/export/SKILL.md @@ -0,0 +1,80 @@ +--- +name: export +description: Exports all project memories to a portable Markdown file for backup or migration. Use when backing up memories, migrating to another project, sharing memory state with teammates, or archiving before cleanup. +--- + +# Mem0 Export + +Export all memories for the current project to a portable Markdown file. + +## Execution + +### Step 1: Resolve identity + +Determine the active identity: +- `user_id` from `MEM0_USER_ID` env var, else `$USER`, else `"default"` +- `project_id` (used as `app_id`) from `MEM0_PROJECT_ID` env var, or via the project resolver + +### Step 2: Fetch all memories + +Call `get_memories` with: +- `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}` +- `page_size=200` + +If the response is paginated (i.e. the result contains a `next` cursor or the count equals `page_size`), continue fetching pages until all memories are retrieved. + +### Step 3: Format each memory as a YAML-frontmatter block + +For each memory record, produce a block in this exact format: + +``` +--- +id: +created_at: +type: +confidence: +branch: +files: +categories: +--- + + +``` + +Notes: +- The `---` delimiters must be on their own lines with no extra whitespace. +- `files` and `categories` are written as comma-separated values on a single line. +- Leave a blank line after the content before the next `---` (for readability). +- If a field is missing or null, write an empty string (not "null"). + +### Step 4: Write the export file + +Determine the output filename: + +``` +mem0-export--.md +``` + +Where `` is today's date in UTC. + +Write all formatted blocks to this file using the Write tool (or equivalent). The file is written to the current working directory. + +### Step 5: Print summary + +``` +Exported memories to +``` + +Where `` is the total number of memory blocks written. + +## Error Handling + +- If `get_memories` returns an error or zero memories, print: + ``` + No memories found for project . Nothing exported. + ``` +- If the write fails, report the error to the user. + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/forget/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/forget/SKILL.md new file mode 100644 index 0000000000..d83004de8e --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/forget/SKILL.md @@ -0,0 +1,74 @@ +--- +name: forget +description: Deletes memories by search query or memory ID with confirmation before removal. Use when removing outdated decisions, incorrect memories, sensitive data, or cleaning up after experiments. Also handles undo of recent additions. +--- + +# Mem0 Forget + +Delete specific memories from mem0. + +## Execution + +### Step 1: Parse input + +The user provides either: +- A search query: `/mem0:forget auth module decisions` +- A memory ID: `/mem0:forget ` + +If no argument, ask: "What should I forget? Provide a search query or memory ID." + +### Step 2: Find memories + +**If memory ID provided** (looks like a UUID or hex string): +- Call `get_memory` with the ID to verify it exists. +- Show: `Found: "" (created )` + +**If search query provided:** +- Call `search_memories` with: + - `query=` + - `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}` + - `top_k=10` +- Show numbered list: + ``` + Found memories matching "": + 1. (type: , created: ) [ID: ] + 2. ... + ``` + +### Step 3: Confirm + +Ask: "Delete which memories? Enter numbers (e.g., 1,3,5), 'all', or 'cancel'." + +For a single memory ID, ask: "Delete this memory? [y/N]" + +**Never delete without confirmation.** This is destructive. + +### Step 4: Delete + +For each confirmed memory, call `delete_memory` with the memory ID. + +### Step 5: Report + +``` +Deleted memories. +``` + +If any deletions failed, report which ones and why. + +## Undo recent writes + +If the user says "undo last N memories" or "undo last write": + +1. Check the `MEM0_SESSION_ID` environment variable (set by the plugin's shell.env hook). +2. If `MEM0_SESSION_ID` is set, call `search_memories` with: + - `query="recently added"` + - `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"session_id": ""}}]}` + - `top_k=20` +3. Sort results by creation time descending and show the last N entries (default 1). Ask for confirmation. +4. Delete confirmed entries via `delete_memory`. + +If `MEM0_SESSION_ID` is not set or the search returns no results, tell the user: "No recent memory IDs tracked this session. Try `/mem0:tour` to browse recent memories, or `/mem0:forget ` to find specific ones." + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/health/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/health/SKILL.md new file mode 100644 index 0000000000..e5f6015d91 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/health/SKILL.md @@ -0,0 +1,159 @@ +--- +name: health +description: Diagnoses mem0 connectivity, API key validity, and memory read/write functionality. Use when memory operations fail, searches return empty, add_memory errors occur, MCP connection drops, or to verify the plugin is working correctly. +--- + +# Mem0 Health Check + +Run a diagnostic check on the mem0 plugin. Useful for troubleshooting. + +## Execution + +Run ALL checks, then display a single summary. Do not stop on the first failure. + +### Check 1: API key + +```bash +_KEY="${MEM0_API_KEY:-}" +[ -n "$_KEY" ] && echo "${_KEY:0:6}..." || echo "NOT_SET" +``` + +- If `NOT_SET`: FAIL — "No API key configured" +- If set: PASS — the command already prints only the first 6 chars + +### Check 2: Identity resolution + +Resolve identity from environment variables set by the plugin's `shell.env` hook: + +```bash +echo "user_id=${MEM0_USER_ID:-${USER:-}}" +echo "project_id=${MEM0_APP_ID:-}" +echo "branch=$(git branch --show-current 2>/dev/null || echo '')" +``` + +- `user_id`: from `MEM0_USER_ID`, falling back to `$USER` +- `project_id`: from `MEM0_APP_ID` +- `branch`: from `git branch --show-current` + +PASS if all three are non-empty. WARN if any falls back to defaults. + +### Check 3: MCP server connectivity + +Call `search_memories` with: +- `query="health check"` +- `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}` +- `top_k=1` + +- If returns successfully (even empty): PASS +- If errors: FAIL — show the error message + +### Check 4: Memory write capability + +Call `add_memory` with: +- `text="Health check probe — safe to delete."` +- `user_id=` +- `app_id=` +- `metadata={"type": "health_check", "probe": true}` +- `infer=False` + +The response returns `event_id` (v3 writes are async). Call `get_event_status(event_id=)` to check processing. + +- If status is `SUCCEEDED`: PASS — extract the memory ID from the event result, then call `delete_memory` with that ID to clean up. +- If status is `PENDING` after 5 seconds: PASS (write accepted, processing delayed) +- If errors: FAIL — show the error. + +### Check 5: Session context + +Check that the plugin's `shell.env` hook has injected session context into the environment: + +```bash +echo "session_id=${MEM0_SESSION_ID:-}" +echo "app_id=${MEM0_APP_ID:-}" +echo "branch=${MEM0_BRANCH:-}" +``` + +- If all three are non-empty: PASS — "Session active" +- If any are missing: WARN — "Plugin env vars not set; shell.env hook may not have fired" + +### Display + +``` +## mem0 health + +PASS API Key m0-dVe... +PASS Identity user=kartik, project=mem0, branch=main +PASS MCP Connection 142ms +PASS Write/Read write + delete OK +PASS Session session_id=abc123, app_id=mem0, branch=main + +All checks passed. +``` + +If any check fails, add a `## Troubleshooting` section with specific fix steps for each failure. + +## Extended mode: Memory Quality Analysis + +When invoked with `--deep` (e.g., `/mem0:health --deep`), run the standard 5 checks above **plus** a memory quality scan. + +### Quality Check 1: Duplicates + +Call `get_memories` with `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=200`. Compare all pairs within the same `metadata.type` group for high textual overlap (shared nouns/keywords > 60%). Report: + +``` +Potential duplicates: pairs + [mem0:] ≈ [mem0:] — both about "" +``` + +### Quality Check 2: Stale memories + +Flag memories where: +- `metadata.type` is `session_state` or `compact_summary` AND older than 90 days +- `metadata.confidence` < 0.3 AND older than 30 days + +``` +Stale candidates: + [mem0:] — session_state, 142d old +``` + +### Quality Check 2b: Low-confidence memories + +Flag memories where `metadata.confidence` < 0.5 (regardless of age). Report separately from stale: + +``` +Low-confidence memories: + [mem0:] — confidence=0.3, "" +``` + +### Quality Check 3: Contradictions + +Within each `metadata.type` group, flag pairs that assert opposing facts about the same topic. Use semantic judgment — look for negation patterns, conflicting tool/framework choices, or reversed decisions. + +``` +Possible contradictions: + [mem0:] vs [mem0:] — conflicting on "" +``` + +### Quality Check 4: Orphan memories + +Memories with no `metadata.type` set, or with `metadata.type` not in the 17 known coding categories. These were likely written without proper tagging. + +``` +Untagged/orphan memories: +``` + +### Quality summary + +``` +## Memory Quality + +Duplicates: · Stale: · Contradictions: · Orphans: +``` + +If all counts are 0: `Memory quality: clean.` +If any non-zero: append `Run /mem0:dream to fix.` + +To fix issues found by `--deep`, run `/mem0:dream` for automated consolidation (merges, prunes, conflict resolution). + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/import/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/import/SKILL.md new file mode 100644 index 0000000000..b982351ad9 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/import/SKILL.md @@ -0,0 +1,185 @@ +--- +name: import +description: Imports memories from an exported Markdown file or MEMORY.md into the current project. Use when migrating from another project, restoring from backup, importing Claude Code native MEMORY.md content, or setting up a new project with existing knowledge. +--- + +# Mem0 Import + +Import memories from a mem0 export file into the current project. + +## Execution + +### Step 1: Determine the export file to import + +If the user provided a filename as an argument to `/mem0:import `, use that file. + +Otherwise, list `.md` files in the current directory whose names contain `mem0-export`: + +```bash +ls -1 *.md 2>/dev/null | grep mem0-export || echo "No export files found" +``` + +If multiple files are found, ask the user which one to import. If none are found, print: +``` +No mem0-export files found in the current directory. +Run /mem0:export first, or provide the filename: /mem0:import +``` + +### Step 2: Parse the export file + +Read the export file directly. It is a JSON file containing a top-level `memories` array. Each element has: +- `id` — original memory ID (for reference only; a new ID will be assigned on import) +- `type` — metadata type +- `confidence` — metadata confidence value +- `branch` — metadata branch +- `files` — list of associated files +- `categories` — list of categories +- `content` — the memory text + +Parse the JSON in-memory (do not run any external script). If the file cannot be read or parsed, or if the `memories` array is missing or empty, print: +``` +Failed to parse or file contains no valid memory blocks. +``` +and stop. + +### Step 3: Resolve identity + +Determine the active identity: +- `user_id` from `MEM0_USER_ID` env var, else `$USER`, else `"default"` +- `project_id` (used as `app_id`) from `MEM0_PROJECT_ID` env var, or via the project resolver + +### Step 4: Import each memory + +For each record in the parsed JSON array, call `add_memory` (MCP tool) with: + +- `text=""` +- `user_id=` +- `app_id=` +- `metadata={` + - `"type": ""` (if non-empty) + - `"confidence": ""` (if non-empty) + - `"branch": ""` (if non-empty) + - `"files": ` (the list, if non-empty) + - `"source": "import"` + - `}` +- `infer=False` + +Notes: +- Do NOT pass the original `id` — the platform assigns a new ID. +- Skip records where `content` is empty. +- Continue importing even if individual records fail; track the count of successes. + +### Step 5: Print results + +``` +Imported memories into project +``` + +Where `` is the number of successfully imported memories. + +If any failed: +``` +Imported / memories into project ( failed) +``` + +## Importing from competing AI tools (`--tools`) + +When invoked with `--tools` (e.g., `/mem0:import --tools`), detect and import +from competing AI tool configuration files: + +### Supported tools + +| Tool | File/directory | +|------|---------------| +| Cursor | `.cursorrules` | +| GitHub Copilot | `.github/copilot-instructions.md` | +| Cline | `memory-bank/` (directory of `.md` files) | +| Continue | `.continue/rules.md` | + +### T1: Detect + +```bash +test -f .cursorrules && echo "cursor: .cursorrules" +test -f .github/copilot-instructions.md && echo "copilot: .github/copilot-instructions.md" +test -d memory-bank/ && echo "cline: memory-bank/" +test -f .continue/rules.md && echo "continue: .continue/rules.md" +``` + +### T2: Ask user + +List found files, ask which to import (numbers, comma-separated, or "all"). +If none found: +``` +No competing tool configuration files found. +Checked: .cursorrules, .github/copilot-instructions.md, memory-bank/, .continue/rules.md +``` + +### T3: Import + +For each selected tool, read the file(s) directly and split the content into logical +chunks to import as individual memories. No external scripts are used — all parsing +and importing is done via MCP tools. + +Chunking rules per tool: + +- **cursorrules / copilot / continue**: Read the file as plain text. Split on blank + lines or section headers (`#`, `##`). Each non-empty chunk becomes one memory. + Skip chunks shorter than 50 characters. + +- **cline (memory-bank/)**: List all `.md` files in the directory. Read each file. + Split each file on blank lines or headers. Each non-empty chunk becomes one memory. + Skip chunks shorter than 50 characters. + +For each chunk, call `add_memory` (MCP tool) with: +- `text=""` +- `user_id=` +- `app_id=` +- `metadata={"type": "task_learning", "source": "-import", "confidence": 0.8}` +- `infer=False` + +Where `` is `cursorrules`, `copilot`, `cline`, or `continue`. + +Notes: chunks longer than 10,000 characters are truncated before import. Safe to +re-run — deduplication handles repeated entries. + +### T4: Report + +``` +Imported memories into (cursor: , copilot: ) +``` + +--- + +## Importing Claude Code's native MEMORY.md + +When invoked with a path to Claude Code's native `MEMORY.md` file (typically +`~/.claude/projects//memory/MEMORY.md`), or when `on_session_start.sh` +detects native auto-memory and the user chooses to import: + +1. Read the file directly. It contains newline-separated memory entries (one fact per + line, sometimes with `- ` bullet prefix). +2. Split by non-empty lines. Each line becomes one memory. +3. Skip lines shorter than 20 characters or lines that are just headers (`#`). +4. For each line, call `add_memory` (MCP tool) with: + - `text=""` + - `user_id=` + - `app_id=` + - `metadata={"type": "task_learning", "source": "memory-md-import", "confidence": 0.8}` + - `infer=False` +5. Report: `Imported memories from MEMORY.md into project ` +6. Suggest disabling native auto-memory: + ``` + To avoid duplicate memory systems, add to ~/.claude/settings.json: + "autoMemoryEnabled": false + ``` + +This handles the cold-start gap when a user has been using Claude Code's native +memory and switches to mem0. + +## Error Handling + +- If `add_memory` calls fail consistently (e.g. auth error), report the issue and stop early. + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/list-projects/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/list-projects/SKILL.md new file mode 100644 index 0000000000..85a39116c8 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/list-projects/SKILL.md @@ -0,0 +1,64 @@ +--- +name: list-projects +description: Lists all projects with stored memories for the current user, showing memory counts and last activity dates. Use when checking which projects have memories, comparing memory distribution across repos, or finding a specific project scope. +--- + +# Mem0 List Projects + +Show all known project scopes for the current user. + +## Execution + +### Step 1: Fetch memories to discover app_ids + +There is no dedicated "list projects" API endpoint. Discover projects by fetching +the user's memories across all scopes. + +**Important:** A filter with only `user_id` triggers implicit null scoping — it +excludes memories that have a non-null `app_id`. Run two queries and merge: + +1. **Null-scoped:** `get_memories` with `filters={"AND": [{"user_id": ""}]}`, `page_size=200` + — catches memories without `app_id` +2. **App-scoped:** `get_memories` with `filters={"AND": [{"user_id": ""}, {"app_id": {"exists": true}}]}`, `page_size=200` + — catches memories with any `app_id` + +Run both calls in parallel. Merge results, deduplicate by memory `id`. + +If either response indicates more pages, paginate (up to 1000 total). + +### Step 2: Extract distinct projects + +For each memory, determine project by: +1. Top-level `app_id` field (preferred) +2. `metadata.project_id` (legacy memories) +3. `metadata.project` (oldest format) +4. `"(unscoped)"` if none found + +Group by resolved project name. For each project, count: +- Total memories +- Most recent `created_at` date +- Top 3 `metadata.type` values by frequency + +### Step 3: Display + +``` +## mem0 projects + + memories (last: ) ← current + memories (last: ) + + projects, total memories +``` + +Mark current project with `← current`. Sort by memory count descending. + +### Step 4: Empty state + +If zero memories found: +``` +No projects found. Run /mem0:onboard to get started. +``` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/LICENSE b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/LICENSE new file mode 100644 index 0000000000..78c99ae280 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/LICENSE @@ -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. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/README.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/README.md new file mode 100644 index 0000000000..4ed29d1621 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/README.md @@ -0,0 +1,73 @@ +# Mem0 Skill for Claude + +Add persistent memory to any AI application in minutes using [Mem0 Platform](https://app.mem0.ai?utm_source=oss&utm_medium=mem0-plugin-skill-readme). + +## What This Skill Does + +When installed, Claude can: + +- **Set up Mem0** in your Python or TypeScript project +- **Integrate memory** into your existing AI app (LangChain, CrewAI, Vercel AI, OpenAI Agents, LangGraph, LlamaIndex, etc.) +- **Generate working code** using real API references and tested patterns +- **Search live docs** on demand for the latest Mem0 documentation + +## Installation + +This skill is included automatically when you install the Mem0 plugin: + +``` +/plugin marketplace add mem0ai/mem0 +/plugin install mem0@mem0-plugins +``` + +See the [plugin README](../../README.md) for full setup instructions. + +### Prerequisites + +- A Mem0 Platform API key ([Get one here](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-skill-readme)) +- Python 3.10+ or Node.js 18+ +- Set the environment variable: + + ```bash + export MEM0_API_KEY="m0-your-api-key" + ``` + +## Quick Start + +After installing, just ask Claude: + +- "Set up mem0 in my project" +- "Add memory to my chatbot" +- "Help me search user memories with filters" +- "Integrate mem0 with my LangChain app" +- "Add graph memory to track entity relationships" + +## What's Inside + +```text +skills/mem0/ +├── SKILL.md # Skill definition and instructions +├── README.md # This file +├── LICENSE # Apache-2.0 +├── scripts/ +│ └── mem0_doc_search.py # Search live Mem0 docs on demand +└── references/ # Documentation (loaded on demand) + ├── quickstart.md # Full quickstart (Python, TS, cURL) + ├── sdk-guide.md # All SDK methods (Python + TypeScript) + ├── api-reference.md # REST endpoints, filters, memory object + ├── architecture.md # Processing pipeline, lifecycle, scoping, performance + ├── features.md # Retrieval, graph, categories, MCP, webhooks, multimodal + ├── integration-patterns.md # LangChain, CrewAI, Vercel AI, LangGraph, LlamaIndex, etc. + └── use-cases.md # 7 real-world patterns with Python + TypeScript code +``` + +## Links + +- [Mem0 Platform Dashboard](https://app.mem0.ai?utm_source=oss&utm_medium=mem0-plugin-skill-readme) +- [Mem0 Documentation](https://docs.mem0.ai) +- [Mem0 GitHub](https://github.com/mem0ai/mem0) +- [API Reference](https://docs.mem0.ai/api-reference) + +## License + +Apache-2.0 diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/SKILL.md new file mode 100644 index 0000000000..2658741c9e --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/SKILL.md @@ -0,0 +1,191 @@ +--- +name: mem0 +description: Mem0 SDK reference covering Python and TypeScript APIs, memory client methods, configuration, and framework integrations. Use when writing code that calls mem0 APIs, configuring memory providers, or integrating mem0 into an application. +license: Apache-2.0 +metadata: + author: mem0ai + version: "0.1.1" + category: ai-memory + tags: "memory, personalization, ai, python, typescript, vector-search" +compatibility: Requires Python 3.10+ or Node.js 18+, pip install mem0ai or npm install mem0ai, MEM0_API_KEY env var (Platform), and internet access to api.mem0.ai. Uses Mem0 v3 API. +--- + +# Mem0 Platform Integration + +> **Skill Graph:** This skill is part of the Mem0 skill graph: +> - **mem0** (this skill) -- Platform Client SDK + OSS (Python + TypeScript) +> - **[mem0-vercel-ai-sdk](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk)** -- Vercel AI SDK provider + +Mem0 is a managed memory layer for AI applications. It stores, retrieves, and manages user memories via API — no infrastructure to deploy. For self-hosted usage, see the OSS section in the client references below. + +## Step 1: Install and authenticate + +**Python:** +```bash +pip install mem0ai +export MEM0_API_KEY="m0-your-api-key" +``` + +**TypeScript/JavaScript:** +```bash +npm install mem0ai +export MEM0_API_KEY="m0-your-api-key" +``` + +Get an API key at: https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-skill + +> **Don't have a `MEM0_API_KEY`?** Sign up at https://app.mem0.ai and create one from the dashboard. Keys start with `m0-`. + +## Step 2: Initialize the client + +**Python:** +```python +from mem0 import MemoryClient +client = MemoryClient(api_key="m0-xxx") +``` + +**TypeScript:** +```typescript +import MemoryClient from 'mem0ai'; +const client = new MemoryClient({ apiKey: 'm0-xxx' }); +``` + +For async Python, use `AsyncMemoryClient`. + +## Step 3: Core operations + +Every Mem0 integration follows the same pattern: **retrieve → generate → store**. + +### Add memories +```python +messages = [ + {"role": "user", "content": "I'm a vegetarian and allergic to nuts."}, + {"role": "assistant", "content": "Got it! I'll remember that."} +] +client.add(messages, user_id="alice") +``` + +### Search memories +```python +results = client.search("dietary preferences", filters={"user_id": "alice"}) +for mem in results.get("results", []): + print(mem["memory"]) +``` + +### Get all memories +```python +all_memories = client.get_all(filters={"user_id": "alice"}) +``` + +### Update a memory +```python +client.update("memory-uuid", text="Updated: vegetarian, nut allergy, prefers organic") +``` + +### Delete a memory +```python +client.delete("memory-uuid") +client.delete_all(user_id="alice") # delete all for a user +``` + +## Common integration pattern + +```python +from mem0 import MemoryClient +from openai import OpenAI + +mem0 = MemoryClient() +openai = OpenAI() + +def chat(user_input: str, user_id: str) -> str: + # 1. Retrieve relevant memories + memories = mem0.search(user_input, filters={"user_id": user_id}) + context = "\n".join([m["memory"] for m in memories.get("results", [])]) + + # 2. Generate response with memory context + response = openai.chat.completions.create( + model="gpt-5-mini", + messages=[ + {"role": "system", "content": f"User context:\n{context}"}, + {"role": "user", "content": user_input}, + ] + ) + reply = response.choices[0].message.content + + # 3. Store interaction for future context + mem0.add( + [{"role": "user", "content": user_input}, {"role": "assistant", "content": reply}], + user_id=user_id + ) + return reply +``` + +## Common edge cases + +- **Search returns empty:** v3 processes `add()` asynchronously — returns an event ID immediately. Wait 2-3s before searching. Also verify `user_id` matches exactly (case-sensitive) and use `filters={"user_id": "..."}` syntax. +- **AND filter with user_id + agent_id returns empty:** Entities are stored separately. `{"AND": [{"user_id": "alice"}, {"agent_id": "bot"}]}` returns nothing. Use `OR` instead, or query each separately. +- **Duplicate memories:** Don't mix `infer=True` (default) and `infer=False` for the same data. `infer=True` extracts facts via LLM with dedup. `infer=False` stores raw — same text can be stored twice. +- **Implicit null scoping:** `filters={"user_id": "alice"}` only returns memories where `agent_id`, `app_id`, `run_id` are ALL null. Wrap in `{"OR": [...]}` to include memories with non-null scoping fields. +- **Platform vs OSS imports:** Platform: `from mem0 import MemoryClient`. OSS: `from mem0 import Memory`. Don't mix them — `MemoryClient` talks to `api.mem0.ai`, `Memory` runs locally. +- **v3 defaults:** `top_k=20`, `threshold=0.1`, `rerank=False`. Adjust as needed. + +## v3 API (Current) + +Mem0 v3 uses single-pass extraction, entity linking, and multi-signal retrieval. + +**Key v3 changes from v2:** +- **Endpoints:** `POST /v3/memories/add/`, `POST /v3/memories/search/`, `POST /v3/memories/` (paginated list) +- **Extraction:** Single ADD-only pass — no more UPDATE/DELETE operations during extraction. Memories accumulate rather than consolidate. +- **Entity linking:** Replaces graph memory. Auto-extracted during `add()`, no config needed. Remove `enable_graph` and `graph_store` from any old config. +- **Defaults:** `top_k=20`, `threshold=0.1`, `rerank=False` +- **Removed params:** `org_id`, `project_id`, `enable_graph` — all removed from SDK +- **TypeScript:** Exclusively camelCase (`userId`, `agentId`, `appId`, `topK`) +- **Add response:** Async — returns event ID immediately, poll via `GET /v1/event/{event_id}/` + +See the [migration guide](https://docs.mem0.ai/migration/platform-v2-to-v3) for details. + +## Live documentation search + +For the latest docs beyond what's in the references, use the doc search tool: + +```bash +python ${CLAUDE_SKILL_DIR}/scripts/mem0_doc_search.py --query "topic" +python ${CLAUDE_SKILL_DIR}/scripts/mem0_doc_search.py --page "/platform/features/graph-memory" +python ${CLAUDE_SKILL_DIR}/scripts/mem0_doc_search.py --index +``` + +No API key needed — searches docs.mem0.ai directly. + +## Client SDK References + +Language-specific deep references (Platform + OSS): + +| Language | File | +|----------|------| +| Python (MemoryClient + AsyncMemoryClient + Memory OSS) | [client/python.md](client/python.md) | +| TypeScript/Node.js (MemoryClient + Memory OSS) | [client/node.md](client/node.md) | +| Python vs TypeScript differences | [client/differences.md](client/differences.md) | + +## Platform References + +Load these on demand for deeper detail: + +| Topic | File | +|-------|------| +| Quickstart (Python, TS, cURL) | [references/quickstart.md](references/quickstart.md) | +| SDK guide (all methods, both languages) | [references/sdk-guide.md](references/sdk-guide.md) | +| API reference (endpoints, filters, object schema) | [references/api-reference.md](references/api-reference.md) | +| Architecture (pipeline, lifecycle, scoping, performance) | [references/architecture.md](references/architecture.md) | +| Platform features (retrieval, graph, categories, MCP, etc.) | [references/features.md](references/features.md) | +| Framework integrations (LangChain, CrewAI, OpenAI Agents, etc.) | [references/integration-patterns.md](references/integration-patterns.md) | +| Use cases & examples (real-world patterns with code) | [references/use-cases.md](references/use-cases.md) | + +## Related Mem0 Skills + +| Skill | When to use | Link | +|-------|-------------|------| +| mem0-vercel-ai-sdk | Vercel AI SDK provider with automatic memory | [GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk) | + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/differences.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/differences.md new file mode 100644 index 0000000000..e5e80e2503 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/differences.md @@ -0,0 +1,129 @@ +# Python vs TypeScript SDK Differences + +Quick-reference cheatsheet for developers working across both Mem0 SDKs. + +## Constructor + +| Aspect | Python | TypeScript | +|--------|--------|------------| +| Import (Platform) | `from mem0 import MemoryClient` | `import MemoryClient from 'mem0ai'` | +| Import (OSS) | `from mem0 import Memory` | `import { Memory } from 'mem0ai/oss'` | +| Constructor | `MemoryClient(api_key="m0-xxx")` | `new MemoryClient({ apiKey: 'm0-xxx' })` | +| Required param | `api_key` (positional or kwarg) | `apiKey` (in options object) | + +Both read from `MEM0_API_KEY` env var if no key provided. + +## Method Naming + +| Operation | Python | TypeScript | +|-----------|--------|------------| +| Add | `add()` | `add()` | +| Search | `search()` | `search()` | +| Get | `get()` | `get()` | +| Get all | `get_all()` | `getAll()` | +| Update | `update()` | `update()` | +| Delete | `delete()` | `delete()` | +| Delete all | `delete_all()` | `deleteAll()` | +| History | `history()` | `history()` | +| Batch update | `batch_update()` | `batchUpdate()` | +| Batch delete | `batch_delete()` | `batchDelete()` | +| List users | `users()` | `users()` | +| Delete users | `delete_users()` | `deleteUsers()` | +| Get project | `project.get()` | `getProject()` | +| Update project | `project.update()` | `updateProject()` | +| Create webhook | `create_webhook()` | `createWebhook()` | +| Get webhooks | `get_webhooks()` | `getWebhooks()` | +| Update webhook | `update_webhook()` | `updateWebhook()` | +| Delete webhook | `delete_webhook()` | `deleteWebhook()` | +| Create export | `create_memory_export()` | `createMemoryExport()` | +| Get export | `get_memory_export()` | `getMemoryExport()` | +| Feedback | `feedback()` | `feedback()` | + +**Rule:** Python uses `snake_case`, TypeScript uses `camelCase` for method names. + +## Parameter Passing + +```python +# Python: kwargs +client.add(messages, user_id="alice", metadata={"source": "chat"}) +client.search("query", filters={"user_id": "alice"}, top_k=5, rerank=True) +``` + +```typescript +// TypeScript: options object with camelCase for top-level params, snake_case for filter keys +await client.add(messages, { userId: 'alice', metadata: { source: 'chat' } }); +await client.search('query', { filters: { user_id: 'alice' }, topK: 5, rerank: true }); +``` + +**v3:** Python uses `snake_case` everywhere. TypeScript uses `camelCase` for top-level params (`userId`, `topK`) but `snake_case` for filter keys (`user_id`, `agent_id`). + +## Architectural Differences + +| Aspect | Python | TypeScript | +|--------|--------|------------| +| HTTP library | httpx | axios | +| Default timeout | 300s | 60s | +| Sync support | Yes (`MemoryClient`) | No (all async) | +| Async support | Yes (`AsyncMemoryClient`) | All methods are async | +| Project management | `client.project.*` (separate class) | `client.getProject()` / `client.updateProject()` | +| Context manager | `async with AsyncMemoryClient()` | Not supported | + +## Platform Features: Python-only + +These methods exist in Python but not TypeScript: + +| Method | Description | +|--------|-------------| +| `get_summary(filters)` | Get summary of memories | +| `reset()` | Delete ALL data (users + memories) | +| `project.create(name)` | Create a new project | +| `project.delete()` | Delete current project | +| `project.get_members()` | List project members | +| `project.add_member(email, role)` | Add member to project | +| `project.update_member(email, role)` | Change member role | +| `project.remove_member(email)` | Remove member | + +## Platform Features: TypeScript-only + +| Method | Description | +|--------|-------------| +| `deleteUser(data)` | Convenience method for single entity deletion | +| `ping()` | Health check endpoint | + +## OSS Config Naming + +| Python config key | TypeScript config key | +|-------------------|----------------------| +| `vector_store` | `vectorStore` | +| `history_db_path` | `historyDbPath` | +| `custom_instructions` | `customInstructions` | + +## OSS Scope Parameter Naming + +| Python | TypeScript | +|--------|------------| +| `user_id="alice"` | `userId: 'alice'` | +| `agent_id="bot"` | `agentId: 'bot'` | +| `run_id="session"` | `runId: 'session'` | + +## Entity ID Passing (v3) + +| Method | Python | TypeScript | +|--------|--------|------------| +| add() | Top-level: `user_id="alice"` | Top-level: `{ userId: 'alice' }` | +| search() | In filters: `filters={"user_id": "alice"}` | In filters: `{ filters: { user_id: 'alice' } }` | +| get_all() | In filters: `filters={"user_id": "alice"}` | In filters: `{ filters: { user_id: 'alice' } }` | + +## Common Gotcha + +When searching/filtering, both Python and TypeScript use `snake_case` for filter keys. TypeScript only uses `camelCase` for top-level method parameters: + +```python +# Python - snake_case in filters +results = client.search("query", filters={"user_id": "alice"}) +``` + +```typescript +// TypeScript - snake_case in filters, camelCase for top-level params +const results = await client.search('query', { filters: { user_id: 'alice' }, topK: 20 }); +``` diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/node.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/node.md new file mode 100644 index 0000000000..ca85ba7ba5 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/node.md @@ -0,0 +1,418 @@ +# Mem0 Node.js / TypeScript SDK Reference + +Complete reference for the `mem0ai` npm package. Covers both the Platform client (managed API) and the Open Source self-hosted variant. + +--- + +## Platform Client + +### Installation + +```bash +npm install mem0ai +export MEM0_API_KEY="m0-your-api-key" +``` + +### MemoryClient + +```typescript +import MemoryClient from 'mem0ai'; + +const client = new MemoryClient({ apiKey: 'm0-xxx' }); +``` + +**Constructor:** `new MemoryClient({ apiKey })`. If `apiKey` is not provided, reads from `MEM0_API_KEY` environment variable. + +- HTTP library: `axios` +- Timeout: 60 seconds +- Base URL: `https://api.mem0.ai` +- All methods are async (return `Promise`) + +--- + +### Memory Methods + +#### add(messages, options?) + +Store new memories from messages. + +```typescript +const messages = [ + { role: 'user', content: "I'm a vegetarian and allergic to nuts." }, + { role: 'assistant', content: "Got it! I'll remember that." }, +]; +await client.add(messages, { userId: 'alice' }); +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `messages` | `Message[]` | Array of `{role, content}` objects | +| `options.userId` | string | User identifier | +| `options.agentId` | string | Agent identifier | +| `options.appId` | string | Application identifier | +| `options.runId` | string | Session identifier | +| `options.metadata` | object | Custom key-value pairs | +| `options.infer` | boolean | If false, store raw text (default: true) | + +**Returns:** `Promise` -- list of events + +#### search(query, options?) + +Search memories by semantic similarity. + +```typescript +const results = await client.search('dietary preferences', { filters: { user_id: 'alice' }, topK: 20 }); +for (const mem of results.results) { + console.log(mem.memory, mem.score); +} +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `query` | string | Natural language search query | +| `options.filters` | object | Filter object with entity IDs (`user_id`, `agent_id`, etc.) and/or `AND`/`OR`/`NOT` conditions | +| `options.topK` | number | Number of results (default: 20) | +| `options.rerank` | boolean | Enable semantic reranking (default: false) | +| `options.threshold` | number | Minimum similarity (default: 0.1) | + +**Returns:** `Promise` -- `{results: [{id, memory, score, ...}]}` + +#### get(memoryId) + +```typescript +const memory = await client.get('ea925981-...'); +``` + +#### getAll(options?) + +Retrieve all memories. Requires at least one entity identifier in filters. + +```typescript +const memories = await client.getAll({ filters: { user_id: 'alice' } }); +// With filters +const filtered = await client.getAll({ + filters: { AND: [{ user_id: 'alice' }, { categories: { contains: 'health' } }] }, +}); +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `options.filters` | object | Filter object with entity IDs (`user_id`, `agent_id`, etc.) and/or `AND`/`OR`/`NOT` conditions | +| `options.page` | number | Page number | +| `options.pageSize` | number | Results per page | + +#### update(memoryId, data) + +```typescript +await client.update('ea925981-...', { text: 'Updated: vegan since 2024' }); +await client.update('ea925981-...', { text: 'Updated', metadata: { verified: true } }); +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `memoryId` | string | Memory ID | +| `data.text` | string | New content | +| `data.metadata` | object | New metadata | +| `data.timestamp` | string | New timestamp | + +#### delete(memoryId) + +```typescript +await client.delete('ea925981-...'); +``` + +#### deleteAll(options?) + +```typescript +await client.deleteAll({ userId: 'alice' }); +``` + +#### history(memoryId) + +```typescript +const history = await client.history('ea925981-...'); +// Returns: [{previousValue, newValue, action, timestamps}] +``` + +--- + +### Batch Methods + +#### batchUpdate(memories) + +```typescript +await client.batchUpdate([ + { memoryId: 'uuid-1', text: 'Updated text' }, + { memoryId: 'uuid-2', text: 'Another update' }, +]); +``` + +#### batchDelete(memories) + +```typescript +await client.batchDelete(['uuid-1', 'uuid-2', 'uuid-3']); +``` + +--- + +### User/Entity Management + +#### users() + +```typescript +const users = await client.users(); +// Returns: {results: [{type: "user", name: "alice"}, ...]} +``` + +#### deleteUser(data) / deleteUsers(data) + +```typescript +await client.deleteUser({ userId: 'alice' }); // Single entity +await client.deleteUsers({ agentId: 'bot-1' }); // Flexible +``` + +--- + +### Project Management + +```typescript +// Get project config +const config = await client.getProject({ fields: ['customCategories'] }); + +// Update project settings +await client.updateProject({ + customInstructions: 'Extract dietary preferences and health info', + customCategories: [{ health: 'Medical and dietary info' }], +}); +``` + +--- + +### Webhooks + +```typescript +// List +const webhooks = await client.getWebhooks({ projectId: 'proj_123' }); + +// Create +const webhook = await client.createWebhook({ + url: 'https://your-app.com/webhook', + name: 'Memory Logger', + projectId: 'proj_123', + eventTypes: ['memory_add', 'memory_update'], +}); + +// Update +await client.updateWebhook({ + webhookId: 'wh_123', + name: 'Updated Logger', + url: 'https://new-url.com', +}); + +// Delete +await client.deleteWebhook({ webhookId: 'wh_123' }); +``` + +--- + +### Feedback + +```typescript +await client.feedback({ + memoryId: 'mem-123', + feedback: 'POSITIVE', + feedbackReason: 'Accurately captured preference', +}); +``` + +--- + +### Export + +```typescript +const exportReq = await client.createMemoryExport({ + schema: JSON.stringify({ type: 'object', properties: { name: { type: 'string' } } }), + filters: { user_id: 'alice' }, +}); + +const result = await client.getMemoryExport({ memoryExportId: exportReq.id }); +``` + +--- + +### TypeScript Types + +Key interfaces from `mem0.types.ts`: + +```typescript +interface Message { role: string; content: string; } +interface Memory { id: string; memory: string; userId: string; categories: string[]; score?: number; /* ... */ } +interface MemoryOptions { userId?: string; agentId?: string; appId?: string; runId?: string; metadata?: object; /* ... */ } +interface SearchOptions { filters?: object; topK?: number; rerank?: boolean; threshold?: number; /* ... */ } +interface MemoryHistory { id: string; memoryId: string; previousValue: string; newValue: string; action: string; /* ... */ } +interface FeedbackPayload { memoryId: string; feedback: string; feedbackReason?: string; } +interface WebhookCreatePayload { url: string; name: string; projectId: string; eventTypes: string[]; } +``` + +--- + +## Open Source / Self-Hosted + +### Installation + +```bash +npm install mem0ai +``` + +### Memory Class + +```typescript +import { Memory } from 'mem0ai/oss'; + +const m = new Memory(); // Uses default config +``` + +**Import:** `from 'mem0ai/oss'` (NOT the default export -- that is `MemoryClient` for Platform) + +### Configuration + +```typescript +const config = { + llm: { + provider: 'openai', // openai, groq, anthropic, google, ollama, lmstudio, mistral, azure + config: { + model: 'gpt-5-mini', + apiKey: 'sk-xxx', + }, + }, + embedder: { + provider: 'openai', // openai, ollama, lmstudio, google, azure, langchain, anthropic + config: { + model: 'text-embedding-3-small', + apiKey: 'sk-xxx', + }, + }, + vectorStore: { + provider: 'qdrant', // memory, qdrant, redis, supabase, langchain, azure_ai_search, pgvector + config: { + collectionName: 'my_memories', + host: 'localhost', + port: 6333, + }, + }, + historyDbPath: 'history.db', + customInstructions: '...', + disableHistory: false, +}; + +const m = new Memory(config); +// Or from dict with validation: +const m2 = Memory.fromConfig(config); +``` + +### Methods + +All methods are async (return `Promise`): + +#### add(messages, config) + +```typescript +await m.add('I prefer dark mode', { userId: 'alice' }); +await m.add([ + { role: 'user', content: 'I like hiking' }, + { role: 'assistant', content: 'Great outdoor activity!' }, +], { userId: 'alice' }); +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `messages` | `string \| Message[]` | Content to store | +| `config.userId` | string | User identifier (at least one scope required) | +| `config.agentId` | string | Agent identifier | +| `config.runId` | string | Session identifier | +| `config.metadata` | object | Custom key-value pairs | +| `config.filters` | object | Additional filters | +| `config.infer` | boolean | LLM inference (default: true) | + +**Returns:** `Promise<{results: [...], relations?: [...]}>` + +#### search(query, config) + +```typescript +const results = await m.search('dietary preferences', { filters: { user_id: 'alice' }, topK: 5 }); +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `query` | string | Search query | +| `config.filters` | object | Filter object with entity IDs (`user_id`, `agent_id`, `run_id`, etc.) | +| `config.topK` | number | Max results (default: 20) | + +#### get(memoryId) / getAll(config) / update(memoryId, data) / delete(memoryId) / deleteAll(config) / history(memoryId) + +Same interface patterns. Note: OSS `update` takes a string for data, not an object. + +```typescript +await m.update('mem-id', 'new content'); +``` + +#### reset() + +Clear the entire vector store and history. + +```typescript +await m.reset(); +``` + +--- + +## Key Differences: Platform vs OSS + +| Aspect | Platform (`MemoryClient`) | OSS (`Memory`) | +|--------|--------------------------|----------------| +| **Import** | `import MemoryClient from 'mem0ai'` | `import { Memory } from 'mem0ai/oss'` | +| **Auth** | API key required (`MEM0_API_KEY`) | No API key -- config-based | +| **Execution** | API calls to `api.mem0.ai` | Local execution | +| **Infrastructure** | Fully managed | Self-managed vector DB, embedder, LLM | +| **Param style** | Top-level: `camelCase` (`userId`, `topK`), filter keys: `snake_case` (`user_id`) | Top-level: `camelCase` (`userId`, `topK`), filter keys: `snake_case` (`user_id`) | +| **Batch ops** | `batchUpdate`, `batchDelete` | Not available | +| **Webhooks** | Full CRUD | Not available | +| **Export** | `createMemoryExport` | Not available | +| **Feedback** | `feedback()` | Not available | +| **Project mgmt** | `getProject`, `updateProject` | Not available | +| **User listing** | `users()`, `deleteUser()` | Not available | +| **History** | Platform-managed | SQLite (configurable) | + +--- + +## v2 Compatibility + +If you're using SDK v2.x: + +**Naming Changes:** +- Top-level params now use camelCase: `topK`, `rerank` (not `top_k`) +- Filter keys use snake_case: `user_id`, `agent_id` +- OSS: `limit` renamed to `topK` + +**API Changes:** +```typescript +// v2 - top-level entity IDs, snake_case +await client.search("query", { user_id: "alice", top_k: 20 }); + +// v3 - filters object with snake_case keys, camelCase top-level params +await client.search("query", { filters: { user_id: "alice" }, topK: 20 }); +``` + +**Default Changes:** +| Param | v2 | v3 | +|-------|----|----| +| `topK` | 100 | 20 | +| `threshold` | none | 0.1 | +| `rerank` | true | false | + +**Removed:** +- `OutputFormat` and `API_VERSION` enums +- `organizationId`, `projectId` from constructor +- `enableGraph`, `asyncMode`, `outputFormat`, `immutable`, `expirationDate`, `filterMemories`, `batchSize`, `forceAddOnly`, `includes`, `excludes`, `keywordSearch` + +See the [v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for details. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/python.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/python.md new file mode 100644 index 0000000000..0cf35a550a --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/client/python.md @@ -0,0 +1,487 @@ +# Mem0 Python SDK Reference + +Complete reference for the `mem0ai` Python package. Covers both the Platform client (managed API) and the Open Source self-hosted variant. + +--- + +## Platform Client + +### Installation + +```bash +pip install mem0ai +export MEM0_API_KEY="m0-your-api-key" +``` + +### MemoryClient (Synchronous) + +```python +from mem0 import MemoryClient + +client = MemoryClient(api_key="m0-xxx") +``` + +**Constructor:** `MemoryClient(api_key=None)`. If `api_key` is not provided, reads from `MEM0_API_KEY` environment variable. Raises `ValueError` if no key found. + +- HTTP library: `httpx` +- Timeout: 300 seconds +- Base URL: `https://api.mem0.ai` + +### AsyncMemoryClient (Asynchronous) + +```python +from mem0 import AsyncMemoryClient + +client = AsyncMemoryClient(api_key="m0-xxx") + +# Or use as context manager +async with AsyncMemoryClient(api_key="m0-xxx") as client: + results = await client.search("query", filters={"user_id": "alice"}) +``` + +Same methods as `MemoryClient`, all `async`/`await`. Supports async context manager. + +--- + +### Memory Methods + +#### add(messages, **kwargs) + +Store new memories from messages. + +```python +messages = [ + {"role": "user", "content": "I'm a vegetarian and allergic to nuts."}, + {"role": "assistant", "content": "Got it! I'll remember that."} +] +client.add(messages, user_id="alice") +``` + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `messages` | str \| dict \| list[dict] | required | Message content. Strings auto-convert to user messages | +| `user_id` | str | None | User identifier | +| `agent_id` | str | None | Agent identifier | +| `app_id` | str | None | Application identifier | +| `run_id` | str | None | Session/run identifier | +| `metadata` | dict | None | Custom key-value pairs | +| `infer` | bool | True | If False, store raw text without LLM inference | +| `custom_categories` | list | None | Override project categories | +| `custom_instructions` | str | None | Override extraction instructions | +| `timestamp` | int \| float \| str | None | Custom timestamp (Unix epoch or ISO 8601) | + +**Returns:** `dict` -- list of events: `[{"id": "...", "event": "ADD", "data": {"memory": "..."}}]` + +#### search(query, **kwargs) + +Search memories by semantic similarity. + +```python +results = client.search("dietary preferences", filters={"user_id": "alice"}) +for mem in results.get("results", []): + print(mem["memory"], mem["score"]) +``` + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `query` | str | required | Natural language search query | +| `filters` | dict | None | Filter object with entity IDs and/or `AND`/`OR`/`NOT` conditions (e.g., `{"user_id": "alice"}`) | +| `top_k` | int | 10 | Number of results | +| `rerank` | bool | False | Enable deep semantic reranking (+150-200ms) | +| `threshold` | float | 0.1 | Minimum similarity score | +| `fields` | list | None | Specific fields to return | +| `categories` | list | None | Filter by category | + +**Returns:** `dict` -- `{"results": [{id, memory, user_id, categories, score, created_at, ...}]}` + +#### get(memory_id) + +Retrieve a single memory by ID. + +```python +memory = client.get(memory_id="ea925981-...") +``` + +**Returns:** `dict` -- full memory object + +#### get_all(**kwargs) + +Retrieve all memories with optional filtering. Requires at least one entity identifier. + +```python +memories = client.get_all(filters={"user_id": "alice"}) +# With compound filters +memories = client.get_all(filters={"AND": [{"user_id": "alice"}, {"categories": {"contains": "health"}}]}) +``` + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `filters` | dict | None | Filter object with entity IDs and/or `AND`/`OR`/`NOT` conditions | +| `top_k` | int | None | Limit results | +| `page` | int | None | Page number | +| `page_size` | int | None | Results per page | + +**Returns:** `dict` -- `{"results": [...]}` + +#### update(memory_id, text=None, metadata=None, timestamp=None) + +Update a memory's content, metadata, or timestamp. At least one parameter required. + +```python +client.update("ea925981-...", text="Updated: vegan since 2024") +client.update("ea925981-...", metadata={"verified": True}) +``` + +**Returns:** `dict` -- updated memory + +#### delete(memory_id) + +Permanently delete a single memory. + +```python +client.delete("ea925981-...") +``` + +#### delete_all(**kwargs) + +Delete all memories matching filters. Irreversible. + +```python +client.delete_all(user_id="alice") +``` + +#### history(memory_id) + +Get the change history of a memory. + +```python +history = client.history("ea925981-...") +# Returns: [{previous_value, new_value, action, timestamps}] +``` + +--- + +### Batch Methods + +#### batch_update(memories) + +Update up to 1000 memories in a single request. + +```python +client.batch_update([ + {"memory_id": "uuid-1", "text": "Updated text"}, + {"memory_id": "uuid-2", "text": "Another update", "metadata": {"verified": True}}, +]) +``` + +#### batch_delete(memories) + +Delete up to 1000 memories in a single request. + +```python +client.batch_delete([ + {"memory_id": "uuid-1"}, + {"memory_id": "uuid-2"}, +]) +``` + +--- + +### User/Entity Management + +#### users() + +List all users, agents, and sessions that have memories. + +```python +users = client.users() +# Returns: {"results": [{"type": "user", "name": "alice"}, ...]} +``` + +#### delete_users(user_id=None, agent_id=None, app_id=None, run_id=None) + +Delete a specific entity and all its memories. + +```python +client.delete_users(user_id="alice") +``` + +#### reset() + +Delete ALL users, agents, sessions, and memories. Complete data reset. + +```python +client.reset() +``` + +--- + +### Export & Summary + +#### create_memory_export(schema, **kwargs) + +Create a structured export of memories. + +```python +import json + +schema = json.dumps({ + "type": "object", + "properties": { + "name": {"type": "string"}, + "preferences": {"type": "array", "items": {"type": "string"}}, + } +}) +export = client.create_memory_export(schema=schema, user_id="alice") +``` + +#### get_memory_export(**kwargs) + +Retrieve a previously created export. + +```python +result = client.get_memory_export(memory_export_id=export["id"]) +``` + +#### get_summary(filters=None) + +Get a summary of memories. + +```python +summary = client.get_summary(filters={"user_id": "alice"}) +``` + +--- + +### Feedback + +#### feedback(memory_id, feedback=None, feedback_reason=None) + +Provide quality feedback on a memory. + +```python +client.feedback( + memory_id="mem-123", + feedback="POSITIVE", # POSITIVE | NEGATIVE | VERY_NEGATIVE | None (clear) + feedback_reason="Accurately captured preference" +) +``` + +--- + +### Webhooks + +```python +# List +webhooks = client.get_webhooks(project_id="proj_123") + +# Create +webhook = client.create_webhook( + url="https://your-app.com/webhook", + name="Memory Logger", + project_id="proj_123", + event_types=["memory_add", "memory_update"] +) + +# Update +client.update_webhook(webhook_id=123, name="Updated", url="https://new-url.com") + +# Delete +client.delete_webhook(webhook_id=123) +``` + +--- + +### Project Management + +Access via `client.project.*`: + +```python +# Get project config +config = client.project.get(fields=["custom_categories", "custom_instructions"]) + +# Update project settings +client.project.update( + custom_instructions="Extract dietary preferences and health info", + custom_categories=[{"health": "Medical and dietary info"}], + multilingual=True, +) + +# Create/delete project +client.project.create(name="My Project", description="...") +client.project.delete() + +# Member management +members = client.project.get_members() +client.project.add_member(email="user@example.com", role="READER") # READER or OWNER +client.project.update_member(email="user@example.com", role="OWNER") +client.project.remove_member(email="user@example.com") +``` + +--- + +## Open Source / Self-Hosted + +### Installation + +```bash +pip install mem0ai +``` + +### Memory Class + +```python +from mem0 import Memory + +m = Memory() # Uses default config (OpenAI embedder + in-memory vector store) +``` + +**Import:** `from mem0 import Memory` (NOT `MemoryClient` -- that is the Platform client) + +### Configuration + +```python +config = { + "llm": { + "provider": "openai", # openai, groq, azure, ollama, lmstudio, google, anthropic, mistral + "config": { + "model": "gpt-5-mini", + "api_key": "sk-xxx", + } + }, + "embedder": { + "provider": "openai", # openai, ollama, azure, lmstudio, google, huggingface + "config": { + "model": "text-embedding-3-small", + "api_key": "sk-xxx", + } + }, + "vector_store": { + "provider": "qdrant", # faiss, qdrant, pgvector, redis, supabase, azure_ai_search, memory + "config": { + "collection_name": "my_memories", + "host": "localhost", + "port": 6333, + } + }, + "history_db_path": "history.db", # SQLite path for change history + "custom_instructions": "...", # Custom LLM prompt for extraction +} + +m = Memory.from_config(config) +``` + +### Context Manager + +```python +with Memory(config) as m: + m.add("I prefer dark mode", user_id="alice") + results = m.search("preferences", filters={"user_id": "alice"}) +# SQLite connections released automatically +``` + +### Methods + +All methods mirror the Platform client but run locally: + +#### add(messages, *, user_id, agent_id, run_id, metadata, infer=True) + +```python +m.add("I'm a vegetarian", user_id="alice") +m.add([ + {"role": "user", "content": "I like hiking"}, + {"role": "assistant", "content": "Great outdoor activity!"} +], user_id="alice") +``` + +At least one of `user_id`, `agent_id`, `run_id` required. + +**Returns:** `{"results": [...], "relations": [...]}` + +#### search(query, *, filters=None, top_k=20, threshold=0.1, rerank=False) + +```python +results = m.search("dietary preferences", filters={"user_id": "alice"}, top_k=5) +``` + +Entity IDs (`user_id`, `agent_id`, `run_id`) must be passed inside the `filters` dict. + +Supports filter operators: `eq`, `ne`, `in`, `nin`, `gt`, `gte`, `lt`, `lte`, `contains`, `not_contains`. + +#### get(memory_id) / get_all(**kwargs) / update(memory_id, data, metadata=None) / delete(memory_id) / delete_all(**kwargs) / history(memory_id) + +Same interface as Platform client. + +#### reset() + +Clear the entire vector store collection and history database. Recreates the vector store. + +```python +m.reset() +``` + +#### close() + +Release SQLite connections. Called automatically when using context manager. + +### AsyncMemory + +```python +from mem0 import AsyncMemory + +m = AsyncMemory(config) +await m.add("text", user_id="alice") +results = await m.search("query", filters={"user_id": "alice"}) +``` + +--- + +## Key Differences: Platform vs OSS + +| Aspect | Platform (`MemoryClient`) | OSS (`Memory`) | +|--------|--------------------------|----------------| +| **Import** | `from mem0 import MemoryClient` | `from mem0 import Memory` | +| **Auth** | API key required (`MEM0_API_KEY`) | No API key -- config-based | +| **Execution** | API calls to `api.mem0.ai` | Local execution | +| **Infrastructure** | Fully managed | Self-managed vector DB, embedder, LLM | +| **Entity filtering** | `filters={"user_id": "..."}` | `filters={"user_id": "..."}` | +| **Batch ops** | `batch_update`, `batch_delete` | Not available | +| **Webhooks** | Full CRUD | Not available | +| **Export** | `create_memory_export`, `get_memory_export` | Not available | +| **Feedback** | `feedback()` | Not available | +| **Project mgmt** | `client.project.*` | Not available | +| **User listing** | `users()`, `delete_users()` | Not available | +| **Custom prompts** | Via project settings | Direct config (`custom_instructions`) | +| **History** | Platform-managed | SQLite (configurable) | +| **Async** | `AsyncMemoryClient` | `AsyncMemory` | + +--- + +## v2 Compatibility + +If you're using SDK v2.x or the v2 API: + +**API Changes:** +- **Entity IDs in search/get_all:** Pass `user_id`, `agent_id` as top-level kwargs instead of inside `filters` + ```python + # v2 + results = client.search("query", user_id="alice") + # v3 + results = client.search("query", filters={"user_id": "alice"}) + ``` +- **add() returns:** v2 returns ADD, UPDATE, DELETE events; v3 returns ADD only + +**Default Changes:** +| Param | v2 | v3 | +|-------|----|----| +| `top_k` | 100 | 20 | +| `threshold` | None | 0.1 | +| `rerank` | True | False | + +**Removed Parameters:** +- Constructor: `org_id`, `project_id` +- add(): `async_mode`, `output_format`, `enable_graph`, `immutable`, `expiration_date`, `filter_memories`, `batch_size`, `force_add_only`, `includes`, `excludes`, `keyword_search` +- search()/get_all(): `enable_graph` +- Config: `enable_graph`, `graph_store`, `custom_fact_extraction_prompt` (renamed to `custom_instructions`) + +See the [v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for full details. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/api-reference.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/api-reference.md new file mode 100644 index 0000000000..4ab8548cac --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/api-reference.md @@ -0,0 +1,150 @@ +# Mem0 Platform API Reference + +REST API endpoints for the Mem0 Platform. Base URL: `https://api.mem0.ai` + +All endpoints require: `Authorization: Token ` + +## Endpoints + +| Operation | Method | URL | +|-----------|--------|-----| +| Add Memories | `POST` | `/v3/memories/add/` | +| Search Memories | `POST` | `/v3/memories/search/` | +| Get All Memories | `POST` | `/v3/memories/` | +| Get Single Memory | `GET` | `/v1/memories/{memory_id}/` | +| Update Memory | `PUT` | `/v1/memories/{memory_id}/` | +| Delete Memory | `DELETE` | `/v1/memories/{memory_id}/` | +| Delete All Memories | `DELETE` | `/v1/memories/?user_id=X&app_id=Y` | +| Get Event Status | `GET` | `/v1/event/{event_id}/` | + +## Memory Object Structure + +| Field | Type | Description | +|-------|------|-------------| +| `id` | string (UUID) | Unique memory identifier | +| `memory` | string | Text content of the memory | +| `user_id` | string | Associated user | +| `agent_id` | string (nullable) | Agent identifier | +| `app_id` | string (nullable) | Application identifier | +| `run_id` | string (nullable) | Run/session identifier | +| `metadata` | object | Custom key-value pairs | +| `categories` | array of strings | Auto-assigned category tags | +| `hash` | string | Content hash | +| `created_at` | datetime | Creation timestamp | +| `updated_at` | datetime | Last modification timestamp | + +Search results additionally include `score` (relevance metric). + +## Scoping Identifiers + +Memories can be scoped to different levels: + +| Scope | Parameter | Use Case | +|-------|-----------|----------| +| User | `user_id` | Per-user memory isolation | +| Agent | `agent_id` | Per-agent memory partitioning | +| Application | `app_id` | Cross-agent app-level memory | +| Run/Session | `run_id` | Session-scoped temporary memory | + +**Critical:** Combining `user_id` and `agent_id` in a single AND filter yields empty results. Entities are stored separately. Use `OR` logic or separate queries. + +## Processing Model + +- Memories are processed **asynchronously** (v3 default) +- Add responses return queued `ADD` events only (v3 is ADD-only, no UPDATE/DELETE) +- Poll status via `GET /v1/event/{event_id}/` + +## Filter System + +Filters use nested JSON with a logical operator at the root: + +```json +{ + "AND": [ + {"user_id": "alice"}, + {"categories": {"contains": "finance"}}, + {"created_at": {"gte": "2024-01-01"}} + ] +} +``` + +Root must be `AND`, `OR`, or `NOT`. Simple shorthand `{"user_id": "alice"}` also works. + +### Supported Operators + +| Operator | Description | +|----------|-------------| +| `eq` | Equal to (default) | +| `ne` | Not equal to | +| `in` | Matches any value in array | +| `gt`, `gte` | Greater than / greater than or equal | +| `lt`, `lte` | Less than / less than or equal | +| `contains` | Case-sensitive containment | +| `icontains` | Case-insensitive containment | +| `*` | Wildcard -- matches any non-null value | + +### Filterable Fields + +| Field | Valid Operators | +|-------|-----------------| +| `user_id`, `agent_id`, `app_id`, `run_id` | `eq`, `ne`, `in`, `*` | +| `created_at`, `updated_at`, `timestamp` | `gt`, `gte`, `lt`, `lte`, `eq`, `ne` | +| `categories` | `eq`, `ne`, `in`, `contains` | +| `metadata` | `eq`, `ne`, `contains` (top-level keys only) | +| `keywords` | `contains`, `icontains` | +| `memory_ids` | `in` | + +### Filter Constraints + +1. **Entity scope partitioning:** `user_id` AND `agent_id` in one `AND` block yields empty results. +2. **Metadata limitations:** Only top-level keys. Only `eq`, `contains`, `ne`. No `in` or `gt`. +3. **Operator syntax:** Use `gte`, `lt`, `ne`. SQL-style (`>=`, `!=`) rejected. +4. **Entity filter required for get-all:** At least one of `user_id`, `agent_id`, `app_id`, or `run_id`. +5. **Wildcard excludes null:** `*` matches only non-null values. +6. **Date format:** ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). Timezone-naive defaults to UTC. + +## Response Formats + +### Add Response (v3) + +```json +{ + "message": "Memory processing has been queued for background execution", + "status": "PENDING", + "event_id": "evt-uuid" +} +``` + +v3 is ADD-only. No UPDATE or DELETE events. + +### Search Response + +```json +{ + "results": [ + { + "id": "ea925981-...", + "memory": "Is a vegetarian and allergic to nuts.", + "user_id": "user123", + "categories": ["food", "health"], + "score": 0.89, + "created_at": "2024-07-26T10:29:36.630547-07:00" + } + ] +} +``` + +In v3, `score` is a combined multi-signal relevance score. + +### Get All Response (v3) + +```json +{ + "count": 123, + "next": "https://api.mem0.ai/v3/memories/?page=2&page_size=50", + "previous": null, + "results": [...] +} +``` + +v3 returns paginated envelope. Use `page` and `page_size` query params. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/architecture.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/architecture.md new file mode 100644 index 0000000000..4a04c820b1 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/architecture.md @@ -0,0 +1,330 @@ +# Mem0 Platform Architecture + +How Mem0 processes, stores, and retrieves memories under the hood. + +## Table of Contents + +- [Core Concept](#core-concept) +- [Memory Processing Pipeline](#memory-processing-pipeline) +- [Retrieval Pipeline](#retrieval-pipeline) +- [Memory Lifecycle](#memory-lifecycle) +- [Memory Object Structure](#memory-object-structure) +- [Scoping & Multi-Tenancy](#scoping--multi-tenancy) +- [Memory Layers](#memory-layers) +- [Performance Characteristics](#performance-characteristics) + +--- + +## Core Concept + +Mem0 is a managed memory layer that sits between your AI application and users. Every integration follows the same 3-step loop: + +``` +User Input → Retrieve relevant memories → Enrich LLM prompt → Generate response → Store new memories +``` + +Mem0 handles the complexity of extraction, deduplication, conflict resolution, and semantic retrieval so your application only needs to call `search()` and `add()`. + +**Storage architecture:** +- **Vector store**: Embeddings for semantic similarity search +- **Entity store**: Automatic entity linking for relationship-aware retrieval + +--- + +## Memory Processing Pipeline + +### What happens when you call `client.add()` + +``` +Messages In + │ + ▼ +┌─────────────────────┐ +│ 1. EXTRACTION │ Single LLM call extracts all distinct new facts +│ (infer=True) │ If infer=False, stores raw text as-is +└─────────┬───────────┘ + │ + ▼ +┌─────────────────────┐ +│ 2. DEDUPLICATION │ Hash-based dedup (MD5 prevents exact duplicates) +│ │ No UPDATE/DELETE - v3 is ADD-only +└─────────┬───────────┘ + │ + ▼ +┌─────────────────────┐ +│ 3. STORAGE │ Batch embed → vector store +│ │ Entity extraction → entity store +└─────────┬───────────┘ + │ + ▼ + Memory Object +``` + +### Processing (v3) + +v3 processes memories asynchronously by default: +- API returns immediately: `{"status": "PENDING", "event_id": "evt-..."}` +- Poll status via `GET /v1/event/{event_id}/` +- Use webhooks for completion notifications + +### Extraction modes + +**Inferred (`infer=True`, default):** +- LLM extracts structured facts from conversation +- Conflict resolution deduplicates and resolves contradictions +- Best for: natural conversation → memory + +**Raw (`infer=False`):** +- Stores text exactly as provided, no LLM processing +- Skips conflict resolution — same fact can be stored twice +- Only `user` role messages are stored; `assistant` messages ignored +- Best for: bulk imports, pre-structured data, migrations + +**Warning:** Don't mix `infer=True` and `infer=False` for the same data — the same fact will be stored twice. + +--- + +## Retrieval Pipeline (v3) + +### What happens when you call `client.search()` + +``` +Query In + │ + ▼ +┌─────────────────────┐ +│ 1. PREPROCESSING │ Lemmatize keywords, extract entities +└─────────┬───────────┘ + │ + ▼ +┌─────────────────────┐ +│ 2. PARALLEL SCORING │ Semantic search (vector similarity) +│ │ BM25 keyword search (term matching) +│ │ Entity matching (entity graph boost) +└─────────┬───────────┘ + │ + ▼ +┌─────────────────────┐ +│ 3. SCORE FUSION │ Combine signals into single score +│ │ Optional: rerank=True for deep reordering +└─────────┬───────────┘ + │ + ▼ + Results (combined score per memory) +``` + +### v3 Search Defaults + +| Parameter | Default | Notes | +|-----------|---------|-------| +| `top_k` | 20 | Was 100 in v2 | +| `threshold` | 0.1 | Was None in v2 | +| `rerank` | False | Was True in v2 | + +### Implicit null scoping + +When you search with `filters={"user_id": "alice"}` only, Mem0 returns memories where `agent_id`, `app_id`, and `run_id` are all null. This prevents cross-scope leakage by default. + +To include memories with non-null fields, use explicit filters: +```python +# Gets memories for alice regardless of agent/app/run +filters={"OR": [{"user_id": "alice"}]} +``` + +--- + +## Memory Lifecycle (v3) + +v3 uses ADD-only extraction. Memories accumulate over time rather than being consolidated. + +### Creation +- `client.add(messages, user_id="...")` +- Single-pass extraction → deduplication → storage +- Returns `{"event_id": "...", "status": "PENDING"}` + +### Updates +- `client.update(memory_id, text="...")` replaces text +- Batch: `client.batch_update([...])` + +### Deletion +- Single: `client.delete(memory_id)` +- Batch: `client.batch_delete([...])` +- Bulk: `client.delete_all(filters={"user_id": "alice"})` + +--- + +## Memory Object Structure + +```json +{ + "id": "uuid-string", + "memory": "Extracted memory text", + "user_id": "user-identifier", + "agent_id": null, + "app_id": null, + "run_id": null, + "metadata": { "source": "chat", "priority": "high" }, + "categories": ["health", "preferences"], + "created_at": "2025-03-12T12:34:56Z", + "updated_at": "2025-03-12T12:34:56Z", + "structured_attributes": { + "day": 12, "month": 3, "year": 2025, + "hour": 12, "minute": 34, + "day_of_week": "wednesday", + "is_weekend": false, + "quarter": 1, "week_of_year": 11 + }, + "score": 0.85 +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `id` | UUID | Unique identifier, used for update/delete | +| `memory` | string | Extracted or stored text content | +| `user_id` | string | Primary entity scope | +| `agent_id` | string | Agent scope | +| `app_id` | string | Application scope | +| `run_id` | string | Session/run scope | +| `metadata` | object | Custom key-value pairs for filtering | +| `categories` | array | Auto-assigned or custom category tags | +| `created_at` | datetime | Creation timestamp | +| `updated_at` | datetime | Last modification timestamp | +| `structured_attributes` | object | Temporal breakdown for time-based queries | +| `score` | float | Semantic similarity (search results only, 0-1) | + +--- + +## Scoping & Multi-Tenancy + +Mem0 separates memories across four dimensions to prevent data mixing: + +| Dimension | Field | Purpose | Example | +|-----------|-------|---------|---------| +| User | `user_id` | Persistent persona or account | `"customer_6412"` | +| Agent | `agent_id` | Distinct agent or tool | `"meal_planner"` | +| App | `app_id` | Product surface or deployment | `"ios_retail_app"` | +| Session | `run_id` | Short-lived flow or thread | `"ticket-9241"` | + +### Storage model + +Each entity combination creates separate records. A memory with `user_id="alice"` is stored separately from one with `user_id="alice"` + `agent_id="bot"`. + +### Critical: cross-entity queries + +```python +# This returns NOTHING — user and agent memories are stored separately +filters={"AND": [{"user_id": "alice"}, {"agent_id": "bot"}]} + +# Use OR to query multiple scopes +filters={"OR": [{"user_id": "alice"}, {"agent_id": "bot"}]} + +# Use wildcard to include any non-null value +filters={"AND": [{"user_id": "*"}]} # All users (excludes null) +``` + +### Recommended scoping patterns + +```python +# User-level: persistent preferences +client.add(messages, user_id="alice") + +# Session-level: temporary context +client.add(messages, user_id="alice", run_id="session_123") +# Clean up when done: client.delete_all(run_id="session_123") + +# Agent-level: agent-specific knowledge +client.add(messages, agent_id="support_bot", app_id="helpdesk") + +# Multi-tenant: full isolation +client.add(messages, user_id="alice", agent_id="bot", app_id="acme_corp", run_id="ticket_42") +``` + +--- + +## Memory Layers + +Mem0 supports three layers of memory, from shortest to longest lived: + +### Conversation memory +- In-flight messages within a single turn +- Tool calls, chain-of-thought reasoning +- **Lifetime:** Single response — lost after turn finishes +- **Managed by:** Your application, not Mem0 + +### Session memory +- Short-lived facts for current task or channel +- Multi-step flows (onboarding, debugging, support tickets) +- **Lifetime:** Minutes to hours +- **Managed by:** Mem0 via `run_id` parameter +- Clean up with `client.delete_all(run_id="session_id")` + +### User memory +- Long-lived knowledge tied to a person or account +- Personal preferences, account state, compliance details +- **Lifetime:** Weeks to forever +- **Managed by:** Mem0 via `user_id` parameter +- Persists across all sessions and interactions + +### How layering works in practice + +```python +def chat(user_input: str, user_id: str, session_id: str) -> str: + # 1. Retrieve user memories (long-term preferences) + user_mems = mem0.search(user_input, filters={"user_id": user_id}) + + # 2. Retrieve session memories (current task context) + session_mems = mem0.search(user_input, filters={ + "AND": [{"user_id": user_id}, {"run_id": session_id}] + }) + + # 3. Combine both layers for LLM context + context = format_memories(user_mems) + format_memories(session_mems) + + # 4. Generate response + response = llm.generate(context=context, input=user_input) + + # 5. Store in session scope (temporary) + user scope (persistent) + messages = [{"role": "user", "content": user_input}, {"role": "assistant", "content": response}] + mem0.add(messages, user_id=user_id, run_id=session_id) + + return response +``` + +--- + +## Performance Characteristics + +### Latency + +| Operation | Typical Latency | +|-----------|----------------| +| Hybrid search (v3 default) | ~100-150ms | +| + reranking | +150-200ms | +| Add (async) | < 50ms response | + +### Processing + +- **Async (default):** Returns immediately, processes in background +- **Batch operations:** Up to 1000 memories per batch_update/batch_delete +- **Webhooks:** Real-time notifications when async processing completes + +### Scoping strategy for performance + +- Use `user_id` for all user-facing queries (most common, fastest) +- Add `run_id` for session isolation (narrows search space) +- Avoid wildcard `"*"` filters on large datasets (scans all non-null records) +- Use `top_k` to limit result count when you only need a few memories + +--- + +## Comparison with Alternatives + +| Approach | Pros | Cons | +|----------|------|------| +| **Raw vector DB** | Fast, full control | No extraction, no dedup, no conflict resolution | +| **In-memory chat history** | Zero latency | Lost on restart, no cross-session, grows unbounded | +| **RAG over documents** | Good for static knowledge | No personalization, no memory updates | +| **Mem0 Platform** | Managed extraction + dedup + graph + scoping | External dependency, async processing delay | + +Mem0 combines the best of vector search (semantic retrieval) with automatic extraction (LLM-powered), conflict resolution (deduplication), and structured scoping (multi-tenancy) — in a single managed API. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/features.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/features.md new file mode 100644 index 0000000000..66875ee753 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/features.md @@ -0,0 +1,425 @@ +# Platform Features -- Mem0 Platform + +Additional platform capabilities beyond core CRUD operations. + +## Table of Contents + +- [Advanced Retrieval](#advanced-retrieval) +- [Entity Linking](#entity-linking) +- [Custom Categories](#custom-categories) +- [Custom Instructions](#custom-instructions) +- [Criteria Retrieval](#criteria-retrieval) +- [Feedback Mechanism](#feedback-mechanism) +- [Memory Export](#memory-export) +- [Group Chat](#group-chat) +- [MCP Integration](#mcp-integration) +- [Webhooks](#webhooks) +- [Multimodal Support](#multimodal-support) + +## Advanced Retrieval + +### Hybrid Search (v3 Default) + +v3 uses multi-signal hybrid search combining: +- **Semantic search** (vector similarity) +- **BM25 keyword search** (normalized term matching) +- **Entity matching** (entity graph boost) + +This is automatic — no configuration needed. + +### Reranking (`rerank=True`) + +Deep semantic reordering of results — most relevant first. + +- Latency: +150-200ms +- Default: `False` (was `True` in v2) +- Best for: user-facing results, top-N precision + +**Python:** +```python +results = client.search(query, filters={"user_id": "user123"}, rerank=True) +``` + +**TypeScript:** +```typescript +const results = await client.search(query, { + filters: { user_id: 'user123' }, + rerank: true, +}); +``` + +--- + +## Entity Linking + +v3 replaces graph memory with built-in entity linking. Entities (proper nouns, quoted text, compound noun phrases) are automatically extracted and linked across memories. + +### How It Works + +1. **Extraction**: During `add()`, entities are automatically extracted from memory text +2. **Storage**: Entities are stored in a parallel collection (`{collection}_entities`) +3. **Retrieval**: During `search()`, query entities are matched and used to boost relevant memories + +Entity linking is automatic — no configuration required. The boost is folded into the combined `score` on each result. + +### v2 Migration Note + +If you were using `enable_graph=True` in v2: +- Remove `enable_graph` from all API calls +- Remove `graph_store` from OSS configuration +- Entity relationships are now consumed through retrieval ranking, not exposed as a separate `relations` array + +See the [v2 to v3 migration guide](https://docs.mem0.ai/migration/platform-v2-to-v3) for details. + +--- + +## Custom Categories + +Replace Mem0's default 15 labels with domain-specific categories. The system automatically tags memories to the closest matching category. + +### Default Categories (15) + +`personal_details`, `family`, `professional_details`, `sports`, `travel`, `food`, `music`, `health`, `technology`, `hobbies`, `fashion`, `entertainment`, `milestones`, `user_preferences`, `misc` + +### Configuration + +**Set project-level categories:** +```python +new_categories = [ + {"lifestyle_management": "Tracks daily routines, habits, wellness activities"}, + {"seeking_structure": "Documents goals around creating routines and systems"}, + {"personal_information": "Basic information about the user"} +] +client.project.update(custom_categories=new_categories) +``` + +```javascript +await client.updateProject({ customCategories: newCategories }); +``` + +**Retrieve active categories:** +```python +categories = client.project.get(fields=["custom_categories"]) +``` + +### Key Constraint + +Per-request overrides (`custom_categories=...` on `client.add`) are **not supported** on the managed API. Only project-level configuration works. Workaround: store ad-hoc labels in `metadata` field. + +--- + +## Custom Instructions + +Natural language filters that control what information Mem0 extracts when creating memories. + +### Set Instructions + +```python +client.project.update(custom_instructions="Your guidelines here...") +``` + +```javascript +await client.updateProject({ customInstructions: "Your guidelines here..." }); +``` + +### Template Structure + +1. **Task Description** -- brief extraction overview +2. **Information Categories** -- numbered sections with specific details to capture +3. **Processing Guidelines** -- quality and handling rules +4. **Exclusion List** -- sensitive/irrelevant data to filter out + +### Domain Examples + +**E-commerce:** Capture product issues, preferences, service experience; exclude payment data. + +**Education:** Extract learning progress, student preferences, performance patterns; exclude specific grades. + +**Finance:** Track financial goals, life events, investment interests; exclude account numbers and SSNs. + +### Best Practices + +- Start simply, test with sample messages, iterate based on results +- Avoid overly lengthy instructions +- Be specific about what to include AND exclude + +--- + +## Criteria Retrieval + +Custom attribute-based memory ranking using LLM-evaluated criteria with weights. Goes beyond semantic similarity to prioritize memories based on domain-specific signals. + +### Configuration + +```python +# Define criteria at project level +retrieval_criteria = [ + {"name": "joy", "description": "Positive emotions like happiness and excitement", "weight": 3}, + {"name": "curiosity", "description": "Inquisitiveness and desire to learn", "weight": 2}, + {"name": "urgency", "description": "Time-sensitive or high-priority items", "weight": 4}, +] +client.project.update(retrieval_criteria=retrieval_criteria) +``` + +```typescript +await client.updateProject({ + retrievalCriteria: [ + { name: 'joy', description: 'Positive emotions', weight: 3 }, + { name: 'urgency', description: 'Time-sensitive items', weight: 4 }, + ], +}); +``` + +### Usage + +Once configured, `client.search()` automatically applies criteria ranking: + +```python +# Criteria-weighted results returned automatically +results = client.search("Why am I feeling happy?", filters={"user_id": "alice"}) +``` + +**Best for:** Wellness assistants, tutoring platforms, productivity tools — any app needing intent-aware retrieval. + +--- + +## Feedback Mechanism + +Provide feedback on extracted memories to improve system quality over time. + +### Feedback Types + +| Type | Meaning | +|------|---------| +| `POSITIVE` | Memory is useful and accurate | +| `NEGATIVE` | Memory is not useful | +| `VERY_NEGATIVE` | Memory is harmful or completely wrong | +| `None` | Clear existing feedback | + +### Usage + +**Python:** +```python +client.feedback( + memory_id="mem-123", + feedback="POSITIVE", + feedback_reason="Accurately captured dietary preference" +) + +# Bulk feedback +for item in feedback_data: + client.feedback(**item) +``` + +**TypeScript:** +```typescript +await client.feedback('mem-123', { + feedback: 'POSITIVE', + feedbackReason: 'Accurately captured dietary preference', +}); +``` + +--- + +## Memory Export + +Create structured exports of memories using customizable schemas with filters. + +### Usage + +```python +import json + +# Define export schema +schema = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "preferences": {"type": "array", "items": {"type": "string"}}, + "health_info": {"type": "string"}, + } +} + +# Create export +response = client.create_memory_export( + schema=json.dumps(schema), + filters={"user_id": "alice"}, + export_instructions="Create comprehensive profile based on all memories" +) + +# Retrieve export (may take a moment to process) +result = client.get_memory_export(memory_export_id=response["id"]) +``` + +**Best for:** Data analytics, user profile generation, compliance audits, CRM sync. + +--- + +## Group Chat + +Process multi-participant conversations and automatically attribute memories to individual speakers. + +### Usage + +```python +messages = [ + {"role": "user", "name": "Alice", "content": "I think we should use React for the frontend"}, + {"role": "user", "name": "Bob", "content": "I prefer Vue.js, it's simpler for our use case"}, + {"role": "assistant", "content": "Both are great choices. Let me note your preferences."}, +] + +# Mem0 automatically attributes memories to each speaker +response = client.add(messages, run_id="team_meeting_1") + +# Retrieve Alice's memories from that session +alice_mems = client.get_all( + filters={"AND": [{"user_id": "alice"}, {"run_id": "team_meeting_1"}]} +) +``` + +Use the `name` field in messages to identify speakers. Mem0 maps names to entity scopes automatically. + +--- + +## MCP Integration + +Model Context Protocol integration enables AI clients (Claude, Claude Code, Cursor, Windsurf, VS Code, OpenCode) to manage Mem0 memory autonomously. + +### Setup + +Add Mem0 MCP to your clients with a single command: + +```bash +npx mcp-add \ + --name mem0-mcp \ + --type http \ + --url "https://mcp.mem0.ai/mcp" \ + --clients "claude,claude code,cursor,windsurf,vscode,opencode" +``` + +### Available MCP Tools + +The MCP server exposes 9 memory tools that AI agents can use autonomously: +- Add, search, get, update, delete memories +- Get history, list users, delete users +- Search Mem0 documentation + +### How It Works + +1. Add Mem0 MCP to your AI client using the setup command above +2. The agent autonomously decides when to store/retrieve memories +3. No manual API calls needed — the agent manages memory as part of its reasoning + +**Best for:** Universal AI client integration — one protocol works everywhere. + +--- + +## Webhooks + +Real-time event notifications for memory operations. + +### Supported Events + +| Event | Trigger | +|-------|---------| +| `memory_add` | Memory created | +| `memory_update` | Memory modified | +| `memory_delete` | Memory removed | +| `memory_categorize` | Memory tagged | + +### Create Webhook + +Note: `project_id` here refers to the Mem0 dashboard project scope for webhooks — not the deprecated client init parameter. + +```python +webhook = client.create_webhook( + url="https://your-app.com/webhook", + name="Memory Logger", + project_id="proj_123", + event_types=["memory_add", "memory_categorize"] +) +``` + +### Manage Webhooks + +```python +# Retrieve +webhooks = client.get_webhooks(project_id="proj_123") + +# Update +client.update_webhook( + name="Updated Logger", + url="https://your-app.com/new-webhook", + event_types=["memory_update", "memory_add"], + webhook_id="wh_123" +) + +# Delete +client.delete_webhook(webhook_id="wh_123") +``` + +### Payload Structure + +Memory events contain: ID, data object with memory content, event type (`ADD`/`UPDATE`/`DELETE`). +Categorization events contain: memory ID, event type (`CATEGORIZE`), assigned category labels. + +--- + +## Multimodal Support + +Mem0 can process images and documents alongside text. + +### Supported Media Types + +- Images: JPG, PNG +- Documents: MDX, TXT, PDF + +### Image via URL + +```python +image_message = { + "role": "user", + "content": { + "type": "image_url", + "image_url": {"url": "https://example.com/image.jpg"} + } +} +client.add([image_message], user_id="alice") +``` + +### Image via Base64 + +```python +import base64 +with open("photo.jpg", "rb") as f: + base64_image = base64.b64encode(f.read()).decode("utf-8") + +image_message = { + "role": "user", + "content": { + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"} + } +} +client.add([image_message], user_id="alice") +``` + +### Document (MDX/TXT) + +```python +doc_message = { + "role": "user", + "content": {"type": "mdx_url", "mdx_url": {"url": document_url}} +} +client.add([doc_message], user_id="alice") +``` + +### PDF Document + +```python +pdf_message = { + "role": "user", + "content": {"type": "pdf_url", "pdf_url": {"url": pdf_url}} +} +client.add([pdf_message], user_id="alice") +``` diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/integration-patterns.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/integration-patterns.md new file mode 100644 index 0000000000..71cfa981c8 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/integration-patterns.md @@ -0,0 +1,395 @@ +# Mem0 Integration Patterns + +Working code examples for integrating Mem0 Platform with popular AI frameworks. +All examples use `MemoryClient` (Platform API key). + +Code examples are sourced from official Mem0 integration docs at docs.mem0.ai, simplified for quick reference. + +--- + +## Common Pattern + +Every integration follows the same 3-step loop: + +1. **Retrieve** -- search relevant memories before generating a response +2. **Generate** -- include memories as context in the LLM prompt +3. **Store** -- save the interaction back to Mem0 for future use + +--- + +## LangChain + +Source: [docs.mem0.ai/integrations/langchain](https://docs.mem0.ai/integrations/langchain) + +```python +from langchain_openai import ChatOpenAI +from langchain_core.messages import SystemMessage, HumanMessage +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from mem0 import MemoryClient + +llm = ChatOpenAI(model="gpt-5-mini") +mem0 = MemoryClient() + +prompt = ChatPromptTemplate.from_messages([ + SystemMessage(content="You are a helpful travel agent AI. Use the provided context to personalize your responses."), + MessagesPlaceholder(variable_name="context"), + HumanMessage(content="{input}") +]) + +def retrieve_context(query: str, user_id: str): + """Retrieve relevant memories from Mem0""" + memories = mem0.search(query, filters={"user_id": user_id}) + memory_list = memories['results'] + serialized = ' '.join([m["memory"] for m in memory_list]) + return [ + {"role": "system", "content": f"Relevant information: {serialized}"}, + {"role": "user", "content": query} + ] + +def chat_turn(user_input: str, user_id: str) -> str: + # 1. Retrieve + context = retrieve_context(user_input, user_id) + # 2. Generate + chain = prompt | llm + response = chain.invoke({"context": context, "input": user_input}) + # 3. Store + mem0.add( + [{"role": "user", "content": user_input}, {"role": "assistant", "content": response.content}], + user_id=user_id + ) + return response.content +``` + +--- + +## CrewAI + +Source: [docs.mem0.ai/integrations/crewai](https://docs.mem0.ai/integrations/crewai) + +CrewAI has native Mem0 integration via `memory_config`: + +```python +from crewai import Agent, Task, Crew, Process +from mem0 import MemoryClient + +client = MemoryClient() + +# Store user preferences first +messages = [ + {"role": "user", "content": "I am more of a beach person than a mountain person."}, + {"role": "assistant", "content": "Noted! I'll recommend beach destinations."}, + {"role": "user", "content": "I like Airbnb more than hotels."}, +] +client.add(messages, user_id="crew_user_1") + +# Create agent +travel_agent = Agent( + role="Personalized Travel Planner", + goal="Plan personalized travel itineraries", + backstory="You are a seasoned travel planner.", + memory=True, +) + +# Create task +task = Task( + description="Find places to live, eat, and visit in San Francisco.", + expected_output="A detailed list of places to live, eat, and visit.", + agent=travel_agent, +) + +# Setup crew with Mem0 memory +crew = Crew( + agents=[travel_agent], + tasks=[task], + process=Process.sequential, + memory=True, + memory_config={ + "provider": "mem0", + "config": {"user_id": "crew_user_1"}, + } +) + +result = crew.kickoff() +``` + +--- + +## Vercel AI SDK + +> **Dedicated skill available.** For comprehensive Vercel AI SDK documentation, see the [mem0-vercel-ai-sdk skill](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk). + +Install: `npm install @mem0/vercel-ai-provider` + +Quick example (wrapped model with automatic memory): + +```typescript +import { generateText } from "ai"; +import { createMem0 } from "@mem0/vercel-ai-provider"; + +const mem0 = createMem0(); +const { text } = await generateText({ + model: mem0("gpt-5-mini", { user_id: "borat" }), + prompt: "Suggest me a good car to buy!", +}); +``` + +Supported providers: `openai`, `anthropic`, `google`, `groq`, `cohere` + +--- + +## OpenAI Agents SDK + +Source: [docs.mem0.ai/integrations/openai-agents-sdk](https://docs.mem0.ai/integrations/openai-agents-sdk) + +```python +from agents import Agent, Runner, function_tool +from mem0 import MemoryClient + +mem0 = MemoryClient() + +@function_tool +def search_memory(query: str, user_id: str) -> str: + """Search through past conversations and memories""" + memories = mem0.search(query, filters={"user_id": user_id}, top_k=3) + if memories and memories.get('results'): + return "\n".join([f"- {mem['memory']}" for mem in memories['results']]) + return "No relevant memories found." + +@function_tool +def save_memory(content: str, user_id: str) -> str: + """Save important information to memory""" + mem0.add([{"role": "user", "content": content}], user_id=user_id) + return "Information saved to memory." + +agent = Agent( + name="Personal Assistant", + instructions="""You are a helpful personal assistant with memory capabilities. + Use search_memory to recall past conversations. + Use save_memory to store important information.""", + tools=[search_memory, save_memory], + model="gpt-5-mini" +) + +result = Runner.run_sync(agent, "I love Italian food and I'm planning a trip to Rome next month") +print(result.final_output) +``` + +### Multi-Agent with Handoffs + +```python +from agents import Agent, Runner, function_tool + +travel_agent = Agent( + name="Travel Planner", + instructions="You are a travel planning specialist. Use search_memory and save_memory tools.", + tools=[search_memory, save_memory], + model="gpt-5-mini" +) + +health_agent = Agent( + name="Health Advisor", + instructions="You are a health and wellness advisor. Use search_memory and save_memory tools.", + tools=[search_memory, save_memory], + model="gpt-5-mini" +) + +triage_agent = Agent( + name="Personal Assistant", + instructions="""Route travel questions to Travel Planner, health questions to Health Advisor.""", + handoffs=[travel_agent, health_agent], + model="gpt-5-mini" +) + +result = Runner.run_sync(triage_agent, "Plan a healthy meal for my Italy trip") +``` + +--- + +## Pipecat (Voice / Real-Time) + +Source: [docs.mem0.ai/integrations/pipecat](https://docs.mem0.ai/integrations/pipecat) + +```python +from pipecat.services.mem0 import Mem0MemoryService + +memory = Mem0MemoryService( + api_key=os.getenv("MEM0_API_KEY"), + user_id="alice", + agent_id="voice_bot", + params={ + "search_limit": 10, + "search_threshold": 0.1, + "system_prompt": "Here are your past memories:", + "add_as_system_message": True, + } +) + +# Use in pipeline +pipeline = Pipeline([ + transport.input(), + stt, + user_context, + memory, # Memory enhances context automatically + llm, + transport.output(), + assistant_context +]) +``` + + + +--- + +## LangGraph + +Source: [docs.mem0.ai/integrations/langgraph](https://docs.mem0.ai/integrations/langgraph) + +State-based agent workflows with memory persistence. Best for complex conversation flows with branching logic. + +```python +from typing import Annotated, TypedDict, List +from langgraph.graph import StateGraph, START +from langgraph.graph.message import add_messages +from langchain_openai import ChatOpenAI +from mem0 import MemoryClient +from langchain_core.messages import SystemMessage, HumanMessage, AIMessage + +llm = ChatOpenAI(model="gpt-5-mini") +mem0 = MemoryClient() + +class State(TypedDict): + messages: Annotated[List[HumanMessage | AIMessage], add_messages] + mem0_user_id: str + +def chatbot(state: State): + messages = state["messages"] + user_id = state["mem0_user_id"] + + # Retrieve relevant memories + memories = mem0.search(messages[-1].content, filters={"user_id": user_id}) + context = "Relevant context:\n" + for memory in memories["results"]: + context += f"- {memory['memory']}\n" + + system_message = SystemMessage(content=f"""You are a helpful support assistant. +{context}""") + + response = llm.invoke([system_message] + messages) + + # Store the interaction + mem0.add( + [{"role": "user", "content": messages[-1].content}, + {"role": "assistant", "content": response.content}], + user_id=user_id + ) + return {"messages": [response]} + +graph = StateGraph(State) +graph.add_node("chatbot", chatbot) +graph.add_edge(START, "chatbot") +app = graph.compile() + +# Usage +result = app.invoke({ + "messages": [HumanMessage(content="I need help with my order")], + "mem0_user_id": "customer_123" +}) +``` + +--- + +## LlamaIndex + +Source: [docs.mem0.ai/integrations/llama-index](https://docs.mem0.ai/integrations/llama-index) + +Install: `pip install llama-index-core llama-index-memory-mem0` + +LlamaIndex has native Mem0 support via `Mem0Memory`. Works with ReAct and FunctionCalling agents. + +```python +from llama_index.memory.mem0 import Mem0Memory + +context = {"user_id": "alice", "agent_id": "llama_agent_1"} +memory = Mem0Memory.from_client( + context=context, + search_msg_limit=4, # messages from chat history used for retrieval (default: 5) +) + +# Use with LlamaIndex agent +from llama_index.core.agent import FunctionCallingAgent +from llama_index.llms.openai import OpenAI + +llm = OpenAI(model="gpt-5-mini") +agent = FunctionCallingAgent.from_tools( + tools=[], + llm=llm, + memory=memory, + verbose=True, +) + +response = agent.chat("I prefer vegetarian restaurants") +# Memory automatically stores and retrieves context +response = agent.chat("What kind of food do I like?") +# Agent retrieves the vegetarian preference from Mem0 +``` + +--- + +## AutoGen + +Source: [docs.mem0.ai/integrations/autogen](https://docs.mem0.ai/integrations/autogen) + +Install: `pip install autogen mem0ai` + +Multi-agent conversational systems with memory persistence. + +```python +from autogen import ConversableAgent +from mem0 import MemoryClient + +memory_client = MemoryClient() +USER_ID = "alice" + +agent = ConversableAgent( + "chatbot", + llm_config={"config_list": [{"model": "gpt-5-mini", "api_key": os.environ["OPENAI_API_KEY"]}]}, + code_execution_config=False, + human_input_mode="NEVER", +) + +def get_context_aware_response(question: str) -> str: + # Retrieve memories for context + relevant_memories = memory_client.search(question, filters={"user_id": USER_ID}) + context = "\n".join([m["memory"] for m in relevant_memories.get("results", [])]) + + prompt = f"""Answer considering previous interactions: + Previous context: {context} + Question: {question}""" + + reply = agent.generate_reply(messages=[{"content": prompt, "role": "user"}]) + + # Store the new interaction + memory_client.add( + [{"role": "user", "content": question}, {"role": "assistant", "content": reply}], + user_id=USER_ID + ) + return reply +``` + +--- + +## All Supported Frameworks + +Beyond the examples above, Mem0 integrates with: + +| Framework | Type | Install | +|-----------|------|---------| +| [Mastra](https://docs.mem0.ai/integrations/mastra) | TS agent framework | `npm install @mastra/mem0` | +| [ElevenLabs](https://docs.mem0.ai/integrations/elevenlabs) | Voice AI | `pip install elevenlabs mem0ai` | +| [LiveKit](https://docs.mem0.ai/integrations/livekit) | Real-time voice/video | `pip install livekit-agents mem0ai` | +| [Camel AI](https://docs.mem0.ai/integrations/camel-ai) | Multi-agent framework | `pip install camel-ai[all] mem0ai` | +| [AWS Bedrock](https://docs.mem0.ai/integrations/aws-bedrock) | Cloud LLM provider | `pip install boto3 mem0ai` | +| [Dify](https://docs.mem0.ai/integrations/dify) | Low-code AI platform | Plugin-based | +| [Google AI ADK](https://docs.mem0.ai/integrations/google-ai-adk) | Google agent framework | `pip install google-adk mem0ai` | + +For the general Python pattern (no framework), see the "Common integration pattern" in [SKILL.md](../SKILL.md). diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/quickstart.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/quickstart.md new file mode 100644 index 0000000000..132982a496 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/quickstart.md @@ -0,0 +1,119 @@ +# Mem0 Platform Quickstart + +Get running with Mem0 in 2 minutes. No infrastructure to deploy -- just an API key. + +## Prerequisites + +- Python 3.10+ or Node.js 18+ +- A Mem0 Platform API key ([Get one here](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-skill-quickstart)) + +## Python Setup + +```bash +pip install mem0ai +export MEM0_API_KEY="m0-your-api-key" +``` + +```python +from mem0 import MemoryClient + +client = MemoryClient(api_key="your-api-key") + +# Add a memory +messages = [ + {"role": "user", "content": "I'm a vegetarian and allergic to nuts."}, + {"role": "assistant", "content": "Got it! I'll remember your dietary preferences."} +] +client.add(messages, user_id="user123") + +# Search memories +results = client.search("What are my dietary restrictions?", filters={"user_id": "user123"}) +print(results) +``` + +### Async Client + +```python +from mem0 import AsyncMemoryClient + +client = AsyncMemoryClient(api_key="your-api-key") + +await client.add(messages, user_id="user123") +results = await client.search("query", filters={"user_id": "user123"}) +``` + +## TypeScript / JavaScript Setup + +```bash +npm install mem0ai +export MEM0_API_KEY="m0-your-api-key" +``` + +```javascript +import MemoryClient from 'mem0ai'; + +const client = new MemoryClient({ apiKey: 'your-api-key' }); + +// Add a memory +const messages = [ + {"role": "user", "content": "I'm a vegetarian and allergic to nuts."}, + {"role": "assistant", "content": "Got it! I'll remember your dietary preferences."} +]; +await client.add(messages, { userId: "user123" }); + +// Search memories +const results = await client.search("What are my dietary restrictions?", { + filters: { user_id: "user123" } +}); +console.log(results); +``` + +## cURL + +```bash +export MEM0_API_KEY="m0-your-api-key" + +# Add memory +curl -X POST https://api.mem0.ai/v3/memories/add/ \ + -H "Authorization: Token $MEM0_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + {"role": "user", "content": "I am a vegetarian and allergic to nuts."}, + {"role": "assistant", "content": "Got it! I will remember your dietary preferences."} + ], + "user_id": "user123" + }' + +# Search memories +curl -X POST https://api.mem0.ai/v3/memories/search/ \ + -H "Authorization: Token $MEM0_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "query": "What are my dietary restrictions?", + "filters": {"user_id": "user123"} + }' +``` + +## Sample Response + +```json +{ + "results": [ + { + "id": "14e1b28a-2014-40ad-ac42-69c9ef42193d", + "memory": "Allergic to nuts", + "user_id": "user123", + "categories": ["health"], + "created_at": "2025-10-22T04:40:22.864647-07:00", + "score": 0.30 + } + ] +} +``` + +## Next Steps + +- [SDK Guide](sdk-guide.md) -- all methods for Python and TypeScript +- [API Reference](api-reference.md) -- REST endpoints and memory object structure +- [Integration Patterns](integration-patterns.md) -- LangChain, CrewAI, Vercel AI, etc. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/sdk-guide.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/sdk-guide.md new file mode 100644 index 0000000000..512c0d19c2 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/sdk-guide.md @@ -0,0 +1,353 @@ +# Mem0 SDK Guide + +Complete SDK reference for Python and TypeScript. All methods use `MemoryClient` (Platform API). + +> **For language-specific deep references (including OSS):** See [client/python.md](../client/python.md) and [client/node.md](../client/node.md). For Python vs TypeScript differences: [client/differences.md](../client/differences.md). + +## Initialization + +**Python:** +```python +from mem0 import MemoryClient +client = MemoryClient(api_key="m0-your-api-key") +``` + +**Python (Async):** +```python +from mem0 import AsyncMemoryClient +client = AsyncMemoryClient(api_key="m0-your-api-key") +``` + +**TypeScript:** +```typescript +import MemoryClient from 'mem0ai'; +const client = new MemoryClient({ apiKey: 'm0-your-api-key' }); +``` + +Constructor accepts `apiKey` (required) and `host` (optional, default: `https://api.mem0.ai`). + +--- + +## add() -- Store Memories + +**Python:** +```python +messages = [ + {"role": "user", "content": "I'm a vegetarian and allergic to nuts."}, + {"role": "assistant", "content": "Got it! I'll remember that."} +] +client.add(messages, user_id="alice") + +# With metadata +client.add(messages, user_id="alice", metadata={"source": "onboarding"}) +``` + +**TypeScript:** +```typescript +await client.add(messages, { userId: "alice" }); +await client.add(messages, { userId: "alice", metadata: { source: "onboarding" } }); +``` + +### Parameters + +| Name | Type | Description | +|------|------|-------------| +| `messages` | array | `[{"role": "user", "content": "..."}]` | +| `user_id` | string | User identifier (recommended) | +| `agent_id` | string | Agent identifier | +| `run_id` | string | Session identifier | +| `metadata` | object | Custom key-value pairs | +| `infer` | boolean | If `false`, store raw text without inference (default: `true`) | + +### Advanced Add Options + +```python +# Agent + session scoping +client.add(messages, user_id="alice", agent_id="nutrition-agent", run_id="session-456") + +# Raw text -- skip LLM inference +client.add( + [{"role": "user", "content": "User prefers dark mode."}], + user_id="alice", + infer=False, +) +``` + +--- + +## search() -- Find Memories + +**Python:** +```python +results = client.search("dietary preferences?", filters={"user_id": "alice"}) + +# With filters and reranking +results = client.search( + query="work experience", + filters={"AND": [{"user_id": "alice"}, {"categories": {"contains": "professional_details"}}]}, + top_k=5, + rerank=True, + threshold=0.5 +) +``` + +**TypeScript:** +```typescript +const results = await client.search("dietary preferences", { filters: { user_id: "alice" } }); +const results = await client.search("work experience", { + filters: { AND: [{ user_id: "alice" }, { categories: { contains: "professional_details" } }] }, + topK: 5, + rerank: true, +}); +``` + +### Parameters + +| Name | Type | Description | +|------|------|-------------| +| `query` | string | Natural language search query | +| `filters` | object | Filter object (AND/OR operators). Use `{"user_id": "..."}` to filter by user | +| `top_k` | number | Number of results (default: 10 for Platform) | +| `rerank` | boolean | Enable reranking for better relevance (default: `false`) | +| `threshold` | number | Minimum similarity score (default: 0.1) | + +### Common Filter Patterns + +**Python:** +```python +# Single user filter +filters={"user_id": "alice"} + +# OR across agents +filters={"OR": [{"user_id": "alice"}, {"agent_id": {"in": ["travel-agent", "sports-agent"]}}]} + +# Category filtering (partial match) +filters={"AND": [{"user_id": "alice"}, {"categories": {"contains": "finance"}}]} + +# Category filtering (exact match) +filters={"AND": [{"user_id": "alice"}, {"categories": {"in": ["personal_information"]}}]} + +# Wildcard (match any non-null run) +filters={"AND": [{"user_id": "alice"}, {"run_id": "*"}]} + +# Date range +filters={"AND": [ + {"user_id": "alice"}, + {"created_at": {"gte": "2024-01-01T00:00:00Z"}}, + {"created_at": {"lt": "2024-02-01T00:00:00Z"}} +]} + +# Exclude categories with NOT +filters={"AND": [{"user_id": "user_123"}, {"NOT": {"categories": {"in": ["spam", "test"]}}}]} + +# Multi-dimensional query +filters={"AND": [ + {"user_id": "user_123"}, + {"keywords": {"icontains": "invoice"}}, + {"categories": {"in": ["finance"]}}, + {"created_at": {"gte": "2024-01-01T00:00:00Z"}} +]} +``` + +**TypeScript:** +```typescript +// Single user filter +filters: { user_id: "alice" } + +// OR across agents +filters: { OR: [{ user_id: "alice" }, { agent_id: { in: ["travel-agent", "sports-agent"] } }] } + +// Category filtering (partial match) +filters: { AND: [{ user_id: "alice" }, { categories: { contains: "finance" } }] } + +// Category filtering (exact match) +filters: { AND: [{ user_id: "alice" }, { categories: { in: ["personal_information"] } }] } +``` + +--- + +## get() / getAll() -- Retrieve Memories + +**Python:** +```python +# Single memory by ID +memory = client.get(memory_id="ea925981-...") + +# All memories for a user +memories = client.get_all(filters={"user_id": "alice"}) + +# With date range +memories = client.get_all( + filters={"AND": [ + {"user_id": "alex"}, + {"created_at": {"gte": "2024-07-01", "lte": "2024-07-31"}} + ]} +) +``` + +**TypeScript:** +```typescript +const memory = await client.get("ea925981-..."); +const memories = await client.getAll({ filters: { user_id: "alice" } }); +``` + +**Note:** `get_all` requires at least one of `user_id`, `agent_id`, `app_id`, or `run_id` in filters. + +--- + +## update() -- Modify Memories + +**Python:** +```python +client.update(memory_id="ea925981-...", text="Updated: vegan since 2024") +client.update(memory_id="ea925981-...", text="Updated", metadata={"verified": True}) +``` + +**TypeScript:** +```typescript +await client.update("ea925981-...", { text: "Updated: vegan since 2024" }); +``` + +--- + +## delete() / deleteAll() -- Remove Memories + +**Python:** +```python +client.delete(memory_id="ea925981-...") +client.delete_all(user_id="alice") # Irreversible bulk delete +``` + +**TypeScript:** +```typescript +await client.delete("ea925981-..."); +await client.deleteAll({ userId: "alice" }); +``` + +--- + +## history() -- Track Changes + +**Python:** +```python +history = client.history(memory_id="ea925981-...") +# Returns: [{previous_value, new_value, action, timestamps}] +``` + +**TypeScript:** +```typescript +const history = await client.history("ea925981-..."); +``` + +--- + +## Batch Operations (TypeScript) + +```typescript +// Batch update +await client.batchUpdate([ + { memoryId: "uuid-1", text: "Updated text" }, + { memoryId: "uuid-2", text: "Another updated text" }, +]); + +// Batch delete +await client.batchDelete(["uuid-1", "uuid-2", "uuid-3"]); +``` + +--- + +## Additional Methods + +```python +# List all users/agents/sessions with memories +users = client.users() + +# Delete a user/agent entity +client.delete_users(user_id="alice") + +# Submit feedback on a memory +client.feedback(memory_id="...", feedback="POSITIVE", feedback_reason="Accurate extraction") + +# Export memories +export = client.create_memory_export(filters={"AND": [{"user_id": "alice"}]}) +data = client.get_memory_export(memory_export_id=export["id"]) +``` + +--- + +## Common Pitfalls + +1. **Entity cross-filtering fails silently** -- `AND` with `user_id` + `agent_id` returns empty. Use `OR`. +2. **SQL operators rejected** -- use `gte`, `lt`, etc. Not `>=`, `<`. +3. **Metadata filtering is limited** -- only top-level keys with `eq`, `contains`, `ne`. +4. **Wildcard `*` excludes null** -- only matches non-null values. +5. **Default threshold is 0.1** -- increase for stricter matching. +6. **Async processing** -- memories process asynchronously. Wait 2-3s after `add()` before searching. + +## Naming Conventions + +Python uses `snake_case` everywhere (`user_id`, `memory_id`, `get_all`). TypeScript uses `camelCase` for methods (`getAll`, `deleteAll`, `batchUpdate`) and top-level parameters (`userId`, `topK`, `pageSize`), but filter keys use `snake_case` (`user_id`, `agent_id`). + +--- + +## v2 to v3 Migration + +### Breaking Changes in v3 + +**1. Entity IDs in search() and getAll()** + +v3 requires entity IDs (`user_id`, `agent_id`, `run_id`) inside `filters` instead of as top-level parameters: + +```python +# v2 (deprecated) +client.search("query", user_id="alice") +client.get_all(user_id="alice") + +# v3 +client.search("query", filters={"user_id": "alice"}) +client.get_all(filters={"user_id": "alice"}) +``` + +```typescript +// v2 (deprecated) +await client.search("query", { user_id: "alice" }); +await client.getAll({ user_id: "alice" }); + +// v3 +await client.search("query", { filters: { user_id: "alice" } }); +await client.getAll({ filters: { user_id: "alice" } }); +``` + +**2. TypeScript Parameter Naming** + +v3 TypeScript uses camelCase for all parameters: + +| v2 | v3 | +|----|-----| +| `user_id` | `userId` | +| `agent_id` | `agentId` | +| `run_id` | `runId` | +| `top_k` | `topK` | +| `page_size` | `pageSize` | + +**3. Default Values Changed** + +| Parameter | v2 Default | v3 Default | +|-----------|------------|------------| +| `threshold` | 0.3 | 0.1 | +| `rerank` | (not specified) | `false` | + +**4. Removed Parameters** + +The following parameters are no longer supported: + +| Parameter | Status | +|-----------|--------| +| `enable_graph` | Removed from add/search/getAll | +| `keyword_search` | Removed from search | +| `filter_memories` | Removed | +| `immutable` | Removed from add | +| `expiration_date` | Removed from add | +| `includes` | Removed from add | +| `excludes` | Removed from add | +| `async_mode` | Removed from add | diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/use-cases.md b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/use-cases.md new file mode 100644 index 0000000000..5f3ba655dc --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/references/use-cases.md @@ -0,0 +1,720 @@ +# Mem0 Use Cases & Examples + +Real-world implementation patterns for Mem0 Platform. Each use case includes complete, runnable code in both Python and TypeScript. + +## Table of Contents + +- [Personalized AI Companion](#1-personalized-ai-companion) +- [Customer Support with Categories](#2-customer-support-with-categories) +- [Healthcare Coach](#3-healthcare-coach) +- [Content Creation Workflow](#4-content-creation-workflow) +- [Multi-Agent / Multi-Tenant](#5-multi-agent--multi-tenant) +- [Personalized Search](#6-personalized-search) +- [Email Intelligence](#7-email-intelligence) +- [Common Patterns Across Use Cases](#common-patterns-across-use-cases) + +--- + +## 1. Personalized AI Companion + +A fitness coach that remembers goals, preferences, and progress across sessions. Mem0 persists context across app restarts — no session state needed. + +### Implementation (Python) + +```python +from mem0 import MemoryClient +from openai import OpenAI + +mem0 = MemoryClient() +openai_client = OpenAI() + +def chat(user_input: str, user_id: str) -> str: + # 1. Retrieve relevant memories + memories = mem0.search(user_input, user_id=user_id) + context = "\n".join([f"- {m['memory']}" for m in memories.get("results", [])]) + + # 2. Generate response with memory context + system_prompt = f"""You are Ray, a personal fitness coach. +Use these known facts about the user to personalize your response: +{context if context else 'No prior context yet.'}""" + + response = openai_client.chat.completions.create( + model="gpt-5-mini", + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_input}, + ] + ) + reply = response.choices[0].message.content + + # 3. Store interaction for future context + mem0.add( + [{"role": "user", "content": user_input}, {"role": "assistant", "content": reply}], + user_id=user_id + ) + return reply + +# Usage +chat("I want to run a marathon in under 4 hours", user_id="max") +# Next day, app restarted: +chat("What should I focus on today?", user_id="max") +# Ray remembers the sub-4 marathon goal +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; +import OpenAI from 'openai'; + +const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); +const openai = new OpenAI(); + +async function chat(userInput: string, userId: string): Promise { + // 1. Retrieve relevant memories + const memories = await mem0.search(userInput, { filters: { user_id: userId } }); + const context = memories.results + ?.map((m: any) => `- ${m.memory}`) + .join('\n') || 'No prior context yet.'; + + // 2. Generate response with memory context + const response = await openai.chat.completions.create({ + model: 'gpt-5-mini', + messages: [ + { role: 'system', content: `You are Ray, a personal fitness coach.\nUser context:\n${context}` }, + { role: 'user', content: userInput }, + ], + }); + const reply = response.choices[0].message.content!; + + // 3. Store interaction + await mem0.add( + [{ role: 'user', content: userInput }, { role: 'assistant', content: reply }], + { userId: userId } + ); + return reply; +} +``` + +### Key Benefits + +- Context persists across app restarts — no session management needed +- Memories are automatically deduplicated and updated +- Works with any LLM provider (OpenAI, Anthropic, etc.) + +**Best for:** Fitness coaches, tutors, therapists — any assistant that needs to remember goals across sessions. + +--- + +## 2. Customer Support with Categories + +Auto-categorize support data so teams retrieve the right facts fast. Uses custom categories for structured retrieval. + +### Implementation (Python) + +```python +from mem0 import MemoryClient + +client = MemoryClient() + +# 1. Define categories at the project level (one-time setup) +custom_categories = [ + {"support_tickets": "Customer issues and resolutions"}, + {"account_info": "Account details and preferences"}, + {"billing": "Payment history and billing questions"}, + {"product_feedback": "Feature requests and feedback"}, +] +client.project.update(custom_categories=custom_categories) + +# 2. Store interactions — auto-classified into categories +def log_support_interaction(user_id: str, message: str, priority: str = "normal"): + client.add( + [{"role": "user", "content": message}], + user_id=user_id, + metadata={"priority": priority, "source": "support_chat"} + ) + +# 3. Retrieve by category +def get_billing_issues(user_id: str): + return client.get_all( + filters={ + "AND": [ + {"user_id": user_id}, + {"categories": {"in": ["billing"]}} + ] + } + ) + +def search_support_history(user_id: str, query: str): + return client.search( + query, + filters={ + "AND": [ + {"user_id": user_id}, + {"categories": {"contains": "support_tickets"}} + ] + }, + top_k=5 + ) + +# Usage +log_support_interaction("maria", "I was charged twice for last month's subscription", priority="high") +log_support_interaction("maria", "The dashboard is loading slowly on mobile") +billing = get_billing_issues("maria") # Returns only billing-related memories +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; + +const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); + +// Setup categories (one-time) +await client.updateProject({ + custom_categories: [ + { support_tickets: 'Customer issues and resolutions' }, + { billing: 'Payment history and billing questions' }, + { product_feedback: 'Feature requests and feedback' }, + ], +}); + +async function logInteraction(userId: string, message: string, priority = 'normal') { + await client.add( + [{ role: 'user', content: message }], + { userId: userId, metadata: { priority, source: 'support_chat' } } + ); +} + +async function getBillingIssues(userId: string) { + return client.getAll({ + filters: { AND: [{ user_id: userId }, { categories: { in: ['billing'] } }] }, + }); +} +``` + +### Key Benefits + +- Automatic categorization — no manual tagging +- Filter by category for structured retrieval +- Metadata (`priority`, `source`) enables multi-dimensional queries + +**Best for:** Help desks, SaaS support, e-commerce — structured retrieval by category eliminates manual scanning. + +--- + +## 3. Healthcare Coach + +Guide patients with an assistant that remembers medical history. Uses high `threshold` for confident retrieval in safety-critical contexts. + +### Implementation (Python) + +```python +from mem0 import MemoryClient +from openai import OpenAI + +mem0 = MemoryClient() +openai_client = OpenAI() + +def save_patient_info(user_id: str, information: str): + mem0.add( + [{"role": "user", "content": information}], + user_id=user_id, + run_id="healthcare_session", + metadata={"type": "patient_information"} + ) + +def consult(user_id: str, question: str) -> str: + # High threshold for medical accuracy + memories = mem0.search(question, user_id=user_id, top_k=5, threshold=0.7) + context = "\n".join([f"- {m['memory']}" for m in memories.get("results", [])]) + + response = openai_client.chat.completions.create( + model="gpt-5-mini", + messages=[ + {"role": "system", "content": f"You are a health coach. Patient context:\n{context}"}, + {"role": "user", "content": question}, + ] + ) + reply = response.choices[0].message.content + + # Store the interaction + mem0.add( + [{"role": "user", "content": question}, {"role": "assistant", "content": reply}], + user_id=user_id, + run_id="healthcare_session", + ) + return reply + +# Usage +save_patient_info("alex", "I'm allergic to penicillin and take metformin for type 2 diabetes") +consult("alex", "Can I take amoxicillin for my sore throat?") +# Remembers penicillin allergy — amoxicillin is a penicillin-type antibiotic +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; +import OpenAI from 'openai'; + +const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); +const openai = new OpenAI(); + +async function savePatientInfo(userId: string, info: string) { + await mem0.add( + [{ role: 'user', content: info }], + { userId: userId, runId: 'healthcare_session', metadata: { type: 'patient_information' } } + ); +} + +async function consult(userId: string, question: string): Promise { + const memories = await mem0.search(question, { + filters: { user_id: userId }, + topK: 5, + threshold: 0.7, + }); + const context = memories.results?.map((m: any) => `- ${m.memory}`).join('\n') || ''; + + const response = await openai.chat.completions.create({ + model: 'gpt-5-mini', + messages: [ + { role: 'system', content: `You are a health coach. Patient context:\n${context}` }, + { role: 'user', content: question }, + ], + }); + const reply = response.choices[0].message.content!; + + await mem0.add( + [{ role: 'user', content: question }, { role: 'assistant', content: reply }], + { userId: userId, runId: 'healthcare_session' } + ); + return reply; +} +``` + +### Key Benefits + +- High threshold (0.7) ensures only confident matches for safety-critical retrieval +- Session scoping via `run_id` groups related health interactions +- Metadata tagging separates patient info from conversation history + +**Best for:** Telehealth, wellness apps, patient management — persistent health context across visits. + +--- + +## 4. Content Creation Workflow + +Store voice guidelines once and apply them across every draft. Uses `run_id` and `metadata` to scope writing preferences per session. + +### Implementation (Python) + +```python +from mem0 import MemoryClient +from openai import OpenAI + +mem0 = MemoryClient() +openai_client = OpenAI() + +def store_writing_preferences(user_id: str, preferences: str): + mem0.add( + [{"role": "user", "content": preferences}], + user_id=user_id, + run_id="editing_session", + metadata={"type": "preferences", "category": "writing_style"} + ) + +def draft_content(user_id: str, topic: str) -> str: + # Retrieve writing preferences + prefs = mem0.search( + "writing style preferences", + filters={"AND": [{"user_id": user_id}, {"run_id": "editing_session"}]} + ) + style_context = "\n".join([f"- {m['memory']}" for m in prefs.get("results", [])]) + + response = openai_client.chat.completions.create( + model="gpt-5-mini", + messages=[ + {"role": "system", "content": f"Write content matching these style preferences:\n{style_context}"}, + {"role": "user", "content": f"Write a blog post about: {topic}"}, + ] + ) + return response.choices[0].message.content + +# Usage +store_writing_preferences("writer_01", "I prefer short sentences. Active voice. No jargon. Use analogies.") +draft_content("writer_01", "Why AI memory matters for chatbots") +# Drafts content matching the stored voice guidelines +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; +import OpenAI from 'openai'; + +const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); +const openai = new OpenAI(); + +async function storePreferences(userId: string, preferences: string) { + await mem0.add( + [{ role: 'user', content: preferences }], + { userId: userId, runId: 'editing_session', metadata: { type: 'preferences' } } + ); +} + +async function draftContent(userId: string, topic: string): Promise { + const prefs = await mem0.search('writing style preferences', { + filters: { AND: [{ user_id: userId }, { run_id: 'editing_session' }] }, + }); + const styleContext = prefs.results?.map((m: any) => `- ${m.memory}`).join('\n') || ''; + + const response = await openai.chat.completions.create({ + model: 'gpt-5-mini', + messages: [ + { role: 'system', content: `Write content matching these preferences:\n${styleContext}` }, + { role: 'user', content: `Write a blog post about: ${topic}` }, + ], + }); + return response.choices[0].message.content!; +} +``` + +### Key Benefits + +- Voice consistency across all content without repeating guidelines +- Scoped sessions let you maintain different style profiles +- Preferences update automatically as you refine them + +**Best for:** Marketing teams, technical writers, agencies — consistent voice across all content. + +--- + +## 5. Multi-Agent / Multi-Tenant + +Keep memories separate using `user_id`, `agent_id`, `app_id`, and `run_id` scoping. Critical for multi-agent workflows and multi-tenant apps. + +### Implementation (Python) + +```python +from mem0 import MemoryClient + +client = MemoryClient() + +# Store memories scoped to user + agent + session +def store_scoped_memory(messages: list, user_id: str, agent_id: str, run_id: str, app_id: str): + client.add( + messages, + user_id=user_id, + agent_id=agent_id, + run_id=run_id, + app_id=app_id + ) + +# Query within a specific scope +def search_user_session(query: str, user_id: str, app_id: str, run_id: str): + """Search memories for a specific user within a specific session.""" + return client.search( + query, + filters={ + "AND": [ + {"user_id": user_id}, + {"app_id": app_id}, + {"run_id": run_id} + ] + } + ) + +def search_agent_knowledge(query: str, agent_id: str, app_id: str): + """Search all memories an agent has across all users.""" + return client.search( + query, + filters={ + "AND": [ + {"agent_id": agent_id}, + {"app_id": app_id} + ] + } + ) + +# Usage: Travel concierge app with multiple agents +store_scoped_memory( + [{"role": "user", "content": "I'm vegetarian and prefer window seats"}], + user_id="traveler_cam", + agent_id="travel_planner", + run_id="tokyo-2025", + app_id="concierge_app" +) + +# User-scoped query: "What does Cam prefer?" +user_mems = search_user_session("dietary restrictions?", "traveler_cam", "concierge_app", "tokyo-2025") + +# Agent-scoped query: "What do all travelers prefer?" (across users) +agent_mems = search_agent_knowledge("common dietary restrictions?", "travel_planner", "concierge_app") +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; + +const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); + +async function storeScopedMemory( + messages: Array<{ role: string; content: string }>, + userId: string, agentId: string, runId: string, appId: string +) { + await client.add(messages, { + userId: userId, + agentId: agentId, + runId: runId, + appId: appId, + }); +} + +async function searchUserSession(query: string, userId: string, appId: string, runId: string) { + return client.search(query, { + filters: { AND: [{ user_id: userId }, { app_id: appId }, { run_id: runId }] }, + }); +} + +async function searchAgentKnowledge(query: string, agentId: string, appId: string) { + return client.search(query, { + filters: { AND: [{ agent_id: agentId }, { app_id: appId }] }, + }); +} +``` + +### Key Benefits + +- Full isolation between users, agents, sessions, and apps +- Query at any scope level — user, agent, session, or app-wide +- No memory leakage between tenants + +**Best for:** Multi-agent workflows, multi-tenant SaaS — proper isolation at every level. + +--- + +## 6. Personalized Search + +Blend real-time search results with personal context. Uses `custom_instructions` to infer preferences from queries. + +### Implementation (Python) + +```python +from mem0 import MemoryClient +from openai import OpenAI + +mem0 = MemoryClient() +openai_client = OpenAI() + +# One-time setup: configure Mem0 to infer from queries +mem0.project.update( + custom_instructions="""Infer user preferences and facts from their search queries. +Extract dietary preferences, location, interests, and purchase history.""" +) + +def personalized_search(user_id: str, query: str, search_results: list) -> str: + # Get user context from memory + memories = mem0.search(query, user_id=user_id, top_k=5) + user_context = "\n".join([f"- {m['memory']}" for m in memories.get("results", [])]) + + response = openai_client.chat.completions.create( + model="gpt-5-mini", + messages=[ + {"role": "system", "content": f"Personalize search results using user context:\n{user_context}"}, + {"role": "user", "content": f"Query: {query}\n\nSearch results:\n{search_results}"}, + ] + ) + reply = response.choices[0].message.content + + # Store the query to learn preferences over time + mem0.add( + [{"role": "user", "content": query}], + user_id=user_id + ) + return reply + +# Usage +personalized_search("user_42", "best restaurants nearby", ["Restaurant A", "Restaurant B"]) +# Over time, Mem0 learns: "user prefers vegetarian, lives in Austin" +# Future searches are automatically personalized +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; +import OpenAI from 'openai'; + +const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); +const openai = new OpenAI(); + +async function personalizedSearch(userId: string, query: string, searchResults: string[]): Promise { + const memories = await mem0.search(query, { filters: { user_id: userId }, topK: 5 }); + const context = memories.results?.map((m: any) => `- ${m.memory}`).join('\n') || ''; + + const response = await openai.chat.completions.create({ + model: 'gpt-5-mini', + messages: [ + { role: 'system', content: `Personalize results using user context:\n${context}` }, + { role: 'user', content: `Query: ${query}\nResults: ${searchResults.join(', ')}` }, + ], + }); + const reply = response.choices[0].message.content!; + + await mem0.add([{ role: 'user', content: query }], { userId: userId }); + return reply; +} +``` + +### Key Benefits + +- Learns preferences from queries automatically via `custom_instructions` +- Personalizes any search provider (Tavily, Google, Bing) +- Zero manual preference setup — improves over time + +**Best for:** Personalized search engines, recommendation systems — search results tailored to individual users. + +--- + +## 7. Email Intelligence + +Capture, categorize, and recall inbox threads using persistent memories with rich metadata. + +### Implementation (Python) + +```python +from mem0 import MemoryClient + +client = MemoryClient() + +def store_email(user_id: str, sender: str, subject: str, body: str, date: str): + client.add( + [{"role": "user", "content": f"Email from {sender}: {subject}\n\n{body}"}], + user_id=user_id, + metadata={"email_type": "incoming", "sender": sender, "subject": subject, "date": date} + ) + +def search_emails(user_id: str, query: str): + return client.search( + query, + filters={"AND": [{"user_id": user_id}, {"categories": {"contains": "email"}}]}, + top_k=10 + ) + +def get_emails_from_sender(user_id: str, sender: str): + return client.get_all( + filters={ + "AND": [ + {"user_id": user_id}, + {"metadata": {"contains": sender}} + ] + } + ) + +# Usage +store_email("alice", "bob@acme.com", "Q3 Budget Review", "Attached is the Q3 budget...", "2025-01-15") +store_email("alice", "carol@acme.com", "Sprint Planning", "Here are the priorities...", "2025-01-16") + +results = search_emails("alice", "budget discussions") +sender_emails = get_emails_from_sender("alice", "bob@acme.com") +``` + +### Implementation (TypeScript) + +```typescript +import MemoryClient from 'mem0ai'; + +const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }); + +async function storeEmail(userId: string, sender: string, subject: string, body: string, date: string) { + await client.add( + [{ role: 'user', content: `Email from ${sender}: ${subject}\n\n${body}` }], + { userId: userId, metadata: { email_type: 'incoming', sender, subject, date } } + ); +} + +async function searchEmails(userId: string, query: string) { + return client.search(query, { + filters: { AND: [{ user_id: userId }, { categories: { contains: 'email' } }] }, + topK: 10, + }); +} +``` + +### Key Benefits + +- Rich metadata enables multi-dimensional queries (sender, date, subject) +- Category filtering separates emails from other memory types +- Semantic search across all email content + +**Best for:** Inbox management, email automation — searchable email memories with metadata filtering. + +--- + +## Common Patterns Across Use Cases + +### Pattern 1: Retrieve → Generate → Store + +Every use case follows the same 3-step loop: + +```python +# 1. Retrieve relevant context +memories = mem0.search(user_input, user_id=user_id) +context = "\n".join([m["memory"] for m in memories.get("results", [])]) + +# 2. Generate with context +response = llm.generate(system_prompt=f"Context:\n{context}", user_input=user_input) + +# 3. Store the interaction +mem0.add( + [{"role": "user", "content": user_input}, {"role": "assistant", "content": response}], + user_id=user_id +) +``` + +### Pattern 2: Scope with Entity Identifiers + +Use `user_id`, `agent_id`, `app_id`, and `run_id` to isolate memories: + +```python +# User-level: personal preferences +client.add(messages, user_id="alice") + +# Session-level: conversation within one session +client.add(messages, user_id="alice", run_id="session_123") + +# Agent-level: agent-specific knowledge +client.add(messages, agent_id="support_bot", app_id="helpdesk") +``` + +### Pattern 3: Rich Metadata for Filtering + +Attach structured metadata for multi-dimensional queries: + +```python +# Store with metadata +client.add(messages, user_id="alice", metadata={"priority": "high", "source": "phone_call"}) + +# Filter by category + metadata +client.search("billing issues", filters={ + "AND": [{"user_id": "alice"}, {"categories": {"contains": "billing"}}] +}) +``` + +### Pattern 4: Custom Instructions for Domain-Specific Extraction + +Control what Mem0 extracts from conversations: + +```python +client.project.update( + custom_instructions="Extract medical conditions, medications, and allergies. Exclude billing info." +) +``` + +--- + +## More Examples + +For 30+ cookbooks with complete working code: [docs.mem0.ai/cookbooks](https://docs.mem0.ai/cookbooks) diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/mem0/scripts/mem0_doc_search.py b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/scripts/mem0_doc_search.py new file mode 100755 index 0000000000..1ff8de1c78 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/mem0/scripts/mem0_doc_search.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +""" +Mem0 Documentation Search Agent (Mintlify-based) +On-demand search tool for querying Mem0 documentation without storing content locally. + +This tool leverages Mintlify's documentation structure to perform just-in-time +retrieval of technical information from docs.mem0.ai. + +Usage: + python mem0_doc_search.py --query "how to add graph memory" + python mem0_doc_search.py --query "filter syntax for categories" + python mem0_doc_search.py --page "/platform/features/graph-memory" + python mem0_doc_search.py --index + python mem0_doc_search.py --query "webhook events" --section platform + +Purpose: + - Avoid bloating local context with full documentation + - Enable just-in-time retrieval of technical details + - Query specific documentation pages on demand + - Search across the full Mem0 documentation site +""" + +import argparse +import json +import sys +import urllib.error +import urllib.parse +import urllib.request + +DOCS_BASE = "https://docs.mem0.ai" +SEARCH_ENDPOINT = f"{DOCS_BASE}/api/search" +LLMS_INDEX = f"{DOCS_BASE}/llms.txt" + +# Known documentation sections for targeted retrieval +SECTION_MAP = { + "platform": [ + "/platform/overview", + "/platform/quickstart", + "/platform/features", + "/platform/features/graph-memory", + "/platform/features/selective-memory", + "/platform/features/custom-categories", + "/platform/features/v2-memory-filters", + "/platform/features/async-client", + "/platform/features/webhooks", + "/platform/features/multimodal-support", + ], + "api": [ + "/api-reference/memory/add-memories", + "/api-reference/memory/v2-search-memories", + "/api-reference/memory/v2-get-memories", + "/api-reference/memory/get-memory", + "/api-reference/memory/update-memory", + "/api-reference/memory/delete-memory", + ], + "open-source": [ + "/open-source/overview", + "/open-source/python-quickstart", + "/open-source/node-quickstart", + "/open-source/features", + "/open-source/features/graph-memory", + "/open-source/features/rest-api", + "/open-source/configure-components", + ], + "openmemory": [ + "/openmemory/overview", + "/openmemory/quickstart", + ], + "sdks": [ + "/sdks/python", + "/sdks/js", + ], + "integrations": [ + "/integrations", + ], +} + + +def fetch_url(url: str) -> str: + """Fetch content from a URL.""" + req = urllib.request.Request(url, headers={"User-Agent": "Mem0DocSearchAgent/1.0"}) + try: + with urllib.request.urlopen(req, timeout=15) as resp: + return resp.read().decode("utf-8") + except urllib.error.HTTPError as e: + return f"HTTP Error {e.code}: {e.reason}" + except urllib.error.URLError as e: + return f"URL Error: {e.reason}" + + +def search_docs(query: str, section: str | None = None) -> dict: + """ + Search Mem0 documentation using Mintlify's search API. + Falls back to the llms.txt index for keyword matching if the API is unavailable. + """ + # Try Mintlify search API first + params = urllib.parse.urlencode({"query": query}) + search_url = f"{SEARCH_ENDPOINT}?{params}" + + try: + result = fetch_url(search_url) + data = json.loads(result) + if isinstance(data, dict) and data.get("results"): + results = data["results"] + if section and section in SECTION_MAP: + section_paths = SECTION_MAP[section] + results = [r for r in results if any(r.get("url", "").startswith(p) for p in section_paths)] + return {"source": "mintlify_search", "results": results} + except (json.JSONDecodeError, Exception): + pass + + # Fallback: search llms.txt index for matching URLs + index_content = fetch_url(LLMS_INDEX) + query_lower = query.lower() + matching_urls = [] + + for line in index_content.splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + if query_lower in line.lower(): + matching_urls.append(line) + + if section and section in SECTION_MAP: + section_paths = SECTION_MAP[section] + matching_urls = [u for u in matching_urls if any(p in u for p in section_paths)] + + return { + "source": "llms_txt_index", + "query": query, + "matching_urls": matching_urls[:20], + "suggestion": "Fetch specific URLs for detailed content", + } + + +def fetch_page(page_path: str) -> dict: + """Fetch a specific documentation page.""" + url = f"{DOCS_BASE}{page_path}" if page_path.startswith("/") else page_path + content = fetch_url(url) + return {"url": url, "content": content[:10000], "truncated": len(content) > 10000} + + +def get_index() -> dict: + """Fetch the full documentation index from llms.txt.""" + content = fetch_url(LLMS_INDEX) + urls = [line.strip() for line in content.splitlines() if line.strip() and not line.startswith("#")] + return {"total_pages": len(urls), "urls": urls, "sections": list(SECTION_MAP.keys())} + + +def list_section(section: str) -> dict: + """List all known pages in a documentation section.""" + if section not in SECTION_MAP: + return {"error": f"Unknown section: {section}", "available": list(SECTION_MAP.keys())} + return { + "section": section, + "pages": [f"{DOCS_BASE}{p}" for p in SECTION_MAP[section]], + } + + +def main(): + parser = argparse.ArgumentParser(description="Search Mem0 documentation on demand") + parser.add_argument("--query", help="Search query for documentation") + parser.add_argument("--page", help="Fetch a specific page path (e.g., /platform/features/graph-memory)") + parser.add_argument("--index", action="store_true", help="Show full documentation index") + parser.add_argument("--section", help="Filter by section or list section pages") + parser.add_argument("--json", action="store_true", help="Output as JSON") + + args = parser.parse_args() + + if args.index: + result = get_index() + elif args.section and not args.query: + result = list_section(args.section) + elif args.page: + result = fetch_page(args.page) + elif args.query: + result = search_docs(args.query, section=args.section) + else: + parser.print_help() + sys.exit(1) + + if args.json: + print(json.dumps(result, indent=2)) + else: + if isinstance(result, dict): + if "results" in result: + print(f"Source: {result.get('source', 'unknown')}") + for r in result["results"]: + print(f" - {r.get('title', 'N/A')}: {r.get('url', 'N/A')}") + if r.get("description"): + print(f" {r['description'][:200]}") + elif "matching_urls" in result: + print(f"Source: {result['source']}") + print(f"Query: {result['query']}") + for url in result["matching_urls"]: + print(f" - {url}") + if result.get("suggestion"): + print(f"\n{result['suggestion']}") + elif "urls" in result: + print(f"Total documentation pages: {result['total_pages']}") + print(f"Sections: {', '.join(result['sections'])}") + for url in result["urls"][:30]: + print(f" - {url}") + if result["total_pages"] > 30: + print(f" ... and {result['total_pages'] - 30} more") + elif "pages" in result: + print(f"Section: {result['section']}") + for page in result["pages"]: + print(f" - {page}") + elif "content" in result: + print(f"URL: {result['url']}") + if result.get("truncated"): + print("[Content truncated to 10000 chars]") + print(result["content"]) + elif "error" in result: + print(f"Error: {result['error']}") + if result.get("available"): + print(f"Available sections: {', '.join(result['available'])}") + else: + print(json.dumps(result, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/memory-reviewer/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/memory-reviewer/SKILL.md new file mode 100644 index 0000000000..fb2eb55b2a --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/memory-reviewer/SKILL.md @@ -0,0 +1,63 @@ +--- +name: memory-reviewer +description: Reviews stored memory quality by detecting duplicates, contradictions, and stale entries with actionable recommendations. Use when search results seem conflicting, before running dream consolidation, or for periodic memory hygiene audits. +--- + +# Memory Reviewer + +Audits memory quality for the active project. Finds duplicates, contradictions, and low-confidence entries. + +## When to use + +- User asks "check my memories", "memory quality", "any duplicates?" +- User runs `/mem0:memory-reviewer` directly +- After a session with 5+ memory writes (suggest proactively) +- After `/mem0:health --deep` identifies issues + +## Steps + +1. **Fetch all memories** for active project via `get_memories` with `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=200`. Paginate if needed — cap at 200 memories. + +2. **Group by `metadata.type`**. Common types: `decision`, `convention`, `anti_pattern`, `task_learning`, `project_profile`, `user_preference`, `session_state`. + +3. **Scan each group for issues:** + + | Issue | Detection method | + |---|---| + | **Near-duplicates** | >60% noun overlap within same type. Compare memory text after stripping stop words. | + | **Contradictions** | Opposing facts about same topic (e.g., "use PostgreSQL" vs "use MySQL" for same component) | + | **Low-confidence** | `metadata.confidence < 0.3` | + | **Missing type** | No `metadata.type` set | + | **Stale** | `created_at` older than 180 days with no updates | + +4. **Output compact summary:** + +``` +memory-reviewer: project= total= + duplicates: found + contradictions: found + low_confidence: found + untagged: found + stale: found +``` + +5. **If issues found**, list them with memory IDs: + +``` +Issues: + [duplicate] "" ≈ "" [mem0:, mem0:] + [contradiction] "" vs "" [mem0:, mem0:] + [low_conf] "" (confidence: 0.1) [mem0:] +``` + +6. **Suggest action**: "Run `/mem0:dream` to consolidate duplicates and resolve contradictions." + +## Constraints + +- **Read-only** — never modify or delete memories (that's `/mem0:dream`'s job) +- **Max 200 memories** per scan +- Report findings, let user decide on action + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/onboard/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/onboard/SKILL.md new file mode 100644 index 0000000000..9cb8c484bc --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/onboard/SKILL.md @@ -0,0 +1,201 @@ +--- +name: onboard +description: Sets up mem0 for a new project including API key configuration, MCP authentication, project file import, and coding categories. Use on first run in a new project, when API key needs updating, or to re-run initial setup after configuration changes. +--- + +# Mem0 Onboarding Wizard + +Run this wizard to set up the mem0 plugin for the current project. Complete in ~60 seconds. + +**IMPORTANT: Execute steps strictly in order (0 → 1 → 2 → 3 → 4 → 5 → 6). Each step depends on the previous one. Do NOT run steps in parallel or skip ahead. Complete one step fully before starting the next.** + +## Step 0: Skip dependency install + +The plugin lists `mem0ai` as a declared dependency — no install step is needed. Proceed directly to Step 1. + +## Step 1: Set up API key + +Check if the API key is available: + +```bash +[ -n "${MEM0_API_KEY:-}" ] && echo "SET" || echo "NOT_SET" +``` + +IMPORTANT: Never run `echo $MEM0_API_KEY` — that prints the secret in plaintext to the conversation log. + +### If API key IS set (output is "SET") + +Print: `- API key found.` and proceed to Step 2. + +### If API key is NOT set (output is "NOT_SET") + +Guide the user through API key setup. Show this message: + +``` +Step 1: Setting up API key. + +- API key not found. Let's set it up. + + 1. Get your API key from https://app.mem0.ai/dashboard/api-keys + + 2. Choose ONE method: + + Option A — Shell profile: + echo 'export MEM0_API_KEY="m0-your-key-here"' >> ~/.zshrc + source ~/.zshrc + + Option B — OpenCode environment config: + Add MEM0_API_KEY to your OpenCode environment settings + so it is available in all sessions. + + 3. Verify: + [ -n "${MEM0_API_KEY:-}" ] && echo "SET" || echo "NOT_SET" +``` + +After the user confirms, re-run the verify command. If NOT_SET, repeat. If SET, proceed to Step 2. + +## Step 2: MCP server connection + +First, check if MCP tools are already available using ToolSearch with query `"mem0 search_memories"`. The exact tool name varies by install method (may be `mcp__mem0__search_memories` or `mcp__plugin_mem0_mem0__search_memories`). + +**If MCP tools ARE found:** Print `- MCP already connected.` and proceed to Step 3. + +**If MCP tools are NOT found:** + +The MCP server authenticates using the `MEM0_API_KEY` set in Step 1. No OAuth or browser login is needed. + +1. Verify the API key is set (re-run the Step 1 check) +2. Check the plugin is installed and the MCP server for mem0 is listed in OpenCode's MCP configuration +3. If the server shows an error, ask the user to restart OpenCode and run `/mem0:onboard` again +4. If all checks pass but tools are still missing: "Restart OpenCode and run `/mem0:onboard` again." + +**STOP here** — do not proceed without MCP tools. + +## Step 3: Verify connectivity and show identity + +Call `search_memories` with `query="project setup"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=1` to verify connectivity. + +Print: +``` +- Connected + user: + project: + branch: +``` + +If the search fails, troubleshoot the API key and MCP connection. + +## Step 4: Import project files + +Check for common project context files in the working directory root. Read any that exist and store their key facts as memories using `add_memory`. + +### 4a: Detect project files + +```bash +for f in CLAUDE.md AGENTS.md .cursorrules .windsurfrules mem0.md .mem0.md; do + [ -f "$f" ] && echo "FOUND: $f ($(wc -c < "$f") bytes)" +done || true +``` + +If no files found, print `- No project files found. Skipping import.` and proceed to Step 5. + +### 4b: Read and import via MCP + +For each file found in 4a: + +1. Read the file contents with the Read tool. +2. Extract the key facts, conventions, and decisions documented in the file. Do not import the entire file verbatim — summarize meaningful chunks. +3. Call `add_memory` for each meaningful chunk with: + - `data`: the extracted fact or convention + - `user_id`: the active user id + - `app_id`: the active project id + - `metadata`: `{"source": "", "type": "project_context"}` + +If a file is very large (over 4000 bytes), split it into logical sections (one `add_memory` call per section). + +### 4c: Report to user + +After processing all files, print a user-friendly summary: + +``` +- Importing project files into mem0... done. + file(s) read, memories stored. + These are available as context for future sessions. +``` + +If `add_memory` calls fail, print: +``` +- Project file import failed. Check API key and MCP connection, then retry with: /mem0:onboard +``` + +## Step 5: Set up coding categories + +Ask: "Install coding categories optimized for development workflows? [Y/n]" + +If the user says no or skips, print `- Coding categories skipped.` and proceed to Step 6. + +If yes, store a project profile memory that records the project and its active coding categories. Call `add_memory` once with: + +- `data`: a plain-text description of the project profile and the list of active coding categories (see below) +- `user_id`: the active user id +- `app_id`: the active project id +- `metadata`: `{"type": "project_profile", "source": "onboard"}` + +The standard set of 17 coding categories to include in the `data` field: + +``` +architecture_decisions - High-level design choices and system structure +api_design - Interface contracts, REST/GraphQL/RPC conventions +data_models - Schemas, entity definitions, relationships +algorithms - Non-trivial logic, performance-sensitive routines +dependencies - Libraries, versions, upgrade notes +environment_setup - Dev environment, tooling, build system +testing_strategy - Test patterns, coverage targets, mocking approach +debugging_notes - Known issues, workarounds, gotchas +performance - Bottlenecks, profiling results, optimizations +security - Auth patterns, secret handling, threat notes +deployment - CI/CD pipelines, infra config, release process +code_conventions - Naming, formatting, style rules beyond the linter +error_handling - Error taxonomy, recovery patterns, logging approach +refactoring_history - Past rewrites, why changes were made +integrations - Third-party services, webhooks, external APIs +onboarding - New-contributor notes, repo orientation +project_meta - Goals, non-goals, stakeholder context +``` + +Example `data` value: + +``` +Project profile for . +Active coding categories: architecture_decisions, api_design, data_models, +algorithms, dependencies, environment_setup, testing_strategy, debugging_notes, +performance, security, deployment, code_conventions, error_handling, +refactoring_history, integrations, onboarding, project_meta. +``` + +After the `add_memory` call succeeds, print: +``` +- Coding categories installed (17 categories). +``` + +If `add_memory` fails, print the error and suggest re-running `/mem0:onboard`. + +## Step 6: Summary + +Print a summary: +``` +- Onboarding complete. + user_id: + project_id: (app_id) + files: found, memories stored + categories: + +Memory is now active for this project. Start working — mem0 will +automatically search relevant context and capture learnings. + +Run /mem0:tour to see what mem0 already knows about this project. +``` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/peek/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/peek/SKILL.md new file mode 100644 index 0000000000..949e0f73f5 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/peek/SKILL.md @@ -0,0 +1,56 @@ +--- +name: peek +description: Searches memories and displays compact one-liner results, or looks up a specific memory by ID. Use for quick memory lookups, checking if a decision was recorded, resolving [mem0:id] citations, or browsing memories without full category detail. +--- + +# Mem0 Peek + +Quick search with compact output. Lighter than `/mem0:tour`. + +## Execution + +### Step 1: Parse query + +The user provides a search query: `/mem0:peek auth middleware` + +If no query provided, ask: "What should I search for?" + +**Memory ID detection:** If the query matches any of these patterns, treat it as a +direct memory ID lookup instead of a search: +- Bare hex: `^[a-f0-9]{8}$` (short ID) or `^[a-f0-9]{8}-[a-f0-9-]+$` (full UUID) +- Citation ref: `[mem0:]` — extract the hex portion + +When an ID is detected: +1. Call `get_memory()` directly (if short ID, try as prefix of full UUID) +2. If found, skip to Step 3 and display the single result +3. If not found, fall through to search using the ID as query text + +### Step 2: Search + +Run 2 parallel `search_memories` calls: + +1. Broad: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` +2. Targeted: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}`, `top_k=5`, `rerank=true` + +### Step 3: Display + +Deduplicate by ID, then show compact results: + +``` +## mem0 peek: "" ( results) + +1. [decision] Auth module uses JWT with RS256 keys (2025-05-15) [mem0:a3f8b2c1] +2. [anti_pattern] Don't use symmetric HS256 — leaked in env (2025-05-10) [mem0:7e2d9f4a] +3. [convention] All middleware in src/middleware/ (2025-05-08) [mem0:c4d5e6f7] +``` + +Format: `. [] () [mem0:]` + +If no results: +``` +No memories matching "" for project . +``` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/pin/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/pin/SKILL.md new file mode 100644 index 0000000000..463521ef7b --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/pin/SKILL.md @@ -0,0 +1,71 @@ +--- +name: pin +description: Pins or unpins a memory to protect it from pruning during dream consolidation. Use when a memory is critical and must never be removed, such as architecture decisions, security constraints, or immutable team conventions. +--- + +# Mem0 Pin + +Pin a memory to mark it as high-priority and protect from pruning. + +## Execution + +### Step 1: Find the memory + +The user provides either a search query or memory ID. + +**If memory ID:** +- Call `get_memory` with the ID. + +**If search query:** +- Call `search_memories` with the query, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=5`. +- Show numbered list with content previews. +- Ask: "Which memory to pin? Enter a number." + +### Step 2: Read current content + +Call `get_memory` with the selected memory ID. Store: +- `original_text` — the memory's text content +- `original_metadata` — the existing `metadata` dict + +### Step 3: Pin it + +The MCP `update_memory` tool only accepts `memory_id`, `text`, and `source` — it +does not accept a `metadata` parameter. To pin, append a pin marker to the text: + +```python +pinned_text = "[PINNED] " + original_text if not original_text.startswith("[PINNED]") else original_text +update_memory(memory_id=, text=pinned_text) +``` + +**For new memories** (user wants to pin text that isn't stored yet): +1. Call `add_memory` with: + - `text="[PINNED] "` + - `user_id=` + - `app_id=` + - `metadata={"pinned": true, "type": "decision", "confidence": 1.0}` + - `infer=False` +2. The response contains `event_id`. Call `get_event_status(event_id=)` once to retrieve the memory ID, then confirm. + +### Step 4: Confirm + +``` +Pinned: "" +Memory ID: +``` + +Append `...` only if content exceeds 80 characters. + +### Unpin + +If the user says "unpin": +1. Call `get_memory` to read current content. +2. Remove the pin marker from the text: + ```python + unpinned_text = original_text.removeprefix("[PINNED] ") + update_memory(memory_id=, text=unpinned_text) + ``` +3. Print: `Unpinned: "..."` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/remember/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/remember/SKILL.md new file mode 100644 index 0000000000..54157bd916 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/remember/SKILL.md @@ -0,0 +1,61 @@ +--- +name: remember +description: Stores a memory verbatim from user input with appropriate type classification and metadata. Use when the user says remember this, save this, store this, note that, or explicitly asks to record a decision, preference, convention, or learning. +--- + +# Mem0 Remember + +Store a fact or learning directly into mem0. + +## Execution + +### Step 1: Extract the content + +The user provides the content as an argument: `/mem0:remember ` + +If no text was provided, ask: "What should I remember?" + +### Step 2: Classify the memory + +Based on the content, pick the best `metadata.type`: + +| Content signal | Type | +|---|---| +| "we decided...", "always use...", "never..." | `decision` | +| "X doesn't work because...", "don't try..." | `anti_pattern` | +| "I prefer...", "use X instead of Y" | `user_preference` | +| "the convention is...", "we always..." | `convention` | +| "learned that...", "figured out..." | `task_learning` | +| setup, env, tooling, config | `environmental` | +| anything else | `task_learning` | + +### Step 3: Store + +Call `add_memory` with: +- `text=""` +- `user_id=` +- `app_id=` +- `metadata={"type": "", "branch": "", "confidence": 1.0, "source": "remember_command"}` +- `infer=False` + +`infer=False` because the user stated the fact explicitly — no extraction needed. +`confidence=1.0` because the user explicitly asked to store this. + +### Step 4: Confirm + +The `add_memory` response returns `event_id` (not `memory_id`) because writes are async. +Call `get_event_status(event_id=)` once. + +- If status is `SUCCEEDED`: print the memory ID from the result. +- If status is `PENDING` or `processing`: print with the event ID as fallback. + +``` +Remembered as : "" +Memory ID: +``` + +Append `...` only if content was truncated (longer than 80 chars). + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/stats/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/stats/SKILL.md new file mode 100644 index 0000000000..b58f04fa06 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/stats/SKILL.md @@ -0,0 +1,135 @@ +--- +name: stats +description: Displays memory usage statistics for the current session and project including counts by category, age distribution, and API latency. Use when checking how many memories exist, reviewing session activity, or auditing memory distribution across categories. +--- + +# Mem0 Stats + +Show session and lifetime memory statistics. + +## Execution + +### Step 1: Gather session context + +Read the session identity from env vars set by the plugin's shell.env hook: + +- `MEM0_USER_ID` (falls back to `$USER` if unset) +- `MEM0_APP_ID` — the active project identifier +- `MEM0_SESSION_ID` — current session identifier +- `MEM0_BRANCH` — current git branch + +If `MEM0_USER_ID` is unset, use `$USER`. If `MEM0_APP_ID` is unset, note "No project configured" and stop. + +### Step 2: Fetch total memory count + +Call `get_memories` MCP tool to get the total count for this project: + +`filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=1` + +Read the `count` field from the response — this is the total number of memories for the project regardless of page size. + +### Step 3: Fetch memories for category breakdown + +Call `get_memories` MCP tool to retrieve memories for grouping: + +`filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=200` + +Group each memory by: +1. `categories[0]` (platform-assigned) — primary grouping +2. `metadata.type` (agent-assigned) — secondary if `categories` is empty or absent +3. `created_at` date — for age analysis + +**Category normalization:** Merge `auto_capture` and `uncategorized` into a single `uncategorized` row. Do NOT show `auto_capture` as its own row. + +Also run a `search_memories` MCP tool call with `query="project"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=1` to measure round-trip latency. Note the time before and after the MCP call — do NOT attempt raw HTTP calls to the API. + +### Step 4: Display + +Print a minimal plain-text dashboard. No markdown formatting — OpenCode TUI renders text verbatim. + +Example output shape: + +``` +mem0 stats + +Session () branch: main + +Project: my-project — 55 memories — API: 84ms + +Category Count +-------------------- ----- +decision 24 +convention 15 +anti_pattern 6 +task_learning 5 +user_preference 3 +session_state 2 + +Age — oldest: 2026-02-15 newest: 2026-05-23 + < 7 days: 5 | 7-30d: 12 | 30-90d: 10 | > 90d: 8 + +Identity — user: kartik project: my-project branch: main +``` + +**Display rules:** +- Use plain text with spaces to align columns — no markdown tables, no | pipes, no ** bold, no ## headers +- Category section: sort by count descending, omit categories with 0 memories +- Age: single line with pipe-separated buckets, computed from `created_at` +- Session line: show MEM0_SESSION_ID (first 12 chars) and MEM0_BRANCH if available; skip the line entirely if both are unset +- If only 1-2 total memories, skip the category table — just show the count +- Keep everything compact — no decorative borders or filler + +## Weekly digest mode + +When invoked with `--weekly` (e.g., `/mem0:stats --weekly`), append a weekly +activity digest after the standard stats dashboard. + +### W1: Fetch recent memories + +Call `search_memories` in parallel with time-scoped queries: +1. `query="decisions made this week"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"created_at": {"gte": "<7 days ago YYYY-MM-DD>"}}]}`, `top_k=20` +2. `query="bugs errors fixes"`, same time filter, `top_k=20` +3. `query="patterns conventions learnings"`, same time filter, `top_k=20` + +### W2: Analyze + +Merge by ID. Group into "New this week" by `categories[0]` or `metadata.type`. +Calculate: memories added last 7 days, most active categories, most active day. + +### W3: Display + +Append after the standard stats in plain text (no markdown): + +``` +This week (May 16 - May 23) + ++12 memories — most active: Wednesday (5) + +Category New +-------------- --- +decision 5 +task_learning 4 +bug_fix 3 + +Highlights +- <2-3 sentence summary of most important decisions/learnings this week> +``` + +### W4: Write digest file + +Write to `~/.mem0/weekly-digest.txt` (overwrite). Append one line to +`~/.mem0/digest-history.log`: +``` + | | + memories | top: +``` + +### W5: Empty state + +If no new memories in 7 days, output: +``` +No new memories in the past week. Total: memories in . +``` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/switch-project/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/switch-project/SKILL.md new file mode 100644 index 0000000000..6ac75a3775 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/switch-project/SKILL.md @@ -0,0 +1,50 @@ +--- +name: switch-project +description: Overrides the auto-detected project scope to read and write memories under a different project ID. Use when working across multiple projects, accessing memories from another repo, or when auto-detection resolves to the wrong project. +--- + +# Mem0 Switch Project + +Override the automatic project_id detection for the current directory. + +## Usage + +The user provides a project name as an argument: `/mem0:switch-project ` + +## Execution + +1. If no project name was given, ask: "What project_id should this directory use?" + +2. Write the mapping to `~/.mem0/project_map.json` using the Bash tool: + + ```bash + python3 -c " + import json, os + map_file = os.path.expanduser('~/.mem0/project_map.json') + mapping = {} + if os.path.isfile(map_file): + with open(map_file) as f: + mapping = json.load(f) + mapping[os.getcwd()] = '' + os.makedirs(os.path.dirname(map_file), exist_ok=True) + with open(map_file, 'w') as f: + json.dump(mapping, f, indent=2) + print(f'Mapped {os.getcwd()} -> ') + " + ``` + + (Replace `` with the user's chosen project name.) + +3. Verify by searching for existing memories: + - Call `search_memories` with `query="project"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=1` + +4. Print: + ``` + Switched to project . + memories found for this project. + Note: This override persists across sessions for this directory. + ``` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode-skills/tour/SKILL.md b/mem0-plugin/.opencode-plugin/opencode-skills/tour/SKILL.md new file mode 100644 index 0000000000..04c061b046 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode-skills/tour/SKILL.md @@ -0,0 +1,156 @@ +--- +name: tour +description: Browses all stored memories grouped by category with full content display. Use when reviewing all project memories, exploring stored knowledge, onboarding to a project, or getting an overview of captured decisions, conventions, and learnings. +--- + +# Mem0 Project Tour + +Show the user what mem0 has stored for the current project. + +## Cross-project mode + +When invoked with `--all-projects` (e.g., `/mem0:tour --all-projects` or +`/mem0:tour --all-projects auth middleware`), search across ALL projects: + +1. Call `get_memories` with `filters={"AND": [{"user_id": ""}]}`, `page_size=200` — **no `app_id` filter**. +2. If a search query was also provided, run `search_memories` with `query=`, + `filters={"AND": [{"user_id": ""}]}`, `top_k=20` — again no `app_id`. +3. Group results by `app_id` first, then by category within each project. +4. Display: + ``` + ## ( memories) ← current + **Architecture Decisions** — + ... + + ## ( memories) + ... + + memories across projects + ``` +5. Mark the current project with `← (current)` in the heading. + +If `--all-projects` is NOT present, use the standard single-project flow below. + +## Peek mode (compact search) + +When `/mem0:tour` receives a search query argument (e.g., `/mem0:tour auth middleware`) +WITHOUT `--all-projects`, run in **peek mode** — compact one-liner results: + +1. Run 2 parallel `search_memories` calls: + - Broad: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` + - Targeted: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}`, `top_k=5`, `rerank=true` +2. Deduplicate by ID, display compact results: + ``` + ## mem0 search: "" ( results) + + 1. [decision] Auth module uses JWT with RS256 keys (2025-05-15) [mem0:a3f8b2c1] + 2. [anti_pattern] Don't use symmetric HS256 — leaked in env (2025-05-10) [mem0:7e2d9f4a] + 3. [convention] All middleware in src/middleware/ (2025-05-08) [mem0:c4d5e6f7] + ``` + Format: `. [] () [mem0:]` +3. If no results: `No memories matching "" for project .` + +If no query argument and no `--all-projects` flag, use the full tour flow below. + +## Execution + +### Step 1: Fetch ALL memories for this project + +Call `get_memories` to fetch all memories for this project: + +`filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `page_size=100` + +### Step 2: Run supplementary semantic searches + +In parallel, run these `search_memories` calls to get relevance-ranked results for key topics: + +- `query="architecture decisions design choices"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` +- `query="bugs errors failures anti-patterns"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` +- `query="project setup tooling conventions preferences"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `top_k=10`, `rerank=true` + +**Do NOT filter by `metadata.type` in these calls.** The platform auto-assigns `categories` — filtering on `metadata.type` misses memories that were auto-categorized but don't have an explicit `metadata.type`. + +### Step 3: Merge and group + +Merge all results by memory ID (deduplicate). For each memory, determine its group using this priority: + +1. **Platform `categories` field** (array on each memory, auto-assigned by Mem0). Use the first category value. +2. **`metadata.type` field** (if present, set explicitly by hooks/agent). Use as fallback if no `categories`. +3. **"other"** bucket for memories with neither. + +Map category names to display names: + +| Platform category / metadata.type | Display name | +|---|---| +| `architecture decisions`, `architecture_decisions`, `decision` | Architecture Decisions | +| `anti patterns`, `anti_patterns`, `anti_pattern` | Anti-Patterns | +| `task learnings`, `task_learnings`, `task_learning` | Task Learnings | +| `coding conventions`, `coding_conventions`, `convention` | Coding Conventions | +| `user preferences`, `user_preferences`, `user_preference` | User Preferences | +| `project profile`, `project_profile` | Project Profile | +| `tooling setup`, `tooling_setup`, `environmental` | Tooling & Setup | +| `technology`, `professional_details` | Tooling & Setup | +| `session_state` | Session State | +| `compact_summary` | Compact Summaries | +| anything else | Other | + +### Step 4: Display results + +Sort groups by descending memory count. Display in compact tabular format: + +First show the category summary table: + +``` +mem0 tour + +Session (ses_abc123) branch: main +Project: my-project - 349 memories + +Category Count +----------------------------------------- +tooling_setup 119 +bug_fixes 78 +architecture_decisions 32 +task_learnings 14 +... +``` + +Then for each category (sorted by count descending), show memories as numbered one-liners. Truncate each memory to 100 chars max: + +``` +tooling_setup (119) + 1. User requires that no git commit or push be performed without explicit permission... + 2. OpenCode plugins are loaded from ~/.config/opencode/plugins/ for global installation... + 3. Assistant determined that the symlink method for loading the Mem0 plugin was failing... + ... and 116 more + +bug_fixes (78) + 1. Fixed getAll filter format from flat object to AND-wrapped array for mem0ai TS SDK v3... + 2. Root cause of user_id mismatch: plugin derived kartik.labhshetwar from git email... + ... and 76 more +``` + +Show top 5 memories per category by recency. If a group has more than 5, note `... and more`. + +Skip empty groups entirely. + +### Step 5: Print totals + +``` + memories across categories +project: branch: + +Identity - user: project: branch: +``` + +### Step 6: Empty state + +If zero memories found for this project, print: +``` +No memories stored yet for project . +Run /mem0-onboard to import project files, or start working - mem0 captures learnings automatically. +``` + +## Output formatting + +IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim - markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables. diff --git a/mem0-plugin/.opencode-plugin/opencode.json b/mem0-plugin/.opencode-plugin/opencode.json new file mode 100644 index 0000000000..701e57c05b --- /dev/null +++ b/mem0-plugin/.opencode-plugin/opencode.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://opencode.ai/config.json", + "plugin": ["@mem0/opencode-plugin"], + "mcp": { + "mem0": { + "type": "remote", + "url": "https://mcp.mem0.ai/mcp/", + "headers": { + "Authorization": "Token {env:MEM0_API_KEY}" + }, + "oauth": false + } + } +} diff --git a/mem0-plugin/.opencode-plugin/package.json b/mem0-plugin/.opencode-plugin/package.json new file mode 100644 index 0000000000..633e0c2e37 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/package.json @@ -0,0 +1,68 @@ +{ + "name": "@mem0/opencode-plugin", + "version": "0.1.0", + "type": "module", + "description": "Mem0 persistent memory plugin for OpenCode — add, search, and manage memories across sessions", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "bin": { + "mem0-opencode": "./cli.ts" + }, + "license": "Apache-2.0", + "keywords": [ + "opencode", + "opencode-plugin", + "mem0", + "memory", + "mcp", + "ai-memory", + "persistent-memory" + ], + "repository": { + "type": "git", + "url": "https://github.com/mem0ai/mem0", + "directory": "mem0-plugin/.opencode-plugin" + }, + "files": [ + "dist", + "cli.ts", + "opencode.json", + "LICENSE", + "opencode-skills/**" + ], + "scripts": { + "build": "bun build opencode-mem0.ts --outdir dist --target bun --format esm --entry-naming index.[ext] && tsc --emitDeclarationOnly --outDir dist --declaration", + "dev": "bun build opencode-mem0.ts --outdir dist --target bun --format esm --entry-naming index.[ext] --watch", + "type-check": "tsc --noEmit", + "prepack": "bun run build", + "postpack": "" + }, + "opencode": { + "type": "plugin", + "hooks": [ + "chat.message", + "tool.execute.before", + "tool.execute.after", + "experimental.chat.system.transform", + "experimental.session.compacting", + "shell.env" + ] + }, + "dependencies": { + "@opencode-ai/plugin": "^1.0.162", + "mem0ai": "^3.0.5" + }, + "devDependencies": { + "bun-types": ">=1.3.14", + "typescript": "^5.7.3" + }, + "peerDependencies": { + "bun": ">=1.0.0" + } +} diff --git a/mem0-plugin/.opencode-plugin/tsconfig.json b/mem0-plugin/.opencode-plugin/tsconfig.json new file mode 100644 index 0000000000..007c949b06 --- /dev/null +++ b/mem0-plugin/.opencode-plugin/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "declaration": true, + "declarationDir": "dist", + "outDir": "dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "lib": ["ESNext"], + "types": ["bun-types"], + "noEmit": false, + "emitDeclarationOnly": true + }, + "include": ["opencode-mem0.ts"], + "exclude": ["node_modules", "dist", "cli.ts", "skills"] +} diff --git a/mem0-plugin/CHANGELOG.md b/mem0-plugin/CHANGELOG.md index 7814080850..69e976e22e 100644 --- a/mem0-plugin/CHANGELOG.md +++ b/mem0-plugin/CHANGELOG.md @@ -2,6 +2,38 @@ All notable changes to the Mem0 plugin will be documented in this file. +## 0.1.0 — OpenCode & Antigravity + +### Added + +- **OpenCode plugin** (`@mem0/opencode-plugin` on npm): Pure TypeScript plugin using the `mem0ai` TS SDK — no Python, no shell scripts. Hooks into all 6 OpenCode events (`chat.message`, `tool.execute.before`, `tool.execute.after`, `experimental.chat.system.transform`, `experimental.session.compacting`, `shell.env`). Features: session start memory loading, per-prompt semantic search, error pattern detection with memory lookup, resume/remember intent detection, auto-capture every 3rd message, periodic save nudges, full metadata defaults injection (confidence, source, type, session_id, files, branch), identity injection for search/get/delete filters, type-filtered error pre-fetch (anti_pattern + bug_fix), pre-compaction memory capture, MEMORY.md write blocking, and secret redaction. +- **16 OpenCode-native skills** bundled in `opencode-skills/`: `context-loader`, `dream`, `export`, `forget`, `health`, `import`, `list-projects`, `mem0` (SDK reference), `memory-reviewer`, `onboard`, `peek`, `pin`, `remember`, `stats`, `switch-project`, `tour`. All skills are pure MCP-tool-based — no Python scripts, no shell scripts, no Claude Code dependencies. +- **Auto-install skills and commands (`installSkills()`):** On plugin load, copies all 16 skills to `.opencode/skills/` and creates command wrapper files in `.opencode/commands/` so they appear in the OpenCode `/` palette. No manual setup needed. +- **`extractUserText()` handler:** Robust text extraction from OpenCode response shapes — handles `parts[]` array, `content[]` array, `message.content`, and plain string responses. +- **Identity resolution:** `getUserId()` uses `os.userInfo().username` (matching Claude Code's `${USER}` convention) with `MEM0_USER_ID` env override. `getProjectId()` uses git remote with `MEM0_APP_ID` env override. +- **Context injection via `experimental.chat.system.transform`:** All memory context (session start memories, per-prompt search results, error-related memories, compaction context) injected as system context. +- **CLI installer (`cli.ts`):** `bunx @mem0/opencode-plugin install` auto-configures plugin and MCP server in `~/.config/opencode/opencode.json`. +- **Antigravity plugin** (`.antigravity/`): Restructured to follow the same shared-infrastructure pattern as Claude Code, Cursor, and Codex. Self-contained plugin directory with `plugin.json`, `mcp_config.json`, `hooks/hooks.json` (own file), `scripts/` (symlink → `../scripts/`), and `skills/` (symlink → `../skills/`). Installable via `agy plugin install .antigravity` or `npx degit mem0ai/mem0/mem0-plugin/.antigravity ~/.gemini/config/plugins/mem0`. Uses `contextFileName: "AGENTS.md"` per Antigravity convention. +- **Codex hooks parity:** Added missing `PreToolUse` Write/Edit/MultiEdit block and `PreCompact` hook to Codex hooks config, bringing it to full parity with Claude Code. + +### Changed + +- **Antigravity plugin directory:** Renamed `.antigravity-plugin/` → `.antigravity/` to match the naming convention of `.claude-plugin/`, `.cursor-plugin/`, `.codex-plugin/`. Plugin is now self-contained so `agy plugin install` and `npx degit` both work. +- **Antigravity hooks:** Hooks now live directly in `.antigravity/hooks/hooks.json` as a standalone file — no indirection. +- **Antigravity install command:** Updated from `npx degit mem0ai/mem0/mem0-plugin` to `npx degit mem0ai/mem0/mem0-plugin/.antigravity`. Added `agy plugin install` as alternative for local clones. + +### Removed + +- **Stale root-level files:** Deleted `mem0-plugin/hooks.json`, `mem0-plugin/mcp_config.json`, `mem0-plugin/plugin.json` — leftover artifacts from before plugin restructuring into per-editor subdirectories. Nothing referenced them; install commands point to `.antigravity/`. + +## 0.2.7 + +### Fixed + +- **First-install auth failure (closes #4876):** Removed `authorizationUrl` from `.mcp.json`. When both a static `Authorization` header and `authorizationUrl` were present, Claude Code preferred the OAuth flow, which failed on reconnect — leaving new users stuck with only `authenticate`/`complete_authentication` stub tools. Authentication now uses the `MEM0_API_KEY` header exclusively; no browser OAuth flow is triggered. +- **Onboarding skill removed OAuth step:** `/mem0:onboard` Step 2 no longer guides users through a browser-based OAuth login. The MCP server authenticates via the API key set in Step 1. +- **Removed `claude plugin configure mem0` references:** This CLI command does not exist. The `userConfig` mechanism works through the plugin enable UI prompt — Claude Code prompts for the API key when the plugin is first enabled and stores it securely in the system keychain. Updated session start banner, onboarding skill, identity script comments, and manual testing guide. + ## 0.2.6 ### Fixed diff --git a/mem0-plugin/README.md b/mem0-plugin/README.md index 69b5788da4..7a39722f99 100644 --- a/mem0-plugin/README.md +++ b/mem0-plugin/README.md @@ -1,6 +1,6 @@ -# Mem0 Plugin for Claude Code, Claude Cowork, Cursor & Codex +# Mem0 Plugin for Claude Code, Claude Cowork, Cursor, Codex, OpenCode & Antigravity -Add persistent memory to your AI workflows. Store, retrieve, and manage memories across sessions using the Mem0 Platform. Works with **Claude Code** (CLI), **Claude Cowork** (desktop app), **Cursor**, and **Codex**. +Add persistent memory to your AI workflows. Store, retrieve, and manage memories across sessions using the Mem0 Platform. Works with **Claude Code** (CLI), **Claude Cowork** (desktop app), **Cursor**, **Codex**, **OpenCode**, and **Antigravity**. ## Quick path for agents @@ -21,7 +21,9 @@ Humans setting up Mem0 by hand should continue with Step 1 below. 1. Sign up at [app.mem0.ai](https://app.mem0.ai?utm_source=oss&utm_medium=mem0-plugin-readme) if you haven't already 2. Go to [app.mem0.ai/dashboard/api-keys](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-readme) 3. Click **Create API Key** and copy the key (starts with `m0-`) -4. Add it to your shell profile: +4. Set the key using **one** of these methods: + + **CLI** — add to your shell profile: ```bash # For zsh (default on macOS) @@ -33,6 +35,12 @@ Humans setting up Mem0 by hand should continue with Step 1 below. source ~/.bashrc ``` + **Desktop app** — use the local environment editor: + + Click the environment dropdown next to the prompt box → hover over **Local** → click the **gear icon** → add `MEM0_API_KEY` with your key. Values are stored encrypted on your machine. + + > **Note:** The Desktop app does not inherit custom environment variables from shell profiles — it only reads `PATH`. You must use the local environment editor for Desktop. + 5. Confirm it's set: ```bash @@ -147,6 +155,69 @@ Add the following to your `.cursor/mcp.json`: Install from the [Cursor Marketplace](https://cursor.com/marketplace) for the complete experience including lifecycle hooks and the Mem0 SDK skill. +### OpenCode + +```bash +bunx @mem0/opencode-plugin@latest install +``` + +Or via OpenCode's built-in CLI: `opencode plugin @mem0/opencode-plugin` + +Then add the MCP server to your `opencode.json` (project or global at `~/.config/opencode/opencode.json`): + +```json +{ + "mcp": { + "mem0": { + "type": "remote", + "url": "https://mcp.mem0.ai/mcp/", + "headers": { + "Authorization": "Token {env:MEM0_API_KEY}" + }, + "oauth": false + } + } +} +``` + +Restart OpenCode. The plugin installs hooks and skills automatically. Drop the `plugin` install if you only want MCP. + +See [OpenCode integration docs](https://docs.mem0.ai/integrations/opencode) for full details. + +### Antigravity (Google) + +**Option A — degit** (recommended, one command): + +```bash +npx degit mem0ai/mem0/mem0-plugin/.antigravity ~/.gemini/config/plugins/mem0 +``` + +**Option B — agy CLI** (if you have the repo cloned locally): + +```bash +agy plugin install /path/to/mem0/mem0-plugin/.antigravity +``` + +Both install the MCP server, lifecycle hooks, all 16 skills, and shared scripts. + +**Option C — MCP only** (no hooks or skills) — create `~/.gemini/config/plugins/mem0/plugin.json`: + +```json +{ + "name": "mem0", + "mcpServers": { + "mem0": { + "serverUrl": "https://mcp.mem0.ai/mcp/", + "headers": { "Authorization": "Token ${MEM0_API_KEY}" } + } + } +} +``` + +All options read `MEM0_API_KEY` from your environment automatically. + +See [Antigravity integration docs](https://docs.mem0.ai/integrations/antigravity) for full details. + ## Post-Installation: Run `/mem0:onboard` After installing, start a new session and run: @@ -194,23 +265,25 @@ The plugin includes 17 skills accessible via `/mem0:` commands: ## What's included -| Component | Claude Code / Cowork | Cursor (Marketplace) | Cursor (Deeplink/Manual) | Codex (Sideload) | Codex (Direct MCP) | -|-----------|:--------------------:|:--------------------:|:------------------------:|:----------------:|:------------------:| -| MCP Server | Yes | Yes | Yes | Yes | Yes | -| Lifecycle Hooks | Yes | Yes | No | Opt-in | No | -| Mem0 SDK Skill | Yes | Yes | No | Yes | No | +| Component | Claude Code / Cowork | Cursor (Marketplace) | Cursor (Deeplink/Manual) | Codex (Sideload) | Codex (Direct MCP) | OpenCode (Full) | OpenCode (MCP) | Antigravity (A/B) | Antigravity (C) | +|-----------|:--------------------:|:--------------------:|:------------------------:|:----------------:|:------------------:|:---------------:|:--------------:|:------------------:|:-----------------:| +| MCP Server | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | +| Lifecycle Hooks | Yes | Yes | No | Opt-in | No | Yes | No | Yes | No | +| Mem0 SDK Skill | Yes | Yes | No | Yes | No | Yes | No | Yes | No | - **MCP Server** — Connects to the Mem0 remote MCP server (`mcp.mem0.ai`), providing tools to add, search, update, and delete memories. No local dependencies required. -- **Lifecycle Hooks** — Automatic memory capture at key points. Claude Code and Cursor wire hooks up natively when the plugin is installed (session start, context compaction, task completion, session end). Codex hooks are opt-in via a one-time installer (`scripts/install_codex_hooks.py`) that writes entries into `~/.codex/hooks.json` for `SessionStart`, `UserPromptSubmit`, and `Stop`. +- **Lifecycle Hooks** — Automatic memory capture at key points. Claude Code, Cursor, OpenCode, and Antigravity wire hooks natively when the full plugin is installed. Codex hooks are opt-in via a one-time installer (`scripts/install_codex_hooks.py`). - **Mem0 SDK Skill** — Guides the AI on how to integrate the Mem0 SDK (Python & TypeScript) into your applications. ## Updating the plugin -When the plugin updates (new version pulled from the marketplace, or a fresh local install), the MCP server connection in your existing Claude Code / Cursor / Codex session is left holding a stale handle and stops responding. **Restart your client to reconnect:** +When the plugin updates (new version pulled from the marketplace, or a fresh local install), the MCP server connection in your existing session is left holding a stale handle and stops responding. **Restart your client to reconnect:** - **Claude Code:** run `/restart` in the prompt, or close and reopen the CLI. - **Cursor:** quit and relaunch. - **Codex:** restart the editor session. +- **OpenCode:** restart the session. +- **Antigravity:** restart the session, or re-run `agy plugin install /path/to/mem0/mem0-plugin/.antigravity`. Your `MEM0_API_KEY` doesn't need to be re-entered — the auth header is re-read from your environment on the new session. The plugin's MCP config uses `${MEM0_API_KEY}` interpolation at session start, not at install time, so as long as the env var is set persistently (in your shell profile or `~/.claude/settings.json` `env` block), reconnection is automatic on restart. diff --git a/mem0-plugin/hooks/codex-hooks.json b/mem0-plugin/hooks/codex-hooks.json index c9293822b9..4756f28a28 100644 --- a/mem0-plugin/hooks/codex-hooks.json +++ b/mem0-plugin/hooks/codex-hooks.json @@ -1,6 +1,16 @@ { "hooks": { "PreToolUse": [ + { + "matcher": "Write|Edit|MultiEdit", + "hooks": [ + { + "type": "command", + "command": "${PLUGIN_ROOT}/scripts/block_memory_write.sh", + "timeout": 3 + } + ] + }, { "matcher": "mcp__mem0__add_memory|mcp__plugin_mem0_mem0__add_memory|mcp__mem0__search_memories|mcp__plugin_mem0_mem0__search_memories|mcp__mem0__get_memories|mcp__plugin_mem0_mem0__get_memories|mcp__mem0__delete_all_memories|mcp__plugin_mem0_mem0__delete_all_memories", "hooks": [ @@ -57,6 +67,17 @@ } ] } + ], + "PreCompact": [ + { + "hooks": [ + { + "type": "command", + "command": "${PLUGIN_ROOT}/scripts/on_pre_compact.sh", + "statusMessage": "Preparing pre-compaction summary..." + } + ] + } ] } } diff --git a/mem0-plugin/scripts/_identity.py b/mem0-plugin/scripts/_identity.py index e25da89162..d60ccb4a39 100644 --- a/mem0-plugin/scripts/_identity.py +++ b/mem0-plugin/scripts/_identity.py @@ -2,7 +2,7 @@ API key resolution (first non-empty wins): 1. MEM0_API_KEY env var (explicit / shell profile) - 2. CLAUDE_PLUGIN_OPTION_API_KEY (set by `claude plugin configure mem0`) + 2. CLAUDE_PLUGIN_OPTION_API_KEY (injected by Claude Code userConfig) 3. CLAUDE_PLUGIN_OPTION_MEM0_API_KEY (legacy userConfig) 4. Extract from shell profile files (~/.zshrc, ~/.bashrc, etc.) Desktop app doesn't inherit shell env — this covers users who diff --git a/mem0-plugin/scripts/_identity.sh b/mem0-plugin/scripts/_identity.sh index 0a0856d122..8a7d3762af 100644 --- a/mem0-plugin/scripts/_identity.sh +++ b/mem0-plugin/scripts/_identity.sh @@ -2,7 +2,7 @@ # # API key resolution (first non-empty wins): # 1. MEM0_API_KEY env var (explicit / shell profile) -# 2. CLAUDE_PLUGIN_OPTION_API_KEY (set by `claude plugin configure mem0`) +# 2. CLAUDE_PLUGIN_OPTION_API_KEY (injected by Claude Code userConfig) # 3. CLAUDE_PLUGIN_OPTION_MEM0_API_KEY (legacy userConfig) # 4. Extract from shell profile files (~/.zshrc, ~/.bashrc, etc.) # Desktop app doesn't inherit shell env — this fallback covers users diff --git a/mem0-plugin/scripts/on_session_start.sh b/mem0-plugin/scripts/on_session_start.sh index 828be49a46..64a1d3f28d 100755 --- a/mem0-plugin/scripts/on_session_start.sh +++ b/mem0-plugin/scripts/on_session_start.sh @@ -53,7 +53,8 @@ Mem0 — Setup Required | user=${_UID} | project=${_PID} | branch=${_BR} | auth= \`\`\` MEM0_API_KEY is not set. To configure: -- **Desktop app**: Run \`claude plugin configure mem0\` in the built-in terminal, or add \`MEM0_API_KEY\` in the Desktop app's environment editor (Settings → Environment) +- **Reinstall the plugin**: Uninstall and reinstall — Claude Code will prompt for your API key during setup (stored securely in keychain) +- **Desktop app**: Click the environment dropdown next to the prompt box → hover over **Local** → click the **gear icon** → add \`MEM0_API_KEY=m0-...\` - **CLI**: Add \`export MEM0_API_KEY=m0-...\` to your shell profile (~/.zshrc or ~/.bashrc) - Get a key at https://app.mem0.ai/dashboard/api-keys diff --git a/mem0-plugin/skills/onboard/SKILL.md b/mem0-plugin/skills/onboard/SKILL.md index ccb5c1af2d..f8ecfb9d90 100644 --- a/mem0-plugin/skills/onboard/SKILL.md +++ b/mem0-plugin/skills/onboard/SKILL.md @@ -46,14 +46,19 @@ Step 1: Setting up API key. 2. Choose ONE method: - Option A — Plugin config (works on Desktop + CLI): - Type: ! claude plugin configure mem0 - Paste your API key when prompted. - - Option B — Shell profile (CLI only): + Option A — CLI (shell profile): echo 'export MEM0_API_KEY="m0-your-key-here"' >> ~/.zshrc source ~/.zshrc + Option B — Desktop app (local environment editor): + Click the environment dropdown next to the prompt box, + hover over "Local", click the gear icon, and add: + MEM0_API_KEY = m0-your-key-here + (Stored encrypted on your machine, applies to all local sessions) + + Note: The Desktop app does NOT inherit custom env vars from + shell profiles — it only reads PATH. Use Option B for Desktop. + 3. Verify: [ -n "${MEM0_API_KEY:-${CLAUDE_PLUGIN_OPTION_API_KEY:-}}" ] && echo "SET" || echo "NOT_SET" ``` @@ -66,26 +71,17 @@ First, check if MCP tools are already available using ToolSearch with query `"me **If MCP tools ARE found:** Print `- MCP already connected.` and proceed to Step 3. -**If MCP tools are NOT found:** Guide the user through OAuth: +**If MCP tools are NOT found:** -``` -Step 2: MCP OAuth login. - - 1. Type /mcp in Claude Code - 2. A browser window will open for authentication at mcp.mem0.ai - 3. Log in with your mem0 account - 4. Return here after authenticating in your browser -``` +The MCP server authenticates using the `MEM0_API_KEY` set in Step 1. No OAuth or browser login is needed. -After the user completes OAuth, verify MCP tools again using ToolSearch. +1. Verify the API key is set (re-run the Step 1 check) +2. Check the plugin is installed: run `/plugins` and confirm `mem0` appears +3. Check the MCP server is listed: run `/mcp` and look for `mcp.mem0.ai` +4. If the server shows an error, ask the user to restart Claude Code and run `/mem0:onboard` again +5. If all checks pass but tools are still missing: "Restart Claude Code and run `/mem0:onboard` again." -- If MCP tools found: Print `- MCP connected.` and proceed to Step 3. -- If NOT found: Troubleshoot before giving up: - 1. Check plugin is installed: run `/plugins` and confirm `mem0` appears - 2. Ask if the browser auth completed successfully - 3. Look for `mcp.mem0.ai` in the MCP server list via `/mcp` - 4. If all checks fail: "Restart Claude Code and run `/mem0:onboard` again." - **STOP here** — do not proceed without MCP tools. +**STOP here** — do not proceed without MCP tools. ## Step 3: Verify connectivity and show identity diff --git a/mem0-ts/package.json b/mem0-ts/package.json index ebec11c322..cc24d72ca2 100644 --- a/mem0-ts/package.json +++ b/mem0-ts/package.json @@ -1,6 +1,6 @@ { "name": "mem0ai", - "version": "3.0.4", + "version": "3.0.5", "description": "The Memory Layer For Your AI Apps", "main": "./dist/index.js", "module": "./dist/index.mjs", diff --git a/mem0-ts/src/client/mem0.ts b/mem0-ts/src/client/mem0.ts index cac2fe36ca..89fe45cef4 100644 --- a/mem0-ts/src/client/mem0.ts +++ b/mem0-ts/src/client/mem0.ts @@ -9,6 +9,7 @@ import { SearchMemoryOptions, GetAllMemoryOptions, DeleteAllMemoryOptions, + DeleteMemoryOptions, MemoryUpdateBody, ProjectResponse, PromptUpdatePayload, @@ -377,11 +378,17 @@ export default class MemoryClient { return response; } - async delete(memoryId: string): Promise<{ message: string }> { + async delete( + memoryId: string, + options: DeleteMemoryOptions = {}, + ): Promise<{ message: string }> { if (this.telemetryId === "") await this.ping(); - this._captureEvent("delete", []); + this._captureEvent("delete", [Object.keys(options || {})]); + const snakeOptions = camelToSnakeKeys(this._prepareParams(options)); + // @ts-ignore + const query = new URLSearchParams(snakeOptions).toString(); return this._fetchWithErrorHandling( - `${this.host}/v1/memories/${memoryId}/`, + `${this.host}/v1/memories/${memoryId}/${query ? `?${query}` : ""}`, { method: "DELETE", headers: this.headers, diff --git a/mem0-ts/src/client/mem0.types.ts b/mem0-ts/src/client/mem0.types.ts index 0c5c1c93f5..185b1dbea7 100644 --- a/mem0-ts/src/client/mem0.types.ts +++ b/mem0-ts/src/client/mem0.types.ts @@ -39,6 +39,15 @@ export interface GetAllMemoryOptions { export interface DeleteAllMemoryOptions extends EntityOptions {} +export interface DeleteMemoryOptions { + /** + * When `true`, also delete the older memories this one superseded (the v3 + * linked chain), transitively — the delete-side counterpart of `latestOnly`. + * Off by default. Serialized as `delete_linked`. + */ + deleteLinked?: boolean; +} + // ─── Project Options ──────────────────────────────────────── export interface ProjectOptions { fields?: string[]; diff --git a/mem0-ts/src/client/tests/memoryClient.crud.test.ts b/mem0-ts/src/client/tests/memoryClient.crud.test.ts index 80bdcfa016..39e3f1733a 100644 --- a/mem0-ts/src/client/tests/memoryClient.crud.test.ts +++ b/mem0-ts/src/client/tests/memoryClient.crud.test.ts @@ -200,9 +200,26 @@ describe("MemoryClient - delete()", () => { const client = new MemoryClient({ apiKey: TEST_API_KEY }); await client.delete("mem_123"); - expect( - findFetchCall(mock, "/v1/memories/mem_123/", "DELETE"), - ).toBeDefined(); + const call = findFetchCall(mock, "/v1/memories/mem_123/", "DELETE"); + expect(call).toBeDefined(); + // Default: no cascade query param, URL byte-identical to before. + expect(call![0]).not.toContain("delete_linked"); + }); + + test("serializes deleteLinked as delete_linked query param", async () => { + const extra = new Map(); + extra.set("/v1/memories/mem_123/", { + status: 200, + body: { message: "Memory deleted successfully", cascade_count: 1 }, + }); + const mock = setupMockFetch(extra); + + const client = new MemoryClient({ apiKey: TEST_API_KEY }); + await client.delete("mem_123", { deleteLinked: true }); + + const call = findFetchCall(mock, "/v1/memories/mem_123/", "DELETE"); + expect(call).toBeDefined(); + expect(call![0]).toContain("delete_linked=true"); }); }); diff --git a/mem0/client/main.py b/mem0/client/main.py index 69ff77bd8f..d4633709f5 100644 --- a/mem0/client/main.py +++ b/mem0/client/main.py @@ -360,11 +360,16 @@ def update( return response.json() @api_error_handler - def delete(self, memory_id: str) -> Dict[str, Any]: + def delete(self, memory_id: str, delete_linked: bool = False) -> Dict[str, Any]: """Delete a specific memory by ID. Args: memory_id: The ID of the memory to delete. + delete_linked: When True, also delete the older memories this one + superseded (the v3 ``linked_memory_ids`` chain), transitively. + This is the delete-side counterpart of ``latest_only`` — it + stops a superseded memory from resurfacing after you delete the + current one. Defaults to False (only the given memory is deleted). Returns: A dictionary containing the API response. @@ -377,10 +382,12 @@ def delete(self, memory_id: str) -> Dict[str, Any]: NetworkError: If network connectivity issues occur. MemoryNotFoundError: If the memory doesn't exist (for updates/deletes). """ - params = self._prepare_params() + params = self._prepare_params({"delete_linked": delete_linked or None}) response = self.client.delete(f"/v1/memories/{memory_id}/", params=params) response.raise_for_status() - capture_client_event("client.delete", self, {"memory_id": memory_id, "sync_type": "sync"}) + capture_client_event( + "client.delete", self, {"memory_id": memory_id, "delete_linked": delete_linked, "sync_type": "sync"} + ) return response.json() @api_error_handler @@ -1268,11 +1275,16 @@ async def update( return response.json() @api_error_handler - async def delete(self, memory_id: str) -> Dict[str, Any]: + async def delete(self, memory_id: str, delete_linked: bool = False) -> Dict[str, Any]: """Delete a specific memory by ID. Args: memory_id: The ID of the memory to delete. + delete_linked: When True, also delete the older memories this one + superseded (the v3 ``linked_memory_ids`` chain), transitively. + This is the delete-side counterpart of ``latest_only`` — it + stops a superseded memory from resurfacing after you delete the + current one. Defaults to False (only the given memory is deleted). Returns: A dictionary containing the API response. @@ -1285,10 +1297,12 @@ async def delete(self, memory_id: str) -> Dict[str, Any]: NetworkError: If network connectivity issues occur. MemoryNotFoundError: If the memory doesn't exist (for updates/deletes). """ - params = self._prepare_params() + params = self._prepare_params({"delete_linked": delete_linked or None}) response = await self.async_client.delete(f"/v1/memories/{memory_id}/", params=params) response.raise_for_status() - capture_client_event("client.delete", self, {"memory_id": memory_id, "sync_type": "async"}) + capture_client_event( + "client.delete", self, {"memory_id": memory_id, "delete_linked": delete_linked, "sync_type": "async"} + ) return response.json() @api_error_handler diff --git a/pyproject.toml b/pyproject.toml index 300d88a6ad..13eb2fac61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mem0ai" -version = "2.0.3" +version = "2.0.4" description = "Long-term memory for AI Agents" authors = [ { name = "Mem0", email = "support@mem0.ai" } diff --git a/tests/test_client.py b/tests/test_client.py index fb685c02a2..66d01a4c0b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -147,3 +147,41 @@ def test_search_passes_complex_nested_filters(self, mock_memory_client): call_args = mock_memory_client.client.post.call_args payload = call_args.kwargs.get("json", call_args.args[1] if len(call_args.args) > 1 else {}) assert payload["filters"] == complex_filter + + +class TestDeleteLinked: + """delete() should forward the opt-in delete_linked flag as a query param.""" + + def _setup_delete(self, client): + client.client.delete.return_value = MagicMock( + json=lambda: {"message": "Memory deleted successfully!"}, + raise_for_status=lambda: None, + ) + + def test_delete_default_omits_delete_linked(self, mock_memory_client): + """Default delete sends no delete_linked param — byte-identical to before.""" + self._setup_delete(mock_memory_client) + + mock_memory_client.delete("mem_123") + + call_args = mock_memory_client.client.delete.call_args + assert call_args.args[0] == "/v1/memories/mem_123/" + assert "delete_linked" not in call_args.kwargs.get("params", {}) + + def test_delete_linked_true_sets_param(self, mock_memory_client): + """delete_linked=True forwards delete_linked into the request params.""" + self._setup_delete(mock_memory_client) + + mock_memory_client.delete("mem_123", delete_linked=True) + + call_args = mock_memory_client.client.delete.call_args + assert call_args.kwargs.get("params", {}).get("delete_linked") is True + + def test_delete_linked_false_omits_param(self, mock_memory_client): + """delete_linked=False is stripped, so the default path is untouched.""" + self._setup_delete(mock_memory_client) + + mock_memory_client.delete("mem_123", delete_linked=False) + + call_args = mock_memory_client.client.delete.call_args + assert "delete_linked" not in call_args.kwargs.get("params", {})