diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 04b4be2cb..ffd139b12 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.3" + "version": "0.2.4" } ] } diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index c0bc6ea43..68a8d34a5 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.3" + "version": "0.2.4" } ] } diff --git a/docs/integrations/claude-code.mdx b/docs/integrations/claude-code.mdx index 5a1bafe4f..be78a3375 100644 --- a/docs/integrations/claude-code.mdx +++ b/docs/integrations/claude-code.mdx @@ -99,6 +99,39 @@ Add to your Claude Code MCP config (`.mcp.json`): Start a new session and ask: *"List my mem0 entities"* or *"Search my memories for hello"*. If the `mem0` tools appear and respond, you're all set. +## Post-Installation: Run `/mem0:onboard` + +After installing the plugin, start a new Claude Code session and run: + +``` +/mem0:onboard +``` + +This runs the setup wizard which: +1. Verifies your API key and MCP connection +2. Detects and imports project files (`CLAUDE.md`, `AGENTS.md`, `.cursorrules`) +3. Installs coding-optimized memory categories +4. Shows your identity (user ID, project scope, branch) + +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 | diff --git a/mem0-plugin/.claude-plugin/plugin.json b/mem0-plugin/.claude-plugin/plugin.json index a398e1758..68da6ebc6 100644 --- a/mem0-plugin/.claude-plugin/plugin.json +++ b/mem0-plugin/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mem0", - "version": "0.2.3", + "version": "0.2.4", "description": "Persistent memory for Claude Code. Remembers decisions, patterns, and preferences across sessions.", "author": { "name": "Mem0", diff --git a/mem0-plugin/.codex-plugin/plugin.json b/mem0-plugin/.codex-plugin/plugin.json index 3d25dbbeb..e7191d879 100644 --- a/mem0-plugin/.codex-plugin/plugin.json +++ b/mem0-plugin/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mem0", - "version": "0.2.3", + "version": "0.2.4", "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Codex workflows using the Mem0 Platform MCP server.", "author": { "name": "Mem0", diff --git a/mem0-plugin/.cursor-plugin/plugin.json b/mem0-plugin/.cursor-plugin/plugin.json index debbd32c9..43a7bfd0e 100644 --- a/mem0-plugin/.cursor-plugin/plugin.json +++ b/mem0-plugin/.cursor-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mem0", - "version": "0.2.3", + "version": "0.2.4", "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/CHANGELOG.md b/mem0-plugin/CHANGELOG.md index ddf8fc1da..eb3a1559b 100644 --- a/mem0-plugin/CHANGELOG.md +++ b/mem0-plugin/CHANGELOG.md @@ -2,6 +2,34 @@ All notable changes to the Mem0 plugin will be documented in this file. +## 0.2.4 + +### Fixed + +- **PostToolUse matcher mismatch (`hooks.json:81`):** Changed from `mcp__mem0__` to `mcp__mem0__|mcp__plugin_mem0_mem0__`. Root cause of session stats recording nothing — 146 consecutive "no memory operations" entries. Unblocks `/mem0:stats` session line, stop-hook report, and `session_stats.py` tracking. +- **File-read hook noise (`on_file_read.sh`):** Replaced bare-filename semantic search with `metadata.files` filter + score threshold (≥ 0.4). Falls back to basename search if metadata filter returns nothing. Eliminates irrelevant context injection on every Read. +- **`/mem0:pin` uses v3-removed `immutable=True`:** Removed Step 3a entirely. Standardized on `metadata.pinned: true` (which `dream/SKILL.md` already respects during pruning). Fixed unconditional `...` appended to short pin confirmations. +- **`/mem0:list-projects` undercounts (implicit null scoping):** Dual-query approach — runs both null-scoped and app-scoped `get_memories` calls, merges by ID. Handles legacy `metadata.project_id` and `metadata.project` fields. +- **`/mem0:peek` free-text search on memory IDs:** Detects bare hex IDs (`^[a-f0-9]{8}$`) and `[mem0:]` citation refs, routes to `get_memory` direct lookup instead of semantic search. +- **Dead `PostToolUseFailure` hook block:** Removed entirely — this hook event does not exist in Claude Code. + +### Added + +- **Session ID capture (`on_session_start.sh`):** Extracts `session_id` from Claude Code stdin JSON, persists to `/tmp/mem0_session_id_$USER`. Falls back to timestamp-based ID. Enables `run_id`-based session scoping. +- **`run_id` injection (`enforce_metadata_defaults.sh`):** Reads session ID from temp file, injects as `run_id` into every `add_memory` call. Tags all memories with session identity for entity-scoped filtering. +- **`min_score`, `metadata_filters`, `rerank`, `threshold` params (`_search.py`):** `search_memories()` now accepts `min_score: float` to filter low-relevance results, `metadata_filters: dict` for field-level filtering, `rerank: bool` for managed reranker, and `threshold: float` (default 0.3, up from platform default 0.1) for server-side relevance gating. Existing callers unaffected (new params have defaults). +- **Rerank on tour/peek:** `/mem0:tour` and `/mem0:peek` search calls now pass `rerank=true` for better result ordering (+150–200ms latency, significantly improved precision). +- **`/mem0:stats` session query via `run_id`:** Queries memories by `run_id` filter for API-backed session counts. Cross-checks against local stats file. Shows truncated session ID in output. + +### Removed + +- **`/mem0:protocol` skill:** Routing table fully superseded by individual skill descriptions (auto-trigger). Operational guidelines (search patterns, metadata rules) covered by `enforce_metadata_defaults.sh` hook and individual skill bodies. + +### Changed + +- **All 17 skill descriptions:** Rewritten per Claude skill best practices — each now includes what the skill does AND when to trigger it, with specific keywords for auto-discovery. Average length 200–270 chars (under 1024 max). Third person, action verbs. +- **Onboarding auto-trigger (`on_session_start.sh`):** Replaced 3-state marker-file logic with memory-count detection. New project (0 memories) → prompts Claude to invoke `/mem0:onboard`. No marker files, no OAuth state. Simplified no-API-key path to single inactive banner. + ## 0.2.3 ### Added @@ -32,7 +60,7 @@ All notable changes to the Mem0 plugin will be documented in this file. - **Compact prompts:** `on_task_completed.sh` and `on_stop.sh` replaced multi-step checklists with single-line directives (0–2 durable facts max). - **`/mem0:dream`:** Removed `--forget` (now standalone `forget` skill) and `--schedule` flags. - **`/mem0:health`:** Removed `--fix` auto-fix mode; output condensed to `PASS/FAIL CheckName Detail` one-liners. -- **`/mem0:protocol` (was `mem0-mcp`):** Added 14-entry natural-language-to-skill routing table. +- **`/mem0:protocol` (was `mem0-mcp`):** Added 14-entry natural-language-to-skill routing table. _(Removed in 0.2.4 — superseded by skill descriptions.)_ - **CLI config fallback removed:** `_identity.sh`/`_identity.py` no longer read `~/.mem0/config.json`; API key resolution is env-var-only. - **`auto_import.py`:** Added content-hash deduplication to skip files with identical content within a single import run. - **All skill descriptions:** Shortened to concise one-liners. diff --git a/mem0-plugin/README.md b/mem0-plugin/README.md index 9e28aa507..69b5788da 100644 --- a/mem0-plugin/README.md +++ b/mem0-plugin/README.md @@ -147,13 +147,50 @@ 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. +## Post-Installation: Run `/mem0:onboard` + +After installing, start a new session and run: + +``` +/mem0:onboard +``` + +This runs the setup wizard which: +1. Verifies your API key and MCP connection +2. Detects and imports project files (`CLAUDE.md`, `AGENTS.md`, `.cursorrules`) +3. Installs coding-optimized memory categories +4. Shows your identity (user ID, project scope, branch) + +The onboarding is idempotent — safe to re-run anytime. On first session in a new project (0 memories), Claude is prompted to run it automatically. + ## Verify it works -After installing, confirm the MCP server is connected: +After onboarding, confirm everything is connected: -1. Start a new session (or restart your current one) -2. Ask: *"List my mem0 entities"* or *"Search my memories for hello"* -3. If the `mem0` tools appear and respond, you're all set +1. Run `/mem0:health` to check connectivity +2. Run `/mem0:stats` to see memory counts +3. Try `/mem0:remember "we use TypeScript"` then `/mem0:tour` to see it stored + +## 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 | +| `/mem0:list-projects` | List all projects with stored memories | +| `/mem0:switch-project` | Override auto-detected project scope | +| `/mem0:memory-reviewer` | Audit memory quality — duplicates, contradictions, stale | +| `/mem0:context-loader` | Pre-load relevant memories for current task | ## What's included @@ -162,12 +199,10 @@ After installing, confirm the MCP server is connected: | MCP Server | Yes | Yes | Yes | Yes | Yes | | Lifecycle Hooks | Yes | Yes | No | Opt-in | No | | Mem0 SDK Skill | Yes | Yes | No | Yes | No | -| Memory Protocol Skill | No | No | 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`. - **Mem0 SDK Skill** — Guides the AI on how to integrate the Mem0 SDK (Python & TypeScript) into your applications. -- **Memory Protocol Skill** — Codex-specific skill that instructs the agent to retrieve relevant memories at task start, store learnings on completion, and capture session state before context loss. Complements the lifecycle hooks on Codex. ## Updating the plugin diff --git a/mem0-plugin/hooks/hooks.json b/mem0-plugin/hooks/hooks.json index 6571d3c4b..689e8d82d 100644 --- a/mem0-plugin/hooks/hooks.json +++ b/mem0-plugin/hooks/hooks.json @@ -78,7 +78,7 @@ ], "PostToolUse": [ { - "matcher": "mcp__mem0__", + "matcher": "mcp__mem0__|mcp__plugin_mem0_mem0__", "hooks": [ { "type": "command", @@ -148,18 +148,6 @@ ] } ], - "PostToolUseFailure": [ - { - "matcher": "mcp__mem0__", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_tool_failure.sh", - "timeout": 5 - } - ] - } - ], "PostCompact": [ { "matcher": "manual|auto", diff --git a/mem0-plugin/scripts/_identity.sh b/mem0-plugin/scripts/_identity.sh index 66199dbb1..1b46ffa77 100644 --- a/mem0-plugin/scripts/_identity.sh +++ b/mem0-plugin/scripts/_identity.sh @@ -30,6 +30,12 @@ _mem0_resolve_identity() { MEM0_RESOLVED_USER_ID="$(_mem0_resolve_identity)" export MEM0_RESOLVED_USER_ID +_MEM0_IDENTITY_ANNOTATION="" +if [ -n "${MEM0_USER_ID:-}" ] && [ "$MEM0_USER_ID" != "${USER:-default}" ]; then + _MEM0_IDENTITY_ANNOTATION=" (override; default: ${USER:-default})" +fi +export _MEM0_IDENTITY_ANNOTATION + # Load settings from ~/.mem0/settings.json if command -v python3 >/dev/null 2>&1; then _SETTINGS_JSON=$(PYTHONPATH="$_SCRIPT_DIR" python3 -c "from load_settings import load_settings; import json; print(json.dumps(load_settings()))" 2>/dev/null || echo "{}") diff --git a/mem0-plugin/scripts/_project.sh b/mem0-plugin/scripts/_project.sh index f08c58197..c4419ac3b 100755 --- a/mem0-plugin/scripts/_project.sh +++ b/mem0-plugin/scripts/_project.sh @@ -53,6 +53,12 @@ _mem0_resolve_project_id() { _mem0_slug="${_mem0_slug//:/-}" if [ -n "$_mem0_slug" ]; then printf '%s' "$_mem0_slug" + _MEM0_PERSIST_CWD="$PWD" _MEM0_PERSIST_SLUG="$_mem0_slug" python3 -c " +import os, sys +sys.path.insert(0, '$(dirname "${BASH_SOURCE[0]:-$0}")') +from _project import save_project_mapping +save_project_mapping(os.environ['_MEM0_PERSIST_CWD'], os.environ['_MEM0_PERSIST_SLUG']) +" 2>/dev/null || true return fi fi diff --git a/mem0-plugin/scripts/_search.py b/mem0-plugin/scripts/_search.py index 51d54f5f6..54939c33f 100644 --- a/mem0-plugin/scripts/_search.py +++ b/mem0-plugin/scripts/_search.py @@ -19,7 +19,11 @@ def search_memories( project_id: str, query: str, metadata_type: str | None = None, + metadata_filters: dict | None = None, top_k: int = 3, + min_score: float = 0.0, + rerank: bool = False, + threshold: float = 0.3, ) -> list[dict]: if not api_key: return [] @@ -27,8 +31,14 @@ def search_memories( filters: dict = {"AND": [{"user_id": user_id}, {"app_id": project_id}]} if metadata_type: filters["AND"].append({"metadata": {"type": metadata_type}}) + if metadata_filters: + for key, value in metadata_filters.items(): + filters["AND"].append({"metadata": {key: value}}) - body = json.dumps({"query": query, "filters": filters, "top_k": top_k}).encode() + payload: dict = {"query": query, "filters": filters, "top_k": top_k, "threshold": threshold} + if rerank: + payload["rerank"] = True + body = json.dumps(payload).encode() req = urllib.request.Request( SEARCH_URL, data=body, @@ -38,9 +48,10 @@ def search_memories( try: with urllib.request.urlopen(req, timeout=SEARCH_TIMEOUT) as r: data = json.loads(r.read()) - if isinstance(data, list): - return data - return data.get("results", []) + results = data if isinstance(data, list) else data.get("results", []) + if min_score > 0: + results = [m for m in results if m.get("score", 0) >= min_score] + return results except Exception: return [] diff --git a/mem0-plugin/scripts/auto_import.py b/mem0-plugin/scripts/auto_import.py index 8245f0e4f..ad204e6e2 100644 --- a/mem0-plugin/scripts/auto_import.py +++ b/mem0-plugin/scripts/auto_import.py @@ -23,7 +23,7 @@ import urllib.request sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from _chunking import filter_and_truncate, split_by_headers from _identity import resolve_api_key, resolve_user_id -from _project import resolve_branch, resolve_project_id +from _project import resolve_branch, resolve_project_id, save_project_mapping log = logging.getLogger("mem0-auto-import") log.setLevel(logging.DEBUG) @@ -93,6 +93,38 @@ def save_hashes(hashes: dict[str, str]) -> None: log.warning("Could not save hash store: %s", e) +def already_imported(api_key: str, user_id: str, project_id: str, filename: str) -> bool: + body = json.dumps({ + "query": filename, + "filters": { + "AND": [ + {"user_id": user_id}, + {"app_id": project_id}, + {"metadata": {"source": "auto-import"}}, + ] + }, + "top_k": 3, + "threshold": 0.0, + }).encode() + req = urllib.request.Request( + f"{API_URL}/v3/memories/search/", + data=body, + headers={"Content-Type": "application/json", "Authorization": f"Token {api_key}"}, + method="POST", + ) + try: + with urllib.request.urlopen(req, timeout=5) as r: + data = json.loads(r.read()) + results = data if isinstance(data, list) else data.get("results", []) + for result in results: + meta = result.get("metadata", {}) if isinstance(result, dict) else {} + if filename in meta.get("file", ""): + return True + return False + except Exception: + return False + + def post_memory(api_key: str, content: str, user_id: str, filename: str, project_id: str, branch: str = "") -> bool: """POST a project profile memory to the Mem0 REST API.""" metadata = { @@ -149,6 +181,8 @@ def main() -> None: project_id = resolve_project_id(cwd) branch = resolve_branch(cwd) + save_project_mapping(cwd, project_id) + git_root = _git_root(cwd) search_dirs = [cwd] if git_root and os.path.realpath(git_root) != os.path.realpath(cwd): @@ -172,6 +206,8 @@ def main() -> None: log.debug("Not found, skipping: %s", filename) continue + filepath = os.path.realpath(filepath) + try: file_size = os.path.getsize(filepath) except OSError: @@ -198,6 +234,12 @@ def main() -> None: log.debug("Unchanged, skipping: %s", filename) continue + if already_imported(api_key, user_id, project_id, filename): + log.debug("Already in mem0, updating hash store: %s", filename) + hashes[hash_key] = current_hash + updated = True + continue + try: with open(filepath, encoding="utf-8", errors="replace") as f: content = f.read() diff --git a/mem0-plugin/scripts/block_memory_write.sh b/mem0-plugin/scripts/block_memory_write.sh index bb38686d6..1b3802663 100755 --- a/mem0-plugin/scripts/block_memory_write.sh +++ b/mem0-plugin/scripts/block_memory_write.sh @@ -26,7 +26,7 @@ if [ -z "$FILE_PATH" ]; then fi case "$FILE_PATH" in - */MEMORY.md|*/.claude/memory/*) + */.claude/*/MEMORY.md|*/.claude/memory/*) echo "BLOCKED: Do not write to $FILE_PATH. Use the mem0 MCP \`add_memory\` tool instead to persist memories. This project uses mem0 for all memory storage." >&2 exit 2 ;; diff --git a/mem0-plugin/scripts/enforce_metadata_defaults.sh b/mem0-plugin/scripts/enforce_metadata_defaults.sh index 4556f1c1e..7f196e104 100755 --- a/mem0-plugin/scripts/enforce_metadata_defaults.sh +++ b/mem0-plugin/scripts/enforce_metadata_defaults.sh @@ -21,43 +21,56 @@ esac TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input // "{}"' 2>/dev/null) -PATCHED=$(python3 -c " -import json, sys +_PATCH_OUT="/tmp/mem0_enforce_$$" +_MEM0_TOOL_INPUT="$TOOL_INPUT" python3 <<'PYEOF' > "$_PATCH_OUT" 2>/dev/null || true +import json, os, sys -raw = sys.stdin.read() +raw = os.environ.get("_MEM0_TOOL_INPUT", "{}") try: inp = json.loads(raw) except Exception: sys.exit(0) -meta = inp.get('metadata') or {} +meta = inp.get("metadata") or {} changed = False -if 'confidence' not in meta: - meta['confidence'] = 0.7 +if "confidence" not in meta: + meta["confidence"] = 0.7 changed = True -if 'files' not in meta: - meta['files'] = ['*'] +if "files" not in meta: + meta["files"] = ["*"] changed = True -if 'source' not in meta: - meta['source'] = 'auto_capture' +if "source" not in meta: + meta["source"] = "auto_capture" changed = True -if 'type' not in meta: - meta['type'] = 'task_learning' +if "type" not in meta: + meta["type"] = "task_learning" changed = True -# If confidence is 1.0 (user explicitly stated), ensure infer=False -if meta.get('confidence', 0) >= 1.0 and 'infer' not in inp: - inp['infer'] = False +if meta.get("confidence", 0) >= 1.0 and "infer" not in inp: + inp["infer"] = False changed = True +if "run_id" not in inp: + session_file = "/tmp/mem0_session_id_" + os.environ.get("USER", "default") + if os.path.isfile(session_file): + try: + with open(session_file) as f: + sid = f.read().strip() + if sid: + inp["run_id"] = sid + changed = True + except OSError: + pass + if changed: - inp['metadata'] = meta + inp["metadata"] = meta print(json.dumps(inp)) -" <<< "$TOOL_INPUT" 2>/dev/null || true) +PYEOF +PATCHED=$(cat "$_PATCH_OUT" 2>/dev/null) +rm -f "$_PATCH_OUT" -if [ -n "$PATCHED" ]; then - # Use hookSpecificOutput.updatedInput to actually modify the tool call +if [ -n "$PATCHED" ] && echo "$PATCHED" | jq empty 2>/dev/null; then jq -n --argjson updated "$PATCHED" '{ "hookSpecificOutput": { "hookEventName": "PreToolUse", diff --git a/mem0-plugin/scripts/on_file_read.sh b/mem0-plugin/scripts/on_file_read.sh index de6e0526c..b53f3f06f 100755 --- a/mem0-plugin/scripts/on_file_read.sh +++ b/mem0-plugin/scripts/on_file_read.sh @@ -31,7 +31,7 @@ fi # Skip repeated reads: track last 10 files in a temp file. # Use full FILE_PATH (not basename) to avoid false dedup. -RECENT_FILE="/tmp/mem0_recent_reads_${USER}" +RECENT_FILE="/tmp/mem0_recent_reads_${USER}_${MEM0_PROJECT_ID:-unknown}" if [ -f "$RECENT_FILE" ] && grep -qxF "$FILE_PATH" "$RECENT_FILE" 2>/dev/null; then exit 0 fi @@ -42,8 +42,13 @@ USER_ID="${MEM0_RESOLVED_USER_ID:-${USER:-default}}" PROJECT_ID="${MEM0_PROJECT_ID:-unknown}" BASENAME=$(basename "$FILE_PATH") -# SECURITY: pass data via env vars, never interpolate into python3 -c -CONTEXT=$(PYTHONPATH="$SCRIPT_DIR" MEM0_SEARCH_USER="$USER_ID" MEM0_SEARCH_PROJECT="$PROJECT_ID" MEM0_SEARCH_QUERY="$BASENAME" python3 -c " +CWD="${MEM0_CWD:-$(pwd)}" +REL_PATH="${FILE_PATH#$CWD/}" +if [ "$REL_PATH" = "$FILE_PATH" ]; then + REL_PATH="$BASENAME" +fi + +CONTEXT=$(PYTHONPATH="$SCRIPT_DIR" MEM0_SEARCH_USER="$USER_ID" MEM0_SEARCH_PROJECT="$PROJECT_ID" MEM0_SEARCH_QUERY="$BASENAME" MEM0_SEARCH_RELPATH="$REL_PATH" python3 -c " import os, sys sys.path.insert(0, os.environ.get('PYTHONPATH', '.')) from _search import search_memories, format_results_for_context @@ -52,8 +57,18 @@ api_key = os.environ.get('MEM0_API_KEY', '') user_id = os.environ.get('MEM0_SEARCH_USER', 'default') project_id = os.environ.get('MEM0_SEARCH_PROJECT', 'unknown') filename = os.environ.get('MEM0_SEARCH_QUERY', '') +relpath = os.environ.get('MEM0_SEARCH_RELPATH', '') -results = search_memories(api_key, user_id, project_id, filename, top_k=3) +results = search_memories( + api_key, user_id, project_id, filename, + metadata_filters={'files': {'contains': relpath}} if relpath else None, + top_k=3, min_score=0.4, +) +if not results and relpath: + results = search_memories( + api_key, user_id, project_id, filename, + top_k=3, min_score=0.4, + ) if results: print(format_results_for_context(results, heading=f'mem0 context for {filename}')) " 2>/dev/null || true) diff --git a/mem0-plugin/scripts/on_session_end.sh b/mem0-plugin/scripts/on_session_end.sh index f4ed6ed5d..3f06440aa 100755 --- a/mem0-plugin/scripts/on_session_end.sh +++ b/mem0-plugin/scripts/on_session_end.sh @@ -1,13 +1,4 @@ #!/usr/bin/env bash -# Hook: SessionEnd -# -# Fires when session actually terminates (after Stop). -# Last-chance capture: if on_stop.sh background REST call didn't complete, -# this fires a synchronous capture attempt. -# -# Input: JSON on stdin with session_id, transcript_path, cwd, reason -# Output: ignored (session is ending) - set -uo pipefail if [ -n "${MEM0_DEBUG:-}" ]; then @@ -18,25 +9,20 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" INPUT=$(cat) REASON=$(echo "$INPUT" | jq -r '.reason // "other"' 2>/dev/null || echo "other") -SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""' 2>/dev/null || echo "") -# Print session-end report (last chance — Stop hook output may not render on /exit) REPORT=$(python3 "$SCRIPT_DIR/session_stats.py" report 2>/dev/null || echo "") if [ -n "$REPORT" ] && [ "$REPORT" != "Session: no memory operations." ]; then - echo "" - echo "---" - echo "mem0 $REPORT" - echo "---" - - # Append to persistent session log mkdir -p "$HOME/.mem0" 2>/dev/null || true - echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | $REPORT" >> "$HOME/.mem0/session-log.md" 2>/dev/null || true + _LOG_FILE="$HOME/.mem0/session-log.md" + echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | $REPORT" >> "$_LOG_FILE" 2>/dev/null || true + _LINE_COUNT=$(wc -l < "$_LOG_FILE" 2>/dev/null || echo 0) + if [ "$_LINE_COUNT" -gt 500 ]; then + tail -500 "$_LOG_FILE" > "${_LOG_FILE}.tmp" 2>/dev/null && mv "${_LOG_FILE}.tmp" "$_LOG_FILE" 2>/dev/null || true + fi fi -# Telemetry (fire-and-forget — session dying, best-effort) python3 "$SCRIPT_DIR/telemetry.py" session_end --reason="$REASON" 2>/dev/null & -# Clean up old capture markers (> 7 days) find "$HOME/.mem0" -name ".captured_*" -mtime +7 -delete 2>/dev/null || true exit 0 diff --git a/mem0-plugin/scripts/on_session_start.sh b/mem0-plugin/scripts/on_session_start.sh index d44cc2f6c..df1309283 100755 --- a/mem0-plugin/scripts/on_session_start.sh +++ b/mem0-plugin/scripts/on_session_start.sh @@ -1,14 +1,4 @@ #!/usr/bin/env bash -# Hook: SessionStart (matcher: startup|resume|compact) -# -# Bootstraps mem0 context at the start of every session. -# Output becomes part of Claude's context so it calls mem0 MCP tools. -# -# Input: JSON on stdin with session_id, source, transcript_path, model, cwd -# Output: Text injected into Claude's context (exit 0) - -# Intentionally omit -e so the script always outputs a bootstrap prompt -# even if jq is missing or stdin is malformed. set -uo pipefail if [ -n "${MEM0_DEBUG:-}" ]; then @@ -16,89 +6,52 @@ if [ -n "${MEM0_DEBUG:-}" ]; then fi SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -# shellcheck source=_identity.sh . "$SCRIPT_DIR/_identity.sh" -# Initialize session stats tracker and settings file -python3 "$SCRIPT_DIR/session_stats.py" init 2>/dev/null || true -PYTHONPATH="$SCRIPT_DIR" python3 "$SCRIPT_DIR/load_settings.py" init 2>/dev/null || true -rm -f "/tmp/mem0_rubric_injected_${USER}" 2>/dev/null || true -rm -f /tmp/mem0_rubric_* 2>/dev/null || true -rm -f "/tmp/mem0_recent_reads_${USER}" 2>/dev/null || true - INPUT=$(cat) SOURCE=$(echo "$INPUT" | jq -r '.source // "startup"' 2>/dev/null || echo "startup") -MEM0_AUTH_MODE="api_key" +if [ "$SOURCE" = "startup" ]; then + python3 "$SCRIPT_DIR/session_stats.py" init 2>/dev/null || true + rm -f /tmp/mem0_recent_reads_${USER}_* 2>/dev/null || true +fi +PYTHONPATH="$SCRIPT_DIR" python3 "$SCRIPT_DIR/load_settings.py" init 2>/dev/null || true +rm -f "/tmp/mem0_rubric_injected_${USER}" 2>/dev/null || true +rm -f /tmp/mem0_rubric_* 2>/dev/null || true +MEM0_SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""' 2>/dev/null || echo "") +if [ -z "$MEM0_SESSION_ID" ]; then + MEM0_SESSION_ID="ses_$(date +%s)_$$" +fi +printf '%s' "$MEM0_SESSION_ID" > "/tmp/mem0_session_id_${USER}" +export MEM0_SESSION_ID + if [ -z "${MEM0_API_KEY:-}" ]; then - _SAFE_PID=$(printf '%s' "${MEM0_PROJECT_ID:-unknown}" | tr '/:' '--') - _ONBOARD_MARKER="$HOME/.mem0/.onboarded_${_SAFE_PID}" - - if [ ! -f "$_ONBOARD_MARKER" ] && [ "${SOURCE:-startup}" = "startup" ]; then - # State 1: First run — trigger onboarding - cat </dev/null 2>&1; then +if command -v python3 >/dev/null 2>&1; then MEM0_COUNT=$(python3 -c " import json, os, urllib.request, urllib.error api_key = os.environ.get('MEM0_API_KEY', '') @@ -125,19 +78,15 @@ except Exception: " 2>/dev/null || echo "?") fi -# Identity line is emitted before every bootstrap variant so the agent -# uses the same user_id the hooks resolved. Without this, the agent's -# search_memories/add_memory MCP calls may bind to a different bucket -# than what the hooks write to. cat </dev/null || echo ".") if command -v python3 >/dev/null 2>&1; then MEM0_PROJECT_CONFIG=$(python3 "$SCRIPT_DIR/parse_mem0_config.py" --full "$MEM0_CWD_RESOLVED" 2>/dev/null || echo "{}") -fi -if [ -n "$MEM0_PROJECT_CONFIG" ] && [ "$MEM0_PROJECT_CONFIG" != "{}" ]; then - _CONFIG_KEYS=$(echo "$MEM0_PROJECT_CONFIG" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d))" 2>/dev/null || echo "?") - echo "### Project Config (mem0.md)" - echo "\`mem0.md loaded (${_CONFIG_KEYS} sections configured)\`" - if [ -n "${MEM0_DEBUG:-}" ]; then - echo "\`\`\`json" - echo "$MEM0_PROJECT_CONFIG" - echo "\`\`\`" + if [ -n "$MEM0_PROJECT_CONFIG" ] && [ "$MEM0_PROJECT_CONFIG" != "{}" ]; then + _CONFIG_KEYS=$(echo "$MEM0_PROJECT_CONFIG" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d))" 2>/dev/null || echo "?") + echo "### Project Config (mem0.md)" + echo "\`mem0.md loaded (${_CONFIG_KEYS} sections configured)\`" + if [ -n "${MEM0_DEBUG:-}" ]; then + echo "\`\`\`json" + echo "$MEM0_PROJECT_CONFIG" + echo "\`\`\`" + fi + echo "" fi - echo "" fi if [ "$SOURCE" = "startup" ]; then - # First-run detection: auto-trigger onboarding for new projects - _SAFE_PID=$(printf '%s' "$MEM0_PROJECT_ID" | tr '/:' '--') - _ONBOARD_MARKER="$HOME/.mem0/.onboarded_${_SAFE_PID}" - if [ ! -f "$_ONBOARD_MARKER" ]; then + if [ "$MEM0_COUNT" = "0" ]; then cat <<'EOF' -IMPORTANT: This is the first run for this project. You MUST invoke the `mem0:onboard` skill now using the Skill tool BEFORE responding to the user. Do not just suggest it — actually call it. This runs the setup wizard (API check, file import, coding categories). +This is a new project with 0 memories. You MUST invoke the `mem0:onboard` skill now using the Skill tool to import project files and install coding categories. Do not ask the user — just invoke it immediately before responding. EOF else cat <<'EOF' @@ -178,7 +122,6 @@ Search mem0 for recent decisions and task learnings before responding to the use EOF fi - # Detect native Claude Code auto-memory for THIS project _PROJ_KEY=$(printf '%s' "$MEM0_CWD_RESOLVED" | tr '/' '-') _MEMORY_MD="$HOME/.claude/projects/${_PROJ_KEY}/memory/MEMORY.md" if [ -f "$_MEMORY_MD" ] && [ -s "$_MEMORY_MD" ]; then @@ -194,7 +137,6 @@ To avoid two parallel memory systems: MEMEOF fi - # Auto-import declarative project files in background MEM0_CWD="$(echo "$INPUT" | jq -r '.cwd // "."' 2>/dev/null || echo ".")" \ python3 "$SCRIPT_DIR/auto_import.py" 2>/dev/null & @@ -209,7 +151,6 @@ Context compacted. Search mem0 for `session_state` and `decision` memories to re EOF fi -# Telemetry (background, fire-and-forget) python3 "$SCRIPT_DIR/telemetry.py" session_start --source="$SOURCE" --memory_count="${MEM0_COUNT:-0}" 2>/dev/null & exit 0 diff --git a/mem0-plugin/scripts/on_stop.sh b/mem0-plugin/scripts/on_stop.sh index de18ebff3..a596a2100 100755 --- a/mem0-plugin/scripts/on_stop.sh +++ b/mem0-plugin/scripts/on_stop.sh @@ -1,16 +1,4 @@ #!/usr/bin/env bash -# Hook: Stop -# -# Fires when Claude finishes responding. -# Reminds Claude to store any unsaved learnings, then spawns a background -# process to capture transcript state via the Mem0 REST API directly. -# -# Input: JSON on stdin with stop_hook_active, transcript_path, cwd -# Output: Text that becomes Claude's context (exit 0), or nothing -# -# IMPORTANT: Check stop_hook_active to avoid infinite loops. - -# Intentionally omit -e so the reminder always emits even if session_stats fails. set -uo pipefail if [ -n "${MEM0_DEBUG:-}" ]; then @@ -26,30 +14,11 @@ if [ "$STOP_HOOK_ACTIVE" = "true" ]; then exit 0 fi -# Telemetry: fire before report() deletes stats file _TELEM_CAT=$(python3 "$SCRIPT_DIR/session_stats.py" peek 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('categories',[])))" 2>/dev/null || echo "0") python3 "$SCRIPT_DIR/telemetry.py" stop --categories_count="$_TELEM_CAT" 2>/dev/null & -# Print session-end report -REPORT=$(python3 "$SCRIPT_DIR/session_stats.py" report 2>/dev/null || echo "") -if [ -n "$REPORT" ]; then - echo "" - echo "---" - echo "**mem0 $REPORT**" - echo "---" - echo "" -fi - -# Append to persistent session log -if [ -n "$REPORT" ]; then - mkdir -p "$HOME/.mem0" 2>/dev/null || true - echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | $REPORT" >> "$HOME/.mem0/session-log.md" 2>/dev/null || true -fi - cat <<'EOF' Store 0-2 durable facts from this turn via `add_memory` — only decisions, anti-patterns, or conventions that would help a future agent. Skip if nothing new was learned. EOF -SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""' 2>/dev/null || echo "") - exit 0 diff --git a/mem0-plugin/scripts/session_stats.py b/mem0-plugin/scripts/session_stats.py index 71c857bc1..357fe4dd6 100644 --- a/mem0-plugin/scripts/session_stats.py +++ b/mem0-plugin/scripts/session_stats.py @@ -90,12 +90,6 @@ def report() -> str: searches = stats.get("searches", 0) categories = stats.get("categories", []) - # Clean up temp file after reading - try: - os.unlink(STATS_FILE) - except OSError: - pass - if adds == 0 and searches == 0: return "" diff --git a/mem0-plugin/skills/context-loader/SKILL.md b/mem0-plugin/skills/context-loader/SKILL.md index ef1ce220e..9e136e393 100644 --- a/mem0-plugin/skills/context-loader/SKILL.md +++ b/mem0-plugin/skills/context-loader/SKILL.md @@ -1,6 +1,6 @@ --- name: context-loader -description: Pre-load relevant memories for current task +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 @@ -9,7 +9,7 @@ Pre-fetches relevant memories to prime context before working on a task. ## When to use -- Session start (auto-triggered by `on_session_start.sh` for onboarded projects) +- 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" diff --git a/mem0-plugin/skills/dream/SKILL.md b/mem0-plugin/skills/dream/SKILL.md index c0938ae53..7c6b7a7e7 100644 --- a/mem0-plugin/skills/dream/SKILL.md +++ b/mem0-plugin/skills/dream/SKILL.md @@ -1,6 +1,6 @@ --- name: dream -description: Consolidate memories — merge duplicates, resolve contradictions, prune stale +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 diff --git a/mem0-plugin/skills/export/SKILL.md b/mem0-plugin/skills/export/SKILL.md index e19b0db81..9a3f9074d 100644 --- a/mem0-plugin/skills/export/SKILL.md +++ b/mem0-plugin/skills/export/SKILL.md @@ -1,6 +1,6 @@ --- name: export -description: Export project memories to a portable Markdown file +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 diff --git a/mem0-plugin/skills/forget/SKILL.md b/mem0-plugin/skills/forget/SKILL.md index bde9b0f29..2b32d865f 100644 --- a/mem0-plugin/skills/forget/SKILL.md +++ b/mem0-plugin/skills/forget/SKILL.md @@ -1,6 +1,6 @@ --- name: forget -description: Delete memories by search or ID — confirms before deleting +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 diff --git a/mem0-plugin/skills/health/SKILL.md b/mem0-plugin/skills/health/SKILL.md index 8d984e0b6..b1eedf29a 100644 --- a/mem0-plugin/skills/health/SKILL.md +++ b/mem0-plugin/skills/health/SKILL.md @@ -1,6 +1,6 @@ --- name: health -description: Diagnose mem0 connection, API key, and memory read/write +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 diff --git a/mem0-plugin/skills/import/SKILL.md b/mem0-plugin/skills/import/SKILL.md index 1c843cc0a..90c02d375 100644 --- a/mem0-plugin/skills/import/SKILL.md +++ b/mem0-plugin/skills/import/SKILL.md @@ -1,6 +1,6 @@ --- name: import -description: Import memories from an export file into this project +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 diff --git a/mem0-plugin/skills/list-projects/SKILL.md b/mem0-plugin/skills/list-projects/SKILL.md index 01ca996f0..8cdbfee71 100644 --- a/mem0-plugin/skills/list-projects/SKILL.md +++ b/mem0-plugin/skills/list-projects/SKILL.md @@ -1,6 +1,6 @@ --- name: list-projects -description: List all projects that have stored memories +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 @@ -12,23 +12,29 @@ Show all known project scopes for the current user. ### Step 1: Fetch memories to discover app_ids There is no dedicated "list projects" API endpoint. Discover projects by fetching -the user's memories and extracting distinct `app_id` values. +the user's memories across all scopes. -Call `get_memories` with: -- `filters={"user_id": ""}` -- `page_size=200` +**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: -Do NOT pass `app_id` — we want memories across ALL projects. +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` -If the response indicates more pages, paginate until all are fetched (up to 1000 -memories max to avoid excessive API calls). +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, read the `app_id` field (may also appear as `metadata.app_id` -on older memories). Collect distinct values. +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 -For each project, count: +Group by resolved project name. For each project, count: - Total memories - Most recent `created_at` date - Top 3 `metadata.type` values by frequency diff --git a/mem0-plugin/skills/mem0/SKILL.md b/mem0-plugin/skills/mem0/SKILL.md index abc48852e..24cd827f2 100644 --- a/mem0-plugin/skills/mem0/SKILL.md +++ b/mem0-plugin/skills/mem0/SKILL.md @@ -1,6 +1,6 @@ --- name: mem0 -description: Mem0 SDK reference — Python, TypeScript, and integrations +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 diff --git a/mem0-plugin/skills/memory-reviewer/SKILL.md b/mem0-plugin/skills/memory-reviewer/SKILL.md index c820afe46..6e60102bb 100644 --- a/mem0-plugin/skills/memory-reviewer/SKILL.md +++ b/mem0-plugin/skills/memory-reviewer/SKILL.md @@ -1,6 +1,6 @@ --- name: memory-reviewer -description: Review memory quality — duplicates, contradictions, stale entries +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 diff --git a/mem0-plugin/skills/onboard/SKILL.md b/mem0-plugin/skills/onboard/SKILL.md index be647bb45..f67a9f9d6 100644 --- a/mem0-plugin/skills/onboard/SKILL.md +++ b/mem0-plugin/skills/onboard/SKILL.md @@ -1,6 +1,6 @@ --- name: onboard -description: Set up mem0 for this project — API key setup, config, categories, identity +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 @@ -108,6 +108,7 @@ If user says yes (or default): - `app_id=` - `metadata={"type": "project_profile", "file": "", "source": "onboard", "branch": ""}` - `infer=False` +- The response contains `event_id` (writes are async). Do not block on each — continue importing. The summary reflects files submitted, not confirmed processed. ## Step 5: Install coding categories @@ -130,16 +131,7 @@ If the script fails with "mem0ai SDK not found", run the dependency installer fi ``` Then retry the categories script. -## Step 6: Mark project as onboarded - -```bash -_SAFE_PID=$(printf '%s' "" | tr '/:' '--') -mkdir -p ~/.mem0 && touch ~/.mem0/.onboarded_${_SAFE_PID} -``` - -This is silent — no user-facing output needed. - -## Step 7: Summary +## Step 6: Summary Print a summary: ``` diff --git a/mem0-plugin/skills/peek/SKILL.md b/mem0-plugin/skills/peek/SKILL.md index 49215e1cf..ccc6b2a0c 100644 --- a/mem0-plugin/skills/peek/SKILL.md +++ b/mem0-plugin/skills/peek/SKILL.md @@ -1,6 +1,6 @@ --- name: peek -description: Quick search memories — compact one-liner results +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 @@ -15,12 +15,22 @@ 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` -2. Targeted: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}`, `top_k=5` +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 diff --git a/mem0-plugin/skills/pin/SKILL.md b/mem0-plugin/skills/pin/SKILL.md index cde56c1d4..f810ff4eb 100644 --- a/mem0-plugin/skills/pin/SKILL.md +++ b/mem0-plugin/skills/pin/SKILL.md @@ -1,6 +1,6 @@ --- name: pin -description: Pin a memory to protect it from consolidation +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 @@ -29,21 +29,6 @@ Call `get_memory` with the selected memory ID. Store: ### Step 3: Pin it -#### 3a: Pinning a new memory (full protection) - -Use `add_memory` with `immutable: true`: - -```python -add_memory( - messages=[{"role": "user", "content": ""}], - user_id=, - immutable=True, - metadata={"pinned": True}, -) -``` - -#### 3b: Pinning an existing memory - Call `update_memory` with: - `memory_id=` - `text=` (preserve existing content) @@ -56,13 +41,19 @@ update_memory(memory_id=, text=, metadata=updated_me **Important:** `update_memory` requires the `text` parameter. Passing only metadata may error or wipe content. +**For new memories** (user wants to pin text that isn't stored yet): +1. Call `add_memory` with the text + `metadata={"pinned": true, "type": "decision", "confidence": 1.0}` +2. The response contains `event_id`. Call `get_event_status(event_id=)` once to retrieve the memory ID, then confirm. + ### Step 4: Confirm ``` -Pinned: "..." +Pinned: "" Memory ID: ``` +Append `...` only if content exceeds 80 characters. + ### Unpin If the user says "unpin": diff --git a/mem0-plugin/skills/protocol/SKILL.md b/mem0-plugin/skills/protocol/SKILL.md deleted file mode 100644 index a33e759b4..000000000 --- a/mem0-plugin/skills/protocol/SKILL.md +++ /dev/null @@ -1,379 +0,0 @@ ---- -name: protocol -description: Memory protocol — when to search, how to filter, what to store ---- - -# Mem0 MCP Memory Protocol - -You have access to persistent memory via the mem0 MCP tools. Follow this protocol to maintain context across sessions. - -## Natural language → skill routing - -When a user says something in natural language, route to the right mem0 skill automatically: - -| User says | Invoke | -|---|---| -| "remember this", "save this", "store this fact" | `/mem0:remember` | -| "forget this", "delete memory about X", "remove that memory", "undo last write" | `/mem0:forget` | -| "what do we know about X", "quick search", "peek at memories", "context for X" | `/mem0:peek` | -| "show my memories", "browse memories", "memory tour", "list all memories" | `/mem0:tour` | -| "clean up memories", "consolidate", "merge duplicates", "prune" | `/mem0:dream` | -| "export memories", "backup memories", "download memories" | `/mem0:export` | -| "import memories", "load memories from file" | `/mem0:import` | -| "check mem0 health", "is mem0 working", "diagnose mem0" | `/mem0:health` | -| "memory stats", "how many memories", "weekly digest" | `/mem0:stats` | -| "set up mem0", "initialize mem0", "configure mem0" | `/mem0:onboard` | -| "switch project", "change mem0 project" | `/mem0:switch-project` | -| "list projects", "show all projects" | `/mem0:list-projects` | -| "pin this memory", "protect this memory" | `/mem0:pin` | -| "unpin this memory", "unprotect this memory" | `/mem0:pin` | -| "check memory quality", "any duplicates?", "review memories" | `/mem0:memory-reviewer` | - -If unsure, use the MCP tools directly (search/add/delete) without invoking a skill. - -## On every new task - -Decide whether persistent memory context would improve your response, then act accordingly. Don't search by default — search deliberately. - -## Project scoping - -Every memory operation MUST be scoped to the current project using `app_id` (entity-scoped memory): - -- **On `add_memory`:** Always pass `app_id=` as a **top-level parameter** (not in metadata). -- **On `search_memories`:** Always include `{"app_id": ""}` in the AND filter. - -Full filter template: -```python -filters={"AND": [ - {"user_id": ""}, - {"app_id": ""}, - {"metadata": {"type": "decision"}} -]} -``` - -### Decide: search or skip? - -**Search WHEN** the user: -- references past work, decisions, or things "we" built -- asks "how should we...", "best way to...", or any decision-style question -- hits an error, bug, or asks for debugging help -- requests work that touches their stack, tools, conventions, or preferences -- starts a non-trivial task in a known project - -**Skip WHEN:** -- the prompt is an acknowledgement or continuation ("ok", "thanks", "continue") -- the user is *stating* new info — that's a write trigger (`add_memory`), not a search -- it's a pure syntax / factual question answerable from general knowledge -- you already searched this scope earlier in the turn - -Empty results are normal. Proceed without context — they don't mean the system is broken. - -### Contradiction detection at search time - -After receiving search results, scan for contradictions before using them: - -1. If 2+ results address the **same topic** (same `metadata.type`, overlapping file paths or entity names) but assert **opposing facts**, surface BOTH to the user instead of silently picking one. -2. Format: - ``` - Conflicting memories: - - [mem0:] "" (confidence: , ) - - [mem0:] "" (confidence: , ) - Which is current? - ``` -3. After the user resolves: update the loser via `update_memory` to mark it superseded, or delete it. Store the winner's fact as authoritative if not already. -4. If the user doesn't resolve, default to the more recent memory with higher confidence, but note the ambiguity in your response. - -### How to search well - -When you do search, run **2–4 parallel** `search_memories` calls at different angles instead of one query echoing the user's prompt. - -**Query phrasing:** -- Use **nouns**, not sentences. `"auth module decisions"` beats `"what did we decide about auth"`. -- Strip conversational filler. *"remember when we picked Postgres?"* → search `"Postgres choice"`. -- Use entity names, not pronouns. Resolve "that thing" from recent context first. -- Don't search on meta-questions ("what was that?") — use recent context or `get_memories` ordered by `created_at`. - -**Metadata filters** match the same `type` values written under "After completing significant work" below. - -Filter rules: - -1. Prefer `AND` / `OR` / `NOT` operators at the root. A bare `{"user_id": "..."}` works as shorthand but cannot combine multiple fields. -2. Metadata uses a **nested** object, not a dotted key. `{"metadata": {"type": "decision"}}`, never `{"metadata.type": "decision"}`. Only top-level metadata keys are filterable. - -Combine `user_id` + `app_id` with one metadata clause per call: - -| `metadata.type` clause | Use for | -|--------|---------| -| `{"metadata": {"type": "decision"}}` | design / architecture / "how should we" questions | -| `{"metadata": {"type": "anti_pattern"}}` | debugging, error handling, things that failed before | -| `{"metadata": {"type": "user_preference"}}` | tooling, stack, style — always include for code work | -| `{"metadata": {"type": "convention"}}` | established patterns in this project | - -### Which categories to search by query intent - -When a query clearly maps to one of the platform's custom categories, fan-out to 2–3 parallel `search_memories` calls scoped to those categories so recall is precise without being noisy. Use the `metadata.type` filter as your primary discriminator; treat the category column below as the semantic lens to pick the right query nouns. - -| User intent / signal | Primary categories to search | Example query nouns | -|---|---|---| -| Design or architecture question | `architecture_decisions`, `api_contracts`, `data_model` | `"architecture decision"`, `"API schema"`, `"data model"` | -| Something failed / debugging | `anti_patterns`, `bug_fixes`, `security_constraints` | `"bug root cause"`, `"failure pattern"`, `"security constraint"` | -| How do we do X here? | `coding_conventions`, `team_norms`, `testing_patterns` | `"code convention"`, `"team norm"`, `"test strategy"` | -| Which library / version to use | `dependency_decisions`, `tooling_setup`, `architecture_decisions` | `"dependency choice"`, `"library version"`, `"tooling setup"` | -| Performance or scale concern | `performance_findings`, `architecture_decisions`, `data_model` | `"performance bottleneck"`, `"profiling result"`, `"optimisation"` | -| Security / auth / compliance | `security_constraints`, `api_contracts`, `coding_conventions` | `"auth rule"`, `"security requirement"`, `"compliance"` | -| Test strategy or coverage | `testing_patterns`, `coding_conventions`, `anti_patterns` | `"test framework"`, `"coverage target"`, `"fixture pattern"` | -| Schema / DB / domain object | `data_model`, `api_contracts`, `domain_glossary` | `"schema"`, `"column"`, `"domain object"` | -| API shape or versioning | `api_contracts`, `data_model`, `architecture_decisions` | `"endpoint"`, `"request schema"`, `"versioning"` | -| How to deploy / release / rollback | `deployment_runbook`, `tooling_setup`, `team_norms` | `"deploy step"`, `"rollback"`, `"CI pipeline"` | -| Team process / branching / PRs | `team_norms`, `coding_conventions`, `deployment_runbook` | `"branching strategy"`, `"PR review"`, `"working agreement"` | -| What does this term mean? | `domain_glossary`, `data_model`, `api_contracts` | `"glossary"`, `"abbreviation"`, `"domain term"` | -| Experiment / spike / A-B test | `experiment_results`, `performance_findings`, `anti_patterns` | `"experiment result"`, `"A/B test"`, `"spike outcome"` | -| User's tool / language preferences | `user_preferences`, `tooling_setup`, `coding_conventions` | `"user preference"`, `"preferred tool"`, `"language choice"` | -| Past task strategies that worked | `task_learnings`, `anti_patterns`, `coding_conventions` | `"task strategy"`, `"approach that worked"` | -| Environment / setup question | `tooling_setup`, `deployment_runbook`, `dependency_decisions` | `"environment setup"`, `"build tool"`, `"install step"` | -| Anything related to current state | `task_learnings`, `architecture_decisions`, `anti_patterns` | (combine with recency filter — see below) | - -Full filter (replace `` and `` with the active values from SessionStart): -```python -filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]} -``` - -### Worked example - -User asks: *"Refactor the auth module to use JWT."* - -Don't: -```python -search_memories(query="Refactor the auth module to use JWT") -# Hits whatever shares words. Misses prior decisions and preferences. -``` - -Do (parallel — substitute the active `user_id` and `app_id` for the placeholders): -```python -search_memories(query="auth module decisions", - filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}) -search_memories(query="JWT", - filters={"AND": [{"user_id": ""}, {"app_id": ""}]}) -search_memories(query="auth refactor failures", - filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "anti_pattern"}}]}) -search_memories(query="auth", - filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "user_preference"}}]}) -``` - -## After completing significant work - -Extract key learnings and store them using the `add_memory` tool: - -### REQUIRED metadata fields - -Every `add_memory` call MUST include these metadata fields. Do NOT omit them: - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `type` | string | YES | Memory category: `decision`, `task_learning`, `anti_pattern`, `convention`, `user_preference`, `environmental`, `session_state`, `compact_summary` | -| `confidence` | float | YES | 0.0–1.0 confidence score. Default: `0.7` for inferred learnings, `0.9` for explicit user statements | -| `files` | list[str] | YES | File paths relevant to this memory. Use `["*"]` for project-wide learnings | -| `source` | string | YES | How the memory was captured: `user_request`, `auto_capture`, `post_commit`, `error_recovery` | - -**Example:** -```json -{ - "metadata": { - "type": "decision", - "confidence": 0.9, - "files": ["src/auth/login.py", "src/auth/middleware.py"], - "source": "user_request" - } -} -``` - -**If you omit `confidence` or `files`, the memory will be harder to rank and retrieve later.** - -- **Decisions made** -> Include metadata `{"type": "decision"}` -- **Strategies that worked** -> Include metadata `{"type": "task_learning"}` -- **Failed approaches** -> Include metadata `{"type": "anti_pattern"}` -- **User preferences observed** -> Include metadata `{"type": "user_preference"}` -- **Environment/setup discoveries** -> Include metadata `{"type": "environmental"}` -- **Conventions established** -> Include metadata `{"type": "convention"}` - -Always include `"branch": ""` in the metadata object alongside `type`. The active branch is shown in the SessionStart banner. This enables branch-scoped filtering later (e.g., "what did we do on feature/auth-rewrite?"). - -> `metadata.type` (which you set explicitly) and `categories` (which the platform auto-tags after the project's custom-category list — see `scripts/setup_coding_categories.py`) are complementary. Always set `metadata.type` for explicit filtering; the platform fills in `categories` on its own. Don't try to set `categories` on `add_memory` calls — per-request overrides aren't supported on the managed API. - -### Expiration: high-churn vs durable - -Some memory types are state snapshots that go stale fast; others are durable facts that should outlive the session that created them. Mark the difference with `expiration_date` on writes. - -| Type | Expiration | Why | -|---|---|---| -| `session_state`, `compact_summary` | `expiration_date` ≈ today + 90 days | Describe a single moment of project state. Useless after a quarter; clutter the recall surface. | -| `decision`, `anti_pattern`, `convention`, `user_preference`, `task_learning`, `environmental` | omit `expiration_date` | Durable facts. A decision made last year is still a decision; same for a convention or a user preference. | - -`add_memory` accepts `expiration_date` as a string (`"YYYY-MM-DD"`). The two server-side hooks (`on_pre_compact.py`, `capture_compact_summary.py`) already set this for the types they write. When you write directly via the MCP tool, follow the same rule. - -### Recency filter on recall - -When the user is asking about *current* state ("where were we", "what's the active task", "the latest decision on X"), filter recall to recent memories so stale snapshots don't surface: - -```python -# Last 90 days only -{"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "session_state"}}, {"created_at": {"gte": "<90 days ago, YYYY-MM-DD>"}}]} -``` - -Skip the recency filter when the user is asking about durable facts ("what conventions does this project use", "have we hit this bug before") — those are timeless and recency would hide them. - -Memories can be as detailed as needed -- include full context, reasoning, code snippets, file paths, and examples. Longer, searchable memories are more valuable than vague one-liners. - -### Use `infer=False` for already-structured content - -When you've done the extraction work yourself — pre-compaction summaries, decisions, anti-patterns, conventions you've explicitly identified — pass `infer=False` so the platform stores your text verbatim instead of running a second extraction pass over it. - -```python -add_memory( - messages=[{"role": "user", "content": ""}], - user_id="", - app_id="", - metadata={"type": "decision", "branch": ""}, - infer=False, -) -``` - -Stick to one mode per distinct piece of content — don't mix `infer=True` (default) and `infer=False` for the same fact, you'll get duplicates. Default (`infer=True`) is right for raw conversational signal you want extracted; `infer=False` is right for pre-extracted structure. - -## Before losing context - -If context is about to be compacted or the session is ending, store a comprehensive session summary: - -``` -## Session Summary - -### User's Goal -[What the user originally asked for] - -### What Was Accomplished -[Numbered list of tasks completed] - -### Key Decisions Made -[Architectural choices, trade-offs discussed] - -### Files Created or Modified -[Important file paths with what changed] - -### Current State -[What is in progress, pending items, next steps] -``` - -Include metadata: `{"type": "session_state"}` - -## Inline citations - -When your response is informed by specific memories, cite them so the user can trace provenance. Use the memory ID returned by `search_memories`. - -Format: `[mem0:]` where `` is the first 8 characters of the memory ID. - -Example: -> We chose Postgres over SQLite for production [mem0:a3f8b2c1] and the auth module uses JWT tokens [mem0:7e2d9f4a]. - -Rules: -- Only cite when the memory **directly informed** your answer. Don't cite for general knowledge. -- Place citations inline, at the end of the relevant sentence. -- If multiple memories support the same point, cite all: `[mem0:abc12345][mem0:def67890]`. -- Don't cite `session_state` or `compact_summary` memories — those are internal bookkeeping. -- Keep it subtle. One or two citations per response is typical. Don't over-cite. - -## Memory hygiene - -- Do NOT write to MEMORY.md or any file-based memory. Use mem0 MCP tools exclusively. -- Only store genuinely useful learnings. Skip trivial interactions. -- Use specific, searchable language in memory content. - -### Confidence scoring on every add_memory - -Every `add_memory` call MUST include a `confidence` field in its `metadata` object. This captures how certain the stored fact is, so downstream callers can filter out speculation. - -| `metadata.confidence` value | Meaning | When to use | -|---|---|---| -| `1.0` | User explicitly stated it | User said "we use Postgres", "always lint before commit", "never use floats for currency" | -| `0.8` | Observed directly in code / config | You read it from a file, migration, or config — not inferred | -| `0.5` | Inferred from context | You derived it from surrounding evidence but the user didn't confirm it | -| `0.3` | Guessed / low-signal | Extrapolated from a single weak signal; treat as a tentative hypothesis | - -Example: - -```python -add_memory( - messages=[{"role": "user", "content": "We always use Postgres — never SQLite in production."}], - user_id="", - app_id="", - metadata={"type": "architecture_decisions", "branch": "", "confidence": 1.0}, - infer=False, -) -``` - -**Search guidance:** When recalling actionable facts (decisions, conventions, security constraints), optionally apply a confidence threshold of 0.6 or above to avoid surfacing low-confidence guesses. Only top-level metadata keys are filterable, so `confidence` filtering requires SDK-side post-filtering or a dedicated high-confidence write path — for now, include the confidence value in every write and document it in the memory content so it is searchable via text. - -### File path tagging on every add_memory - -Every `add_memory` call that is associated with specific files MUST include a `files` key in its `metadata` object. The value is an array of affected file paths relative to the project root. - -```python -add_memory( - messages=[{"role": "user", "content": "The auth middleware lives in src/middleware/auth.ts and validates JWTs using the shared key in config/secrets.ts."}], - user_id="", - app_id="", - metadata={ - "type": "architecture_decisions", - "branch": "", - "confidence": 0.8, - "files": ["src/middleware/auth.ts", "config/secrets.ts"], - }, - infer=False, -) -``` - -**Filtering by files:** Use the `contains` operator to filter by `metadata.files` at search time: - -```python -search_memories( - query="auth middleware", - filters={ - "AND": [ - {"user_id": ""}, - {"app_id": ""}, - {"metadata.files": {"contains": "src/middleware/auth.ts"}}, - ] - }, - top_k=5, -) -``` - -Also embed bare filenames in the memory content text as a fallback — the vector search will surface them even if the structured filter misses. - -### Access counter: track memory usage - -When you retrieve a memory via `search_memories` and **actually use it** in your response (i.e., it informed your answer or you cited it), increment its access counter and update the last-accessed timestamp by calling: - -```python -# 1. Read current state -mem = get_memory(memory_id=) -current_text = mem["content"] # or mem["memory"], depending on response shape -current_meta = mem.get("metadata", {}) - -# 2. Bump access_count and set last_accessed -import datetime -current_meta["access_count"] = current_meta.get("access_count", 0) + 1 -current_meta["last_accessed"] = datetime.datetime.now(datetime.timezone.utc).isoformat() - -# 3. Update with preserved content and bumped metadata -update_memory( - memory_id=, - text=current_text, # preserve original text — required parameter - metadata=current_meta, # pass updated access_count and last_accessed -) -``` - -**Important:** `update_memory` requires the `text` parameter. Always `get_memory` first to read the current content, then pass it back unchanged. A metadata-only update may error or wipe the content. - -**When to increment:** Only when you actually used the memory to answer. Don't bump on every search hit — that inflates counts for memories that were returned but irrelevant. Aim for 1-3 bumps per response at most. - -**Why:** `access_count` and `last_accessed` feed into `/mem0:dream` pruning decisions. Memories that are never accessed after creation are candidates for cleanup. Frequently accessed memories are protected from pruning regardless of age. diff --git a/mem0-plugin/skills/remember/SKILL.md b/mem0-plugin/skills/remember/SKILL.md index 785feec8e..9a14edf08 100644 --- a/mem0-plugin/skills/remember/SKILL.md +++ b/mem0-plugin/skills/remember/SKILL.md @@ -1,6 +1,6 @@ --- name: remember -description: Save a memory verbatim from your input +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 @@ -43,10 +43,15 @@ Call `add_memory` with: ### Step 4: Confirm -Print: +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: +Memory ID: ``` Append `...` only if content was truncated (longer than 80 chars). diff --git a/mem0-plugin/skills/stats/SKILL.md b/mem0-plugin/skills/stats/SKILL.md index e82ad09e0..a6b0438e2 100644 --- a/mem0-plugin/skills/stats/SKILL.md +++ b/mem0-plugin/skills/stats/SKILL.md @@ -1,6 +1,6 @@ --- name: stats -description: Show memory usage stats for this session and project +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 @@ -22,8 +22,9 @@ The `peek` command returns JSON without clearing the stats file (unlike `report` If the script returns empty or errors, note "No session data available" and continue. -### Step 2: Fetch lifetime stats from API +### Step 2: Fetch lifetime and session stats from API +**Lifetime stats:** Call `get_memories` with: - `user_id=` - `app_id=` @@ -34,7 +35,17 @@ Count the returned memories. Group them by: 2. `metadata.type` (agent-assigned) — secondary if no categories 3. `created_at` date — for age analysis -Also run a `search_memories` call with `query="project"`, `limit=1` to measure round-trip latency (time the call). +**Session stats (API-backed):** +Read the session ID file at `/tmp/mem0_session_id_$USER`. If it exists and contains +a non-empty value, also call `get_memories` with: +- `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"run_id": ""}]}` +- `page_size=100` + +This returns only memories written in the current session. Use this count to +cross-check the local stats file. If the API count is higher, use the API count +(the local tracker may have missed operations). + +Also run a `search_memories` call with `query="project"`, `top_k=1` to measure round-trip latency (time the call). ### Step 3: Display @@ -43,7 +54,7 @@ Print a minimal dashboard. No ASCII bar charts — use a clean table layout: ``` ## mem0 stats -**Session** — 3 written, 5 searches, categories: decision, convention +**Session** () — 3 written, 5 searches, categories: decision, convention **Project: my-project** — 55 memories, API: 84ms diff --git a/mem0-plugin/skills/switch-project/SKILL.md b/mem0-plugin/skills/switch-project/SKILL.md index 477155da1..4296a6b8a 100644 --- a/mem0-plugin/skills/switch-project/SKILL.md +++ b/mem0-plugin/skills/switch-project/SKILL.md @@ -1,6 +1,6 @@ --- name: switch-project -description: Override the auto-detected project scope +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 diff --git a/mem0-plugin/skills/tour/SKILL.md b/mem0-plugin/skills/tour/SKILL.md index 9f8d7c7f4..c28421d07 100644 --- a/mem0-plugin/skills/tour/SKILL.md +++ b/mem0-plugin/skills/tour/SKILL.md @@ -1,6 +1,6 @@ --- name: tour -description: Browse stored memories grouped by category +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 @@ -37,8 +37,8 @@ When `/mem0:tour` receives a search query argument (e.g., `/mem0:tour auth middl 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": ""}]}`, `limit=10` - - Targeted: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}`, `limit=5` + - Broad: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `limit=10`, `rerank=true` + - Targeted: `query=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}, {"metadata": {"type": "decision"}}]}`, `limit=5`, `rerank=true` 2. Deduplicate by ID, display compact results: ``` ## mem0 search: "" ( results) @@ -71,9 +71,9 @@ Pass `page_size=100` (or the maximum allowed) to get a full picture. 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": ""}]}`, `limit=10` -- `query="bugs errors failures anti-patterns"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `limit=10` -- `query="project setup tooling conventions preferences"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `limit=10` +- `query="architecture decisions design choices"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `limit=10`, `rerank=true` +- `query="bugs errors failures anti-patterns"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `limit=10`, `rerank=true` +- `query="project setup tooling conventions preferences"`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `limit=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`. @@ -103,7 +103,7 @@ Map category names to display names: ### Step 4: Display results -For each group that has results, print: +Sort groups by descending memory count. For each group that has results, print: ``` ## ( memories) diff --git a/mem0-plugin/tests/test_session_stats.py b/mem0-plugin/tests/test_session_stats.py index 5bcf4d48d..19d15e20d 100644 --- a/mem0-plugin/tests/test_session_stats.py +++ b/mem0-plugin/tests/test_session_stats.py @@ -83,13 +83,13 @@ def test_report_empty_session(_isolate_stats_file): assert result == "" -def test_report_cleans_up_file(_isolate_stats_file): +def test_report_preserves_file(_isolate_stats_file): import session_stats session_stats.init() session_stats.record_add() session_stats.report() - assert not os.path.isfile(_isolate_stats_file) + assert os.path.isfile(_isolate_stats_file) def test_record_add_no_category(_isolate_stats_file):