From ac8f862ff71c3e929a1fee429b315a14138c01e9 Mon Sep 17 00:00:00 2001 From: Kartik Date: Thu, 25 Jun 2026 09:06:11 +0530 Subject: [PATCH] fix(mem0-plugin): store files_touched as a list to stop double JSON-encoding (#5806) --- .claude-plugin/marketplace.json | 2 +- .cursor-plugin/marketplace.json | 2 +- docs/core-concepts/memory-evaluation.mdx | 2 +- docs/integrations/antigravity.mdx | 1 + docs/integrations/claude-code.mdx | 5 +- docs/integrations/codex.mdx | 32 +++++--- docs/integrations/cursor.mdx | 5 +- .../mem0-plugin/.claude-plugin/plugin.json | 2 +- .../mem0-plugin/.codex-plugin/plugin.json | 2 +- .../mem0-plugin/.cursor-plugin/plugin.json | 2 +- integrations/mem0-plugin/CHANGELOG.md | 12 +++ integrations/mem0-plugin/plugin.json | 2 +- .../scripts/capture_session_summary.py | 2 +- .../tests/test_capture_session_summary.py | 79 +++++++++++++++++++ 14 files changed, 129 insertions(+), 21 deletions(-) create mode 100644 integrations/mem0-plugin/tests/test_capture_session_summary.py diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index fdbe45f43..f5fdd19a8 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "mem0", "source": "./integrations/mem0-plugin", "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.", - "version": "0.2.10" + "version": "0.2.11" } ] } diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index 0d559f6c7..87c5fcadb 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "mem0", "source": "./integrations/mem0-plugin", "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.", - "version": "0.2.10" + "version": "0.2.11" } ] } diff --git a/docs/core-concepts/memory-evaluation.mdx b/docs/core-concepts/memory-evaluation.mdx index aae537c48..5f6dfb894 100644 --- a/docs/core-concepts/memory-evaluation.mdx +++ b/docs/core-concepts/memory-evaluation.mdx @@ -345,7 +345,7 @@ When evaluating memory systems, keep these considerations in mind: Published research papers and technical reports - + Detailed writeup of the new algorithm design and results diff --git a/docs/integrations/antigravity.mdx b/docs/integrations/antigravity.mdx index 9a9d9b1f4..7fd541408 100644 --- a/docs/integrations/antigravity.mdx +++ b/docs/integrations/antigravity.mdx @@ -65,6 +65,7 @@ The plugin uses the same shell scripts as Claude Code, Cursor, and Codex — hoo | **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message | | **Pre-tool** | `PreToolUse` | Blocks MEMORY.md writes, enforces `user_id`/`app_id` on mem0 tools | | **Post-tool** | `PostToolUse` | Tracks stats, scans bash errors for related memories | +| **Stop** | `Stop` | Stores a session summary when the session ends | ## Troubleshooting diff --git a/docs/integrations/claude-code.mdx b/docs/integrations/claude-code.mdx index 3a3516725..4781868a9 100644 --- a/docs/integrations/claude-code.mdx +++ b/docs/integrations/claude-code.mdx @@ -64,7 +64,7 @@ Add the Mem0 MCP server directly with a single command: npx mcp-add \ --name mem0-mcp \ --type http \ - --url "https://mcp.mem0.ai/mcp" \ + --url "https://mcp.mem0.ai/mcp/" \ --clients "claude code" ``` @@ -142,7 +142,8 @@ When installed via the plugin marketplace, Mem0 hooks into Claude Code's lifecyc | **User prompt** | `UserPromptSubmit` | Searches relevant memories before each message; skips short prompts | | **Pre-tool** | `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 | +| **Stop** | `Stop` | Stores a session summary when the session ends | +| **Pre-compact** | `PreCompact` | Stores a summary before the context is compacted | ## Example Workflow diff --git a/docs/integrations/codex.mdx b/docs/integrations/codex.mdx index f7f1226fe..2bcc26dd0 100644 --- a/docs/integrations/codex.mdx +++ b/docs/integrations/codex.mdx @@ -41,7 +41,17 @@ Install the full plugin including MCP server, lifecycle hooks, and SDK skill. codex plugin marketplace add mem0ai/mem0 ``` -2. Restart Codex, open the Plugin Directory, browse the **Mem0 Plugins** marketplace, and install **Mem0**. +2. Install the plugin: + + ```bash + codex plugin add mem0@mem0-plugins + ``` + + Or, in the app: restart Codex, open the Plugin Directory, browse the **Mem0 Plugins** marketplace, and install **Mem0**. + + + Step 1 is required for the app UI. Mem0 isn't in OpenAI's curated directory yet, so **without `codex plugin marketplace add`, Mem0 won't appear in the Codex app's Plugin Directory** — searching for it returns nothing. Adding the marketplace surfaces it (under **Created by you**) and makes it installable. + Do not combine with Option B. The plugin manifest auto-registers the `mem0` MCP server, so adding both will create a duplicate registration. @@ -49,27 +59,30 @@ Install the full plugin including MCP server, lifecycle hooks, and SDK skill. ### Option B — Direct MCP -The fastest way to connect Codex to Mem0 — no plugin, no marketplace. Add to `~/.codex/config.toml`: +The fastest way to connect Codex to Mem0 — no plugin, no marketplace. Add the MCP server with a single command: + +```bash +codex mcp add mem0 --url https://mcp.mem0.ai/mcp/ --bearer-token-env-var MEM0_API_KEY +``` + +Or add it manually to `~/.codex/config.toml`: ```toml [mcp_servers.mem0] -url = "https://mcp.mem0.ai/mcp" +url = "https://mcp.mem0.ai/mcp/" bearer_token_env_var = "MEM0_API_KEY" ``` Make sure `MEM0_API_KEY` is exported in the shell you launch Codex from, then restart Codex. - - 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). - - This gives you the MCP tools but not the lifecycle hooks or SDK skill. ### Managing the Plugin ```bash codex plugin marketplace upgrade # pull latest plugin versions -codex plugin marketplace remove mem0-plugins # unregister the marketplace +codex plugin remove mem0@mem0-plugins # uninstall the plugin (keeps the marketplace) +codex plugin marketplace remove mem0-plugins # unregister the marketplace entirely ``` To update, run `codex plugin marketplace upgrade` to pull the latest from the Mem0 repo. @@ -112,7 +125,8 @@ When installed via the plugin marketplace, Mem0 hooks into Codex's lifecycle to | **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 | +| **Stop** | `Stop` | Stores a session summary when the session ends | +| **Pre-compact** | `PreCompact` | Stores a summary before the context is compacted | ## Example Workflow diff --git a/docs/integrations/cursor.mdx b/docs/integrations/cursor.mdx index 1e516af72..d6021538c 100644 --- a/docs/integrations/cursor.mdx +++ b/docs/integrations/cursor.mdx @@ -47,7 +47,7 @@ The fastest way to get started. Click the link below to install the Mem0 MCP ser npx mcp-add \ --name mem0-mcp \ --type http \ - --url "https://mcp.mem0.ai/mcp" \ + --url "https://mcp.mem0.ai/mcp/" \ --clients "cursor" ``` @@ -110,7 +110,8 @@ When installed via the Cursor Marketplace, Mem0 hooks into Cursor's lifecycle: | **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 | +| **Stop** | `stop` | Stores a session summary when the session ends | +| **Pre-compact** | `preCompact` | Stores a summary before the context is compacted | ## Example Workflow diff --git a/integrations/mem0-plugin/.claude-plugin/plugin.json b/integrations/mem0-plugin/.claude-plugin/plugin.json index 0ab6583ee..e6cb12716 100644 --- a/integrations/mem0-plugin/.claude-plugin/plugin.json +++ b/integrations/mem0-plugin/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mem0", - "version": "0.2.10", + "version": "0.2.11", "description": "Persistent memory for Claude Code. Remembers decisions, patterns, and preferences across sessions.", "author": { "name": "Mem0", diff --git a/integrations/mem0-plugin/.codex-plugin/plugin.json b/integrations/mem0-plugin/.codex-plugin/plugin.json index 65c39271b..116db347b 100644 --- a/integrations/mem0-plugin/.codex-plugin/plugin.json +++ b/integrations/mem0-plugin/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mem0", - "version": "0.2.10", + "version": "0.2.11", "description": "Persistent memory for Codex. Remembers decisions, patterns, and preferences across sessions.", "author": { "name": "Mem0", diff --git a/integrations/mem0-plugin/.cursor-plugin/plugin.json b/integrations/mem0-plugin/.cursor-plugin/plugin.json index fafbf6aa6..ac63f6138 100644 --- a/integrations/mem0-plugin/.cursor-plugin/plugin.json +++ b/integrations/mem0-plugin/.cursor-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mem0", - "version": "0.2.10", + "version": "0.2.11", "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/integrations/mem0-plugin/CHANGELOG.md b/integrations/mem0-plugin/CHANGELOG.md index e8712b798..0905f0cd2 100644 --- a/integrations/mem0-plugin/CHANGELOG.md +++ b/integrations/mem0-plugin/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to the Mem0 plugin will be documented in this file. +## 0.2.11 — Session-summary metadata fix + rerank auto-injected context by default + +> Versions: Claude Code / Cursor / Codex `0.2.11`; Antigravity `0.1.3`. All four editors share `scripts/`, so the fix below applies to every editor. + +### Fixed + +- **`files_touched` was double-JSON-encoded in session summaries (`scripts/capture_session_summary.py`):** the Stop-hook summary set `metadata["files_touched"] = json.dumps(files[:20])` — a pre-serialized JSON string — and then serialized the whole request body again with `json.dumps(body)`. The stored memory therefore carried an escaped string blob (`"[\"mem0/memory/main.py\", \"src/client/index.ts\"]"`) instead of a real array, so file paths surfaced as backslash- and slash-heavy escaped text when those memories were returned by `search_memories`/`get_memories` and shown in Claude Code, Cursor, Codex, and Antigravity. The fix stores the list directly (`metadata["files_touched"] = files[:20]`) so the body is encoded exactly once. New `tests/test_capture_session_summary.py` asserts the posted body contains a JSON array and no escaped-string artifact. + +### Changed + +- **Auto-injected memory context is now reranked by default (`scripts/_search.py`, `scripts/file_context.py`, `scripts/on_bash_output.sh`, `scripts/on_user_prompt.sh`):** the REST search endpoint does not rerank when `rerank` is omitted, so hook-injected context (file-context, bash-error lookup, session-resume prefetch) was ordered by raw vector similarity and the single most relevant memory could fall outside the injected `top_k` window. A new `should_rerank()` helper turns reranking on for every auto-injection path; the extra ~150–200 ms stays within the hook's curl budget. Opt out with `MEM0_RERANK=0` (also accepts `false`/`no`/`off`). (#5690) + ## 0.2.10 — Accurate per-editor telemetry attribution ### Fixed diff --git a/integrations/mem0-plugin/plugin.json b/integrations/mem0-plugin/plugin.json index ef931be88..a2465f6c4 100644 --- a/integrations/mem0-plugin/plugin.json +++ b/integrations/mem0-plugin/plugin.json @@ -1,7 +1,7 @@ { "id": "mem0", "name": "mem0", - "version": "0.1.2", + "version": "0.1.3", "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", diff --git a/integrations/mem0-plugin/scripts/capture_session_summary.py b/integrations/mem0-plugin/scripts/capture_session_summary.py index 47d0eff19..441ed9a6e 100644 --- a/integrations/mem0-plugin/scripts/capture_session_summary.py +++ b/integrations/mem0-plugin/scripts/capture_session_summary.py @@ -173,7 +173,7 @@ def store_summary( if branch: metadata["branch"] = branch if files: - metadata["files_touched"] = json.dumps(files[:20]) + metadata["files_touched"] = files[:20] body = { "messages": [{"role": "user", "content": summary_prompt}], diff --git a/integrations/mem0-plugin/tests/test_capture_session_summary.py b/integrations/mem0-plugin/tests/test_capture_session_summary.py new file mode 100644 index 000000000..7ce1f1458 --- /dev/null +++ b/integrations/mem0-plugin/tests/test_capture_session_summary.py @@ -0,0 +1,79 @@ +"""Regression tests for capture_session_summary.py request body construction. + +Guards against the double-JSON-encoding bug where ``files_touched`` was stored +as a pre-serialized JSON string and then encoded a second time with the rest of +the request body — surfacing as escaped, slash-heavy blobs in the memories shown +inside Claude Code / Cursor / Codex / Antigravity (all four editors share this +script). +""" + +from __future__ import annotations + +import json + + +class _FakeResp: + status = 200 + + def __enter__(self): + return self + + def __exit__(self, *_): + return False + + +def _capture_request_body(monkeypatch): + """Patch urlopen so store_summary posts nowhere; capture the request body.""" + import capture_session_summary as css + + captured: dict = {} + + def fake_urlopen(req, timeout=0): + captured["raw"] = req.data.decode("utf-8") + captured["body"] = json.loads(captured["raw"]) + return _FakeResp() + + monkeypatch.setattr(css.urllib.request, "urlopen", fake_urlopen) + return captured, css + + +def test_files_touched_is_json_array_not_double_encoded(monkeypatch): + """files_touched must be a real JSON array, encoded exactly once.""" + captured, css = _capture_request_body(monkeypatch) + files = ["mem0/memory/main.py", "src/client/index.ts"] + + css.store_summary( + api_key="test-key", + summary_prompt="did some work", + user_id="u1", + session_id="s1", + project_id="p1", + branch="main", + files=files, + ) + + files_touched = captured["body"]["metadata"]["files_touched"] + assert isinstance(files_touched, list), ( + "files_touched must be a JSON array, not a double-encoded string; " + f"got {type(files_touched).__name__}: {files_touched!r}" + ) + assert files_touched == files + # The file paths must not appear as an escaped JSON string inside the body. + assert '\\"' not in captured["raw"] + + +def test_files_touched_omitted_when_no_files(monkeypatch): + """No files touched -> no files_touched key (unchanged behaviour).""" + captured, css = _capture_request_body(monkeypatch) + + css.store_summary( + api_key="test-key", + summary_prompt="did some work", + user_id="u1", + session_id="s1", + project_id="p1", + branch="main", + files=[], + ) + + assert "files_touched" not in captured["body"]["metadata"]