diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index b137cb7f8..04b4be2cb 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.2"
+ "version": "0.2.3"
}
]
}
diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json
index c884692b7..c0bc6ea43 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.2"
+ "version": "0.2.3"
}
]
}
diff --git a/docs/integrations/claude-code.mdx b/docs/integrations/claude-code.mdx
index 782e3d05b..5a1bafe4f 100644
--- a/docs/integrations/claude-code.mdx
+++ b/docs/integrations/claude-code.mdx
@@ -22,10 +22,25 @@ Before setting up Mem0 with Claude Code, ensure you have:
2. Claude Code CLI or Claude Cowork desktop app installed
-3. Your API key exported in your shell:
+3. Your API key added to your shell profile (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
+```
+
+
+Confirm it's set:
```bash
-export MEM0_API_KEY="m0-your-api-key"
+echo $MEM0_API_KEY
+# Should print: m0-your-api-key
```
## Installation
@@ -149,9 +164,10 @@ You: Add refresh token rotation to the auth system.
## Troubleshooting
-- **"Connection failed"** — Verify `MEM0_API_KEY` is set in your shell: `echo $MEM0_API_KEY`
+- **"Connection failed"** — Verify `MEM0_API_KEY` is set in your shell: `echo $MEM0_API_KEY`. If empty, add it to your shell profile (see Prerequisites)
- **No tools appearing** — Restart your Claude Code session after installation
-- **Memories not being captured** — Ensure you installed via the plugin marketplace (Option A) for lifecycle hooks. MCP-only installs require manual memory operations.
+- **Memories not being captured** — Ensure you installed via the plugin marketplace (Option A) for lifecycle hooks. MCP-only installs require manual memory operations
+- **"Mem0 Inactive" banner every session** — Your API key isn't persisting. Add `export MEM0_API_KEY="m0-..."` to your `~/.zshrc` (or `~/.bashrc`) and run `source ~/.zshrc`
diff --git a/docs/integrations/codex.mdx b/docs/integrations/codex.mdx
index 0a8831250..85d24680d 100644
--- a/docs/integrations/codex.mdx
+++ b/docs/integrations/codex.mdx
@@ -22,12 +22,20 @@ Before setting up Mem0 with Codex, ensure you have:
2. OpenAI Codex access
-3. Your API key exported in your shell:
+3. Your API key added to your shell profile (persists across sessions):
-```bash
-export MEM0_API_KEY="m0-your-api-key"
+
+```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 — Direct MCP (Recommended)
diff --git a/docs/integrations/cursor.mdx b/docs/integrations/cursor.mdx
index 61e7a8dec..462dc424c 100644
--- a/docs/integrations/cursor.mdx
+++ b/docs/integrations/cursor.mdx
@@ -22,12 +22,20 @@ Before setting up Mem0 with Cursor, ensure you have:
2. Cursor installed ([cursor.com](https://cursor.com))
-3. Your API key exported in your shell:
+3. Your API key added to your shell profile (persists across sessions):
-```bash
-export MEM0_API_KEY="m0-your-api-key"
+
+```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
+```
+
+
Already have `mem0` configured as an MCP server in Cursor? Remove the existing entry from your Cursor MCP settings before installing to avoid duplicate tools.
diff --git a/mem0-plugin/.claude-plugin/plugin.json b/mem0-plugin/.claude-plugin/plugin.json
index 9e638dd1c..a398e1758 100644
--- a/mem0-plugin/.claude-plugin/plugin.json
+++ b/mem0-plugin/.claude-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"name": "mem0",
- "version": "0.2.2",
- "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows using the Mem0 Platform MCP server.",
+ "version": "0.2.3",
+ "description": "Persistent memory for Claude Code. Remembers decisions, patterns, and preferences across sessions.",
"author": {
"name": "Mem0",
"email": "support@mem0.ai"
@@ -9,5 +9,14 @@
"homepage": "https://mem0.ai",
"repository": "https://github.com/mem0ai/mem0",
"license": "Apache-2.0",
- "keywords": ["memory", "personalization", "mcp", "semantic-search"]
+ "keywords": ["memory", "personalization", "mcp", "semantic-search"],
+ "userConfig": {
+ "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.",
+ "sensitive": true,
+ "required": true
+ }
+ }
}
diff --git a/mem0-plugin/.codex-plugin/plugin.json b/mem0-plugin/.codex-plugin/plugin.json
index bc29f76df..3d25dbbeb 100644
--- a/mem0-plugin/.codex-plugin/plugin.json
+++ b/mem0-plugin/.codex-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "mem0",
- "version": "0.2.2",
+ "version": "0.2.3",
"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 de4aab2d2..debbd32c9 100644
--- a/mem0-plugin/.cursor-plugin/plugin.json
+++ b/mem0-plugin/.cursor-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "mem0",
- "version": "0.2.2",
+ "version": "0.2.3",
"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 dd091bc0c..a99e7ba07 100644
--- a/mem0-plugin/.mcp.json
+++ b/mem0-plugin/.mcp.json
@@ -5,7 +5,8 @@
"url": "https://mcp.mem0.ai/mcp/",
"headers": {
"Authorization": "Token ${MEM0_API_KEY}"
- }
+ },
+ "authorizationUrl": "https://mcp.mem0.ai/authorize"
}
}
}
diff --git a/mem0-plugin/hooks/codex-hooks.json b/mem0-plugin/hooks/codex-hooks.json
index 368120361..3422b1ffe 100644
--- a/mem0-plugin/hooks/codex-hooks.json
+++ b/mem0-plugin/hooks/codex-hooks.json
@@ -1,6 +1,16 @@
{
"hooks": {
"PreToolUse": [
+ {
+ "matcher": "Read",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "${CODEX_PLUGIN_ROOT}/scripts/on_file_read.sh",
+ "timeout": 8
+ }
+ ]
+ },
{
"matcher": "mcp__mem0__add_memory|mcp__plugin_mem0_mem0__add_memory",
"hooks": [
@@ -31,7 +41,7 @@
"type": "command",
"command": "${CODEX_PLUGIN_ROOT}/scripts/on_user_prompt.sh",
"statusMessage": "Checking memory relevance...",
- "timeout": 5
+ "timeout": 12
}
]
}
@@ -58,7 +68,7 @@
{
"type": "command",
"command": "${CODEX_PLUGIN_ROOT}/scripts/on_bash_output.sh",
- "timeout": 5
+ "timeout": 12
}
]
}
diff --git a/mem0-plugin/hooks/cursor-hooks.json b/mem0-plugin/hooks/cursor-hooks.json
index 13ccd456c..c17cc6537 100644
--- a/mem0-plugin/hooks/cursor-hooks.json
+++ b/mem0-plugin/hooks/cursor-hooks.json
@@ -11,6 +11,11 @@
"command": "${CURSOR_PLUGIN_ROOT}/scripts/block_memory_write_cursor.sh",
"matcher": "Write|Edit"
},
+ {
+ "command": "${CURSOR_PLUGIN_ROOT}/scripts/on_file_read.sh",
+ "matcher": "Read",
+ "timeout": 8
+ },
{
"command": "${CURSOR_PLUGIN_ROOT}/scripts/enforce_metadata_defaults.sh",
"matcher": "mcp__mem0__add_memory|mcp__plugin_mem0_mem0__add_memory",
@@ -31,7 +36,7 @@
{
"command": "${CURSOR_PLUGIN_ROOT}/scripts/on_bash_output.sh",
"matcher": "Bash",
- "timeout": 5
+ "timeout": 12
}
],
"preCompact": [
@@ -48,7 +53,7 @@
"beforeSubmitPrompt": [
{
"command": "${CURSOR_PLUGIN_ROOT}/scripts/on_user_prompt_cursor.sh",
- "timeout": 5
+ "timeout": 12
}
]
}
diff --git a/mem0-plugin/hooks/hooks.json b/mem0-plugin/hooks/hooks.json
index db424633f..6571d3c4b 100644
--- a/mem0-plugin/hooks/hooks.json
+++ b/mem0-plugin/hooks/hooks.json
@@ -55,6 +55,16 @@
}
]
},
+ {
+ "matcher": "Read",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_file_read.sh",
+ "timeout": 8
+ }
+ ]
+ },
{
"matcher": "mcp__mem0__add_memory|mcp__plugin_mem0_mem0__add_memory",
"hooks": [
@@ -88,7 +98,7 @@
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_bash_output.sh",
- "timeout": 5
+ "timeout": 12
}
]
}
@@ -122,7 +132,7 @@
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_user_prompt.sh",
"statusMessage": "Checking memory relevance...",
- "timeout": 5
+ "timeout": 12
}
]
}
diff --git a/mem0-plugin/output-styles/compact-memory.md b/mem0-plugin/output-styles/compact-memory.md
new file mode 100644
index 000000000..aeaf655e4
--- /dev/null
+++ b/mem0-plugin/output-styles/compact-memory.md
@@ -0,0 +1,37 @@
+---
+name: compact-memory
+description: Terse one-liner memory display — type, content, and ID.
+---
+
+# Compact Memory Output Style
+
+When this style is active, all memory-related output uses this one-liner format:
+
+```
+[] [mem0:]
+```
+
+## Examples
+
+```
+[decision] Auth module uses JWT with RS256 signing keys [mem0:a3f8b2c1]
+[convention] All API routes live in src/routes/ with kebab-case filenames [mem0:7e2d9f4a]
+[anti_pattern] Don't use raw SQL — always go through the ORM layer [mem0:c4d5e6f7]
+[task_learning] Redis cache TTL should be 300s for user sessions [mem0:d8e9f0a1]
+```
+
+## Rules
+
+- Type in brackets, lowercase, from `metadata.type`
+- Content truncated at 80 chars with no trailing ellipsis
+- Short ID = first 8 chars of memory ID
+- One memory per line, no extra formatting
+- No headers or separators between memories unless grouped
+- When grouped by type, use a blank line between groups
+
+## When to use
+
+- `/mem0:peek` results
+- `/mem0:tour` memory listings
+- Search results from `search_memories`
+- Any context where memories are displayed inline
diff --git a/mem0-plugin/scripts/_identity.py b/mem0-plugin/scripts/_identity.py
index 16ae7e0ac..59f7ec498 100644
--- a/mem0-plugin/scripts/_identity.py
+++ b/mem0-plugin/scripts/_identity.py
@@ -1,12 +1,16 @@
-"""Resolve mem0 identity: API key and user_id.
+"""Resolve mem0 identity: API key, user_id, and settings.
API key resolution (first non-empty wins):
1. MEM0_API_KEY env var (explicit / shell profile)
- 2. CLAUDE_PLUGIN_OPTION_MEM0_API_KEY (set by Claude Code userConfig)
+ 2. CLAUDE_PLUGIN_OPTION_API_KEY (set by `claude plugin configure mem0`)
+ 3. CLAUDE_PLUGIN_OPTION_MEM0_API_KEY (legacy userConfig)
User ID resolution:
1. MEM0_USER_ID env var (explicit override)
2. $USER, else "default"
+
+Settings resolution:
+ ~/.mem0/settings.json (user-editable, falls back to defaults)
"""
from __future__ import annotations
@@ -18,7 +22,13 @@ def resolve_api_key() -> str:
key = os.environ.get("MEM0_API_KEY", "").strip()
if key:
return key
- return os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", "").strip()
+ key = os.environ.get("CLAUDE_PLUGIN_OPTION_API_KEY", "").strip()
+ if key:
+ return key
+ key = os.environ.get("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", "").strip()
+ if key:
+ return key
+ return ""
def resolve_user_id() -> str:
@@ -28,6 +38,25 @@ def resolve_user_id() -> str:
return os.environ.get("USER") or "default"
+def resolve_config() -> dict:
+ """Resolve settings from ~/.mem0/settings.json (primary) with env var overrides."""
+ try:
+ from load_settings import load_settings
+ return load_settings()
+ except ImportError:
+ return {
+ "auto_save": True,
+ "auto_search": True,
+ "search_limit": 10,
+ "retention_session_days": 90,
+ "confidence_threshold": 0.3,
+ "output_style": "compact",
+ "debug": False,
+ "skip_tools": ["Read", "Glob", "Grep"],
+ "capture_tools": ["Edit", "Write", "Bash"],
+ }
+
+
try:
from _project import resolve_branch, resolve_project_id, save_project_mapping
except ImportError:
diff --git a/mem0-plugin/scripts/_identity.sh b/mem0-plugin/scripts/_identity.sh
index ac69caa52..66199dbb1 100644
--- a/mem0-plugin/scripts/_identity.sh
+++ b/mem0-plugin/scripts/_identity.sh
@@ -1,14 +1,19 @@
-# Source this file. Sets MEM0_API_KEY and MEM0_RESOLVED_USER_ID.
+# Source this file. Sets MEM0_API_KEY, MEM0_RESOLVED_USER_ID, and settings.
#
# API key resolution (first non-empty wins):
# 1. MEM0_API_KEY env var (explicit / shell profile)
-# 2. CLAUDE_PLUGIN_OPTION_MEM0_API_KEY (set by Claude Code userConfig)
+# 2. CLAUDE_PLUGIN_OPTION_API_KEY (set by `claude plugin configure mem0`)
+# 3. CLAUDE_PLUGIN_OPTION_MEM0_API_KEY (legacy userConfig)
#
-# User ID resolution:
-# 1. MEM0_USER_ID env var (explicit override)
-# 2. $USER, else "default"
+# Settings: ~/.mem0/settings.json (user-editable, falls back to defaults)
-# Resolve API key from userConfig fallback
+_SCRIPT_DIR="$( cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd )"
+
+# Resolve API key: env var > userConfig
+if [ -z "${MEM0_API_KEY:-}" ] && [ -n "${CLAUDE_PLUGIN_OPTION_API_KEY:-}" ]; then
+ MEM0_API_KEY="$CLAUDE_PLUGIN_OPTION_API_KEY"
+ export MEM0_API_KEY
+fi
if [ -z "${MEM0_API_KEY:-}" ] && [ -n "${CLAUDE_PLUGIN_OPTION_MEM0_API_KEY:-}" ]; then
MEM0_API_KEY="$CLAUDE_PLUGIN_OPTION_MEM0_API_KEY"
export MEM0_API_KEY
@@ -25,5 +30,24 @@ _mem0_resolve_identity() {
MEM0_RESOLVED_USER_ID="$(_mem0_resolve_identity)"
export MEM0_RESOLVED_USER_ID
+# 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 "{}")
+ MEM0_AUTO_SAVE=$(echo "$_SETTINGS_JSON" | python3 -c "import sys,json; print(str(json.load(sys.stdin).get('auto_save',True)).lower())" 2>/dev/null || echo "true")
+ MEM0_AUTO_SEARCH=$(echo "$_SETTINGS_JSON" | python3 -c "import sys,json; print(str(json.load(sys.stdin).get('auto_search',True)).lower())" 2>/dev/null || echo "true")
+ MEM0_SEARCH_LIMIT=$(echo "$_SETTINGS_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('search_limit',10))" 2>/dev/null || echo "10")
+ MEM0_RETENTION_SESSION_DAYS=$(echo "$_SETTINGS_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('retention_session_days',90))" 2>/dev/null || echo "90")
+ MEM0_CONFIDENCE_THRESHOLD=$(echo "$_SETTINGS_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('confidence_threshold',0.3))" 2>/dev/null || echo "0.3")
+ MEM0_DEBUG=$(echo "$_SETTINGS_JSON" | python3 -c "import sys,json; print(str(json.load(sys.stdin).get('debug',False)).lower())" 2>/dev/null || echo "false")
+else
+ MEM0_AUTO_SAVE="true"
+ MEM0_AUTO_SEARCH="true"
+ MEM0_SEARCH_LIMIT="10"
+ MEM0_RETENTION_SESSION_DAYS="90"
+ MEM0_CONFIDENCE_THRESHOLD="0.3"
+ MEM0_DEBUG="false"
+fi
+export MEM0_AUTO_SAVE MEM0_AUTO_SEARCH MEM0_SEARCH_LIMIT MEM0_RETENTION_SESSION_DAYS MEM0_CONFIDENCE_THRESHOLD MEM0_DEBUG
+
# Also resolve project context
-. "$( cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd )/_project.sh"
+. "$_SCRIPT_DIR/_project.sh"
diff --git a/mem0-plugin/scripts/_search.py b/mem0-plugin/scripts/_search.py
new file mode 100644
index 000000000..51d54f5f6
--- /dev/null
+++ b/mem0-plugin/scripts/_search.py
@@ -0,0 +1,61 @@
+"""Shared mem0 search API helper.
+
+Wraps POST /v3/memories/search/ into a single function call.
+All pre-fetch hooks use this instead of duplicating urllib boilerplate.
+"""
+
+from __future__ import annotations
+
+import json
+import urllib.request
+
+SEARCH_URL = "https://api.mem0.ai/v3/memories/search/"
+SEARCH_TIMEOUT = 5
+
+
+def search_memories(
+ api_key: str,
+ user_id: str,
+ project_id: str,
+ query: str,
+ metadata_type: str | None = None,
+ top_k: int = 3,
+) -> list[dict]:
+ if not api_key:
+ return []
+
+ filters: dict = {"AND": [{"user_id": user_id}, {"app_id": project_id}]}
+ if metadata_type:
+ filters["AND"].append({"metadata": {"type": metadata_type}})
+
+ body = json.dumps({"query": query, "filters": filters, "top_k": top_k}).encode()
+ req = urllib.request.Request(
+ SEARCH_URL,
+ data=body,
+ headers={"Authorization": f"Token {api_key}", "Content-Type": "application/json"},
+ method="POST",
+ )
+ 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", [])
+ except Exception:
+ return []
+
+
+def format_results_for_context(
+ memories: list[dict],
+ heading: str = "Relevant memories",
+) -> str:
+ if not memories:
+ return ""
+ lines = [f"### {heading}", ""]
+ for m in memories:
+ mid = m.get("id", "?")[:8]
+ text = m.get("memory", "")[:200]
+ cat = (m.get("metadata") or {}).get("type", "unknown")
+ lines.append(f"- [{cat}] {text} [mem0:{mid}]")
+ lines.append("")
+ return "\n".join(lines)
diff --git a/mem0-plugin/scripts/auto_import.py b/mem0-plugin/scripts/auto_import.py
index 3f4be30a2..8245f0e4f 100644
--- a/mem0-plugin/scripts/auto_import.py
+++ b/mem0-plugin/scripts/auto_import.py
@@ -158,6 +158,7 @@ def main() -> None:
hashes = load_hashes()
updated = False
+ seen_content_hashes: set[str] = set()
for filename in TARGET_FILES:
filepath = ""
@@ -187,6 +188,11 @@ def main() -> None:
log.debug("Cannot hash %s: %s", filename, e)
continue
+ if current_hash in seen_content_hashes:
+ log.debug("Duplicate content, skipping: %s (same as earlier file)", filename)
+ continue
+ seen_content_hashes.add(current_hash)
+
hash_key = f"{project_id}:{filename}"
if hashes.get(hash_key) == current_hash:
log.debug("Unchanged, skipping: %s", filename)
diff --git a/mem0-plugin/scripts/enforce_metadata_defaults.sh b/mem0-plugin/scripts/enforce_metadata_defaults.sh
index 1e0ba4c19..4556f1c1e 100755
--- a/mem0-plugin/scripts/enforce_metadata_defaults.sh
+++ b/mem0-plugin/scripts/enforce_metadata_defaults.sh
@@ -46,6 +46,11 @@ 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
+ changed = True
+
if changed:
inp['metadata'] = meta
print(json.dumps(inp))
diff --git a/mem0-plugin/scripts/load_settings.py b/mem0-plugin/scripts/load_settings.py
new file mode 100644
index 000000000..e77c81224
--- /dev/null
+++ b/mem0-plugin/scripts/load_settings.py
@@ -0,0 +1,52 @@
+"""Load plugin settings from ~/.mem0/settings.json.
+
+Settings file is user-editable. Missing file or keys fall back to defaults.
+"""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+
+SETTINGS_PATH = Path.home() / ".mem0" / "settings.json"
+
+DEFAULTS = {
+ "auto_save": True,
+ "auto_search": True,
+ "search_limit": 10,
+ "retention_session_days": 90,
+ "confidence_threshold": 0.3,
+ "output_style": "compact",
+ "debug": False,
+ "skip_tools": ["Read", "Glob", "Grep"],
+ "capture_tools": ["Edit", "Write", "Bash"],
+}
+
+
+def load_settings() -> dict:
+ settings = dict(DEFAULTS)
+ if SETTINGS_PATH.exists():
+ try:
+ with open(SETTINGS_PATH) as f:
+ user = json.load(f)
+ settings.update({k: v for k, v in user.items() if k in DEFAULTS})
+ except (json.JSONDecodeError, OSError):
+ pass
+ return settings
+
+
+def create_default_settings() -> None:
+ SETTINGS_PATH.parent.mkdir(parents=True, exist_ok=True)
+ if not SETTINGS_PATH.exists():
+ with open(SETTINGS_PATH, "w") as f:
+ json.dump(DEFAULTS, f, indent=2)
+ f.write("\n")
+
+
+if __name__ == "__main__":
+ import sys
+ if len(sys.argv) > 1 and sys.argv[1] == "init":
+ create_default_settings()
+ print(f"Created {SETTINGS_PATH}")
+ else:
+ print(json.dumps(load_settings()))
diff --git a/mem0-plugin/scripts/on_bash_output.sh b/mem0-plugin/scripts/on_bash_output.sh
index a79a705b1..61f0654d1 100755
--- a/mem0-plugin/scripts/on_bash_output.sh
+++ b/mem0-plugin/scripts/on_bash_output.sh
@@ -46,10 +46,6 @@ fi
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/_identity.sh" 2>/dev/null || true
-if [ -z "${MEM0_API_KEY:-}" ]; then
- exit 0
-fi
-
# Extract error class/message (first matching line)
ERROR_LINE=$(echo "$TOOL_RESULT" | grep -iE '(Error:|Exception:|panic:|FAIL:|fatal:)' | head -1 | sed 's/^[[:space:]]*//' | cut -c1-120)
@@ -62,45 +58,60 @@ if [ -n "$TRACE_FILES" ]; then
FILE_DISPLAY=$(echo "$TRACE_FILES" | sed 's/^/ - /')
fi
-USER_ID="$MEM0_RESOLVED_USER_ID"
+USER_ID="${MEM0_RESOLVED_USER_ID:-${USER:-default}}"
-cat < $ERROR_LINE
-
-EOF
-
-if [ -n "$FILE_DISPLAY" ]; then
- cat </dev/null &
+# No API key — skip output entirely
+if [ -z "${MEM0_API_KEY:-}" ]; then
+ exit 0
+fi
+
+# Pre-fetch memories: anti_pattern and bug_fix searches
+RESULTS=$(PYTHONPATH="$SCRIPT_DIR" MEM0_SEARCH_QUERY="$ERROR_QUERY" MEM0_SEARCH_USER="$USER_ID" \
+ MEM0_API_KEY="${MEM0_API_KEY}" MEM0_PROJECT_ID="${MEM0_PROJECT_ID:-unknown}" \
+ python3 -c "
+import os, sys
+sys.path.insert(0, os.environ.get('PYTHONPATH', '.'))
+from _search import search_memories, format_results_for_context
+
+api_key = os.environ.get('MEM0_API_KEY', '')
+user_id = os.environ.get('MEM0_SEARCH_USER', 'default')
+project_id = os.environ.get('MEM0_PROJECT_ID', 'unknown')
+query = os.environ.get('MEM0_SEARCH_QUERY', '')
+
+r1 = search_memories(api_key, user_id, project_id, query, metadata_type='anti_pattern', top_k=3)
+r2 = search_memories(api_key, user_id, project_id, query, metadata_type='bug_fix', top_k=3)
+
+seen = set()
+combined = []
+for m in r1 + r2:
+ mid = m.get('id', '')
+ if mid not in seen:
+ seen.add(mid)
+ combined.append(m)
+
+print(format_results_for_context(combined, heading='Prior error memories'), end='')
+" 2>/dev/null || echo "")
+
+# Output error header
+printf '\n## Error detected in command output\n\n'
+printf '`%s` produced an error:\n> %s\n\n' "$COMMAND" "$ERROR_LINE"
+
+if [ -n "$FILE_DISPLAY" ]; then
+ printf '**Files in stack trace:**\n%s\n\n' "$FILE_DISPLAY"
+fi
+
+if [ -n "$RESULTS" ]; then
+ printf '%s\n' "$RESULTS"
+else
+ printf 'No prior memories found for this error.\n\n'
+fi
+
+printf 'If you solve this, store the fix as an `anti_pattern` or `bug_fix` memory for next time.\n'
+
exit 0
diff --git a/mem0-plugin/scripts/on_file_read.sh b/mem0-plugin/scripts/on_file_read.sh
new file mode 100755
index 000000000..de6e0526c
--- /dev/null
+++ b/mem0-plugin/scripts/on_file_read.sh
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+# Hook: PreToolUse (matcher: Read)
+#
+# When Claude reads a file, searches mem0 for memories tagged with that
+# file path. Injects results as additionalContext if found.
+
+set -uo pipefail
+
+INPUT=$(cat)
+
+FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null || echo "")
+
+if [ -z "$FILE_PATH" ]; then
+ exit 0
+fi
+
+# Skip non-code files (images, binaries, lockfiles, etc.)
+case "$FILE_PATH" in
+ *.png|*.jpg|*.jpeg|*.gif|*.svg|*.ico|*.woff|*.woff2|*.ttf|*.eot) exit 0 ;;
+ *.lock|*.sum|*.min.js|*.min.css|*.map) exit 0 ;;
+ *node_modules/*|*.git/*|*__pycache__/*) exit 0 ;;
+esac
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+. "$SCRIPT_DIR/_identity.sh" 2>/dev/null || true
+
+# Skip if no API key (checked after _identity.sh resolves CLAUDE_PLUGIN_OPTION_MEM0_API_KEY)
+if [ -z "${MEM0_API_KEY:-}" ]; then
+ exit 0
+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}"
+if [ -f "$RECENT_FILE" ] && grep -qxF "$FILE_PATH" "$RECENT_FILE" 2>/dev/null; then
+ exit 0
+fi
+echo "$FILE_PATH" >> "$RECENT_FILE" 2>/dev/null || true
+tail -10 "$RECENT_FILE" > "$RECENT_FILE.tmp" 2>/dev/null && mv "$RECENT_FILE.tmp" "$RECENT_FILE" 2>/dev/null || true
+
+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 "
+import os, sys
+sys.path.insert(0, os.environ.get('PYTHONPATH', '.'))
+from _search import search_memories, format_results_for_context
+
+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', '')
+
+results = search_memories(api_key, user_id, project_id, filename, top_k=3)
+if results:
+ print(format_results_for_context(results, heading=f'mem0 context for {filename}'))
+" 2>/dev/null || true)
+
+if [ -n "$CONTEXT" ]; then
+ jq -nc --arg ctx "$CONTEXT" '{hookSpecificOutput:{hookEventName:"PreToolUse",additionalContext:$ctx}}'
+fi
+
+exit 0
diff --git a/mem0-plugin/scripts/on_post_tool_use.sh b/mem0-plugin/scripts/on_post_tool_use.sh
index 0f1d88275..2d4883dca 100755
--- a/mem0-plugin/scripts/on_post_tool_use.sh
+++ b/mem0-plugin/scripts/on_post_tool_use.sh
@@ -16,19 +16,19 @@ INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null || echo "")
case "$TOOL_NAME" in
- mcp__mem0__add_memory)
+ *__add_memory)
CATEGORY=$(echo "$INPUT" | jq -r '.tool_input.metadata.type // .tool_input.metadata.category // ""' 2>/dev/null || echo "")
python3 "$SCRIPT_DIR/session_stats.py" add "$CATEGORY" 2>/dev/null || true
python3 "$SCRIPT_DIR/telemetry.py" tool_use --tool=add_memory 2>/dev/null &
;;
- mcp__mem0__search_memories|mcp__mem0__get_memories)
+ *__search_memories|*__get_memories)
python3 "$SCRIPT_DIR/session_stats.py" search 2>/dev/null || true
python3 "$SCRIPT_DIR/telemetry.py" tool_use --tool=search_memories 2>/dev/null &
;;
- mcp__mem0__delete_memory)
+ *__delete_memory)
python3 "$SCRIPT_DIR/telemetry.py" tool_use --tool=delete_memory 2>/dev/null &
;;
- mcp__mem0__update_memory)
+ *__update_memory)
python3 "$SCRIPT_DIR/telemetry.py" tool_use --tool=update_memory 2>/dev/null &
;;
esac
diff --git a/mem0-plugin/scripts/on_session_start.sh b/mem0-plugin/scripts/on_session_start.sh
index 29f8fbdac..d44cc2f6c 100755
--- a/mem0-plugin/scripts/on_session_start.sh
+++ b/mem0-plugin/scripts/on_session_start.sh
@@ -19,26 +19,71 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=_identity.sh
. "$SCRIPT_DIR/_identity.sh"
-# Initialize session stats tracker
+# 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")
-# Skip bootstrap if no API key — still show banner but with "no key" status.
+MEM0_AUTH_MODE="api_key"
if [ -z "${MEM0_API_KEY:-}" ]; then
- echo "## Mem0 Inactive"
- echo ""
- echo "\`user=${MEM0_RESOLVED_USER_ID:-$USER} | project=${MEM0_PROJECT_ID:-unknown} | branch=${MEM0_BRANCH:-unknown} | api_key=NOT_SET\`"
- echo ""
- echo "IMPORTANT: In your FIRST response to the user, you MUST display the status banner exactly as shown below (copy-paste it as your opening line before any other output):"
- echo ""
- echo '```'
- echo "Mem0 Inactive | user=${MEM0_RESOLVED_USER_ID:-$USER} | project=${MEM0_PROJECT_ID:-unknown} | branch=${MEM0_BRANCH:-unknown} | api_key=NOT_SET"
- echo '```'
- echo ""
- echo "Set MEM0_API_KEY to enable persistent memory. Get a key at https://app.mem0.ai/dashboard/api-keys"
- exit 0
+ _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 [ "$MEM0_AUTH_MODE" = "api_key" ] && 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', '')
@@ -83,20 +129,22 @@ fi
# 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.
-echo "## Mem0 Active"
-echo ""
-echo "\`user=$MEM0_RESOLVED_USER_ID | project=$MEM0_PROJECT_ID | branch=$MEM0_BRANCH | memories=$MEM0_COUNT\`"
-echo ""
-echo "IMPORTANT: In your FIRST response to the user, you MUST display the identity banner exactly as shown below (copy-paste it as your opening line before any other output):"
-echo ""
-echo '```'
-echo "Mem0 Active | user=$MEM0_RESOLVED_USER_ID | project=$MEM0_PROJECT_ID | branch=$MEM0_BRANCH | memories=$MEM0_COUNT"
-echo '```'
-echo ""
-echo "Always include \`user_id\` + \`app_id\` in every \`search_memories\` filter and \`add_memory\` call:"
-echo "- user_id: \`$MEM0_RESOLVED_USER_ID\`"
-echo "- app_id: \`$MEM0_PROJECT_ID\` (project scope — passed as top-level \`app_id\`, NOT in metadata)"
-echo ""
+cat </dev/null || true
- touch "$_ONBOARD_MARKER"
cat <<'EOF'
-## Mem0 First Run — Automatic Onboarding
-
-This is the first time mem0 is running for this project. You MUST run the onboarding wizard NOW before doing anything else.
-
-Execute the `/mem0:onboard` skill immediately. This will:
-1. Verify the API key
-2. Detect and import project files (CLAUDE.md, AGENTS.md, .cursorrules, etc.)
-3. Install coding categories optimized for development
-4. Show a summary of what was set up
-
-Do NOT skip this. Do NOT ask the user if they want to onboard. Just run `/mem0:onboard` now.
+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).
EOF
else
cat <<'EOF'
-## Mem0 Session Bootstrap
-
-You have access to persistent memory via the mem0 MCP tools. Before doing anything else:
-
-1. Call `search_memories` with a query related to the current project or user request to load relevant context.
-2. Review the returned memories to understand what has been learned in prior sessions.
-3. If appropriate, call `get_memories` to browse all stored memories for this user.
-
-IMPORTANT: Do NOT skip this step. Always bootstrap context first.
+Search mem0 for recent decisions and task learnings before responding to the user's first message. Run 2 parallel searches: one for `decision` type, one for `task_learning` type.
EOF
fi
@@ -172,26 +200,12 @@ MEMEOF
elif [ "$SOURCE" = "resume" ]; then
cat <<'EOF'
-## Mem0 Session Resumed
-
-This is a resumed session. Your prior context is already loaded. Before continuing:
-
-1. Call `search_memories` with a query related to the current task to refresh relevant memories.
-2. If significant time has passed, search for recent project-wide updates.
-
-Continue where you left off.
+Session resumed. Search mem0 for `session_state` and `decision` memories to pick up where you left off. Run 2 parallel searches.
EOF
elif [ "$SOURCE" = "compact" ]; then
cat <<'EOF'
-## Mem0 Post-Compaction Recovery
-
-Context was just compacted. Reload your context from mem0.
-
-1. Call `search_memories` to reload context, layering up to three angles:
- - `metadata.type=session_state` -- the pre-compaction summary you wrote before compaction
- - `metadata.type=decision` / `anti_pattern` -- specific facts you stored during the session
-2. Continue working from the recovered context.
+Context compacted. Search mem0 for `session_state` and `decision` memories to recover context. Run 2 parallel searches.
EOF
fi
diff --git a/mem0-plugin/scripts/on_stop.sh b/mem0-plugin/scripts/on_stop.sh
index 656170fce..de18ebff3 100755
--- a/mem0-plugin/scripts/on_stop.sh
+++ b/mem0-plugin/scripts/on_stop.sh
@@ -47,19 +47,7 @@ if [ -n "$REPORT" ]; then
fi
cat <<'EOF'
-Before finishing, check if there are important learnings from this interaction that should be persisted using the mem0 `add_memory` tool:
-
-1. Were any significant decisions made? -> Store with metadata `{"type": "decision"}`
-2. Were any new patterns or strategies discovered? -> Store with metadata `{"type": "task_learning"}`
-3. Did any approach fail? -> Store with metadata `{"type": "anti_pattern"}`
-4. Did you learn anything about the user's preferences? -> Store with metadata `{"type": "user_preference"}`
-5. Were there environment/setup discoveries? -> Store with metadata `{"type": "environmental"}`
-
-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.
-
-If nothing notable happened in this interaction, it's fine to skip. Only store genuinely useful learnings.
-
-Always include `app_id` (the active project_id from SessionStart) as a top-level parameter in every `add_memory` call.
+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 "")
diff --git a/mem0-plugin/scripts/on_task_completed.sh b/mem0-plugin/scripts/on_task_completed.sh
index 2d8eef903..8608a9b94 100755
--- a/mem0-plugin/scripts/on_task_completed.sh
+++ b/mem0-plugin/scripts/on_task_completed.sh
@@ -25,16 +25,7 @@ _PROJECT="${MEM0_PROJECT_ID:-unknown}"
cat < Store with metadata \`{"type": "task_learning"}\`
-2. Were there failed approaches before finding the solution? -> Store with metadata \`{"type": "anti_pattern"}\`
-3. Were there architectural decisions? -> Store with metadata \`{"type": "decision"}\`
-4. Any new conventions or patterns established? -> Store with metadata \`{"type": "convention"}\`
-
-Memories can be as detailed as needed — include full context, reasoning, code snippets, and examples.
-Only store genuinely useful learnings — skip if the task was trivial.
-Include \`app_id\` = \`"$_PROJECT"\` as a top-level parameter in every \`add_memory\` call (not in metadata).
+Store 0-2 key learnings via \`add_memory\` with \`app_id="$_PROJECT"\`. Use types: \`decision\`, \`task_learning\`, \`anti_pattern\`, or \`convention\`. Skip if trivial.
EOF
# Telemetry (background, fire-and-forget)
diff --git a/mem0-plugin/scripts/on_tool_failure.sh b/mem0-plugin/scripts/on_tool_failure.sh
index 7a2b9fa55..eb40991bb 100755
--- a/mem0-plugin/scripts/on_tool_failure.sh
+++ b/mem0-plugin/scripts/on_tool_failure.sh
@@ -20,8 +20,9 @@ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null || echo "")
TOOL_RESULT=$(echo "$INPUT" | jq -r '.tool_error // ""' 2>/dev/null || echo "")
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}' 2>/dev/null || echo "{}")
-# Extract the short tool name (strip mcp__mem0__ prefix)
-SHORT_NAME="${TOOL_NAME#mcp__mem0__}"
+# Extract the short tool name (strip any mcp prefix variant)
+SHORT_NAME="${TOOL_NAME##*__}"
+[ "$SHORT_NAME" = "$TOOL_NAME" ] && SHORT_NAME="${TOOL_NAME#mcp__mem0__}"
# Log failure to persistent file for debugging
mkdir -p "$HOME/.mem0" 2>/dev/null || true
diff --git a/mem0-plugin/scripts/on_user_prompt.sh b/mem0-plugin/scripts/on_user_prompt.sh
index e3e4f5a2a..a9098ff6c 100755
--- a/mem0-plugin/scripts/on_user_prompt.sh
+++ b/mem0-plugin/scripts/on_user_prompt.sh
@@ -29,6 +29,20 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=_identity.sh
. "$SCRIPT_DIR/_identity.sh"
+# Rubric dedup: only inject full rubric once per session.
+# Key on session ID (from stdin JSON) to avoid cross-session interference.
+SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""' 2>/dev/null || echo "")
+RUBRIC_DIR="${MEM0_RUBRIC_DIR:-/tmp}"
+if [ -n "$SESSION_ID" ]; then
+ RUBRIC_FLAG="$RUBRIC_DIR/mem0_rubric_${SESSION_ID}"
+else
+ RUBRIC_FLAG="$RUBRIC_DIR/mem0_rubric_injected_${USER}"
+fi
+RUBRIC_ALREADY_SHOWN=""
+if [ -f "$RUBRIC_FLAG" ]; then
+ RUBRIC_ALREADY_SHOWN="true"
+fi
+
# Detect stack traces and error patterns in the prompt (no API needed)
HAS_ERROR=""
if echo "$PROMPT" | grep -qE '(Traceback|panic:)'; then
@@ -42,10 +56,24 @@ fi
# Detect file paths in the prompt (no API needed)
FILE_PATHS=$(echo "$PROMPT" | grep -oE '([a-zA-Z0-9_./-]+\.(py|ts|tsx|js|jsx|rs|go|rb|java|sh|yaml|yml|json|toml|md|sql|css|html))\b' 2>/dev/null | head -5 || echo "")
+# Detect session-resume patterns
+HAS_RESUME=""
+if echo "$PROMPT" | grep -qiE '(where (did )?(we|I) (leave|left) off|continue (from )?(where|last)|what were we (working|doing)|pick up where|resume (from |where)|what.s the (current|latest) (state|status)|catch me up|where are we)'; then
+ HAS_RESUME="true"
+fi
+
+# Detect explicit memory-save intent
+HAS_REMEMBER=""
+if echo "$PROMPT" | grep -qiE '(remember (this|that)|save (this|that) (fact|info|memory|note)|store (this|that)|don.t forget (this|that)|keep (this|that) in (mind|memory))'; then
+ HAS_REMEMBER="true"
+fi
+
# Telemetry (background, fire-and-forget)
_TELEM_ARGS=""
[ -n "$HAS_ERROR" ] && _TELEM_ARGS="$_TELEM_ARGS --error_detected"
[ -n "$FILE_PATHS" ] && _TELEM_ARGS="$_TELEM_ARGS --file_paths_detected"
+[ -n "$HAS_RESUME" ] && _TELEM_ARGS="$_TELEM_ARGS --resume_detected"
+[ -n "$HAS_REMEMBER" ] && _TELEM_ARGS="$_TELEM_ARGS --remember_detected"
python3 "$SCRIPT_DIR/telemetry.py" user_prompt $_TELEM_ARGS 2>/dev/null &
# No API key — emit detections only, skip search rubric
@@ -60,33 +88,62 @@ if [ -z "${MEM0_API_KEY:-}" ]; then
fi
USER_ID="$MEM0_RESOLVED_USER_ID"
-cat </dev/null || echo "")
+
+ if [ -n "$RESUME_RESULTS" ]; then
+ echo ""
+ echo "$RESUME_RESULTS"
+ fi
+fi
+
+if [ -n "$HAS_REMEMBER" ]; then
+ cat <<'REMEMBER_EOF'
+
+**Remember intent detected.** Use `/mem0:remember` (not raw `add_memory`) — it auto-classifies, sets confidence=1.0, and stores verbatim.
+REMEMBER_EOF
+fi
+
+if [ -z "$RUBRIC_ALREADY_SHOWN" ]; then
+ cat </dev/null || true
+fi
if [ -n "$HAS_ERROR" ]; then
cat <", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "anti_pattern"}}]})\`
-- \`search_memories(query="", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "task_learning"}}]})\`
-This surfaces past debugging context and known failure modes.
+**ERROR DETECTED in prompt.** Search mem0 for prior occurrences — use \`anti_pattern\` and \`task_learning\` type filters with the error class or filename from the stack trace.
EOF
fi
@@ -94,29 +151,7 @@ if [ -n "$FILE_PATHS" ]; then
cat <", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata.files": {"contains": ""}}]})\`
-- Also run a broader text search without the files filter as fallback:
-- \`search_memories(query="", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}]})\`
EOF
fi
-cat <"}}\`. Skip recency for durable facts (conventions, decisions).
-- Empty results are normal -- proceed without context.
-EOF
-
exit 0
diff --git a/mem0-plugin/skills/context-loader/SKILL.md b/mem0-plugin/skills/context-loader/SKILL.md
new file mode 100644
index 000000000..ef1ce220e
--- /dev/null
+++ b/mem0-plugin/skills/context-loader/SKILL.md
@@ -0,0 +1,50 @@
+---
+name: context-loader
+description: Pre-load relevant memories for current task
+---
+
+# Context Loader
+
+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)
+- 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 | `{"metadata": {"type": "decision"}}` | Architecture decisions |
+ | File paths mentioned | `{"metadata": {"type": "convention"}}` | Coding patterns |
+ | Error keywords (if any) | `{"metadata": {"type": "anti_pattern"}}` | Known pitfalls |
+ | Broad project context | no metadata filter | Catch-all |
+
+ All calls must include `user_id` and `app_id` filters.
+
+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
diff --git a/mem0-plugin/skills/dream/SKILL.md b/mem0-plugin/skills/dream/SKILL.md
new file mode 100644
index 000000000..c0938ae53
--- /dev/null
+++ b/mem0-plugin/skills/dream/SKILL.md
@@ -0,0 +1,224 @@
+---
+name: dream
+description: Consolidate memories — merge duplicates, resolve contradictions, prune stale
+---
+
+# 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.
+
+---
+
+## Step 1: Load Retention Policies
+
+Determine the active retention policy by running the parser script. Use the
+appropriate `PLUGIN_ROOT` variable for the current platform (`${CLAUDE_PLUGIN_ROOT}`,
+`${CODEX_PLUGIN_ROOT}`, or `${CURSOR_PLUGIN_ROOT}`):
+
+```bash
+python3 "/scripts/parse_mem0_config.py" ""
+```
+
+Parse the JSON output (a dict of `category → days | null`). If the script fails
+or returns `{}`, 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 (item 15)
+
+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:
+ - `messages=[{"role": "user", "content": ""}]`
+ - `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:
+- Identify the loser (the non-chosen memory).
+- First call `get_memory()` to read its current text content.
+- Then call `update_memory(, text=)` to preserve the text while updating it.
+- **Important:** `update_memory` requires the `text` parameter. A metadata-only call may error or wipe the content. Always read first, then update with the original text.
+
+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.
+
+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, store a reminder memory:
+ ```python
+ add_memory(
+ messages=[{"role": "user", "content": "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
diff --git a/mem0-plugin/skills/mem0-export/SKILL.md b/mem0-plugin/skills/export/SKILL.md
similarity index 86%
rename from mem0-plugin/skills/mem0-export/SKILL.md
rename to mem0-plugin/skills/export/SKILL.md
index f94611fa0..e19b0db81 100644
--- a/mem0-plugin/skills/mem0-export/SKILL.md
+++ b/mem0-plugin/skills/export/SKILL.md
@@ -1,10 +1,6 @@
---
-name: mem0-export
-description: >
- Export all memories for the current project to a local Markdown file.
- Each memory is written as a YAML-frontmatter block that can be re-imported later.
- TRIGGER: user runs /mem0:export, or asks "export memories", "backup memories",
- "download my memories", "save memories to file".
+name: export
+description: Export project memories to a portable Markdown file
---
# Mem0 Export
diff --git a/mem0-plugin/skills/forget/SKILL.md b/mem0-plugin/skills/forget/SKILL.md
new file mode 100644
index 000000000..bde9b0f29
--- /dev/null
+++ b/mem0-plugin/skills/forget/SKILL.md
@@ -0,0 +1,71 @@
+---
+name: forget
+description: Delete memories by search or ID — confirms before deleting
+---
+
+# 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. Read session stats to get recently written memory IDs:
+ ```bash
+ SCRIPT_DIR="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}/scripts"
+ python3 "$SCRIPT_DIR/session_stats.py" peek
+ ```
+2. Parse the `recent_ids` array from the JSON output. Each entry has `id`, `category`, `ts`.
+3. Show the last N entries (default 1) and ask for confirmation.
+4. Delete confirmed entries via `delete_memory`.
+
+If `recent_ids` is empty, tell the user: "No recent memory IDs tracked this session. Try `/mem0:tour` to browse recent memories, or `/mem0:forget ` to find specific ones."
diff --git a/mem0-plugin/skills/mem0-health/SKILL.md b/mem0-plugin/skills/health/SKILL.md
similarity index 59%
rename from mem0-plugin/skills/mem0-health/SKILL.md
rename to mem0-plugin/skills/health/SKILL.md
index 2b30b900c..8d984e0b6 100644
--- a/mem0-plugin/skills/mem0-health/SKILL.md
+++ b/mem0-plugin/skills/health/SKILL.md
@@ -1,10 +1,6 @@
---
-name: mem0-health
-description: >
- Diagnostic health check for the mem0 plugin. Verifies API key, MCP server
- connectivity, identity resolution, and memory read/write capability.
- TRIGGER: user runs /mem0:health, or asks "is mem0 working", "mem0 health",
- "check mem0 connection", "debug mem0".
+name: health
+description: Diagnose mem0 connection, API key, and memory read/write
---
# Mem0 Health Check
@@ -70,26 +66,22 @@ This file is created by the SessionStart hook and updated by PostToolUse hooks t
### Display
```
-## mem0 Health Check
+## mem0 health
-| Check | Status | Detail |
-|--------------------|--------|-------------------------------|
-| API Key | PASS | m0-dVe... |
-| Identity | PASS | user=kartik, project=mem0 |
-| MCP Connectivity | PASS | 142ms round-trip |
-| Memory Write/Read | PASS | write + delete OK |
-| Session Tracker | PASS | stats file active |
+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 Tracker stats file active
-All checks passed. mem0 is healthy.
+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`) or `--fix` (e.g., `/mem0:health --fix`), run the standard 5 checks above **plus** a memory quality scan.
-
-`--fix` implies `--deep` and automatically applies safe fixes after showing the analysis (see bottom of this section).
+When invoked with `--deep` (e.g., `/mem0:health --deep`), run the standard 5 checks above **plus** a memory quality scan.
### Quality Check 1: Duplicates
@@ -141,31 +133,11 @@ Untagged/orphan memories:
```
## Memory Quality
-| Metric | Count | Action |
-|----------------|-------|---------------------------------|
-| Duplicates | | Run /mem0:dream to merge |
-| Stale | | Run /mem0:dream to prune |
-| Contradictions | | Run /mem0:dream to resolve |
-| Orphans | | Consider retagging via MCP |
+
+Duplicates: · Stale: · Contradictions: · Orphans:
```
-If all counts are 0: `Memory quality: clean. No duplicates, stale entries, or contradictions found.`
+If all counts are 0: `Memory quality: clean.`
+If any non-zero: append `Run /mem0:dream to fix.`
-### Auto-fix mode (`--fix`)
-
-When `--fix` is passed, apply these safe fixes automatically after displaying the quality summary:
-
-1. **Orphans:** For each untagged memory, infer a `metadata.type` from content and call `update_memory` to set it. If inference is uncertain, skip.
-2. **Stale `session_state`/`compact_summary` > 90d:** Delete them via `delete_memory`. These are ephemeral by design.
-3. **Duplicates:** Do NOT auto-merge — print "Run `/mem0:dream` to merge duplicates" instead.
-4. **Contradictions:** Do NOT auto-resolve — print "Run `/mem0:dream` to resolve contradictions" instead.
-5. **Low-confidence < 0.3 AND > 30d old:** Delete them via `delete_memory`.
-
-Print a summary of actions taken:
-
-```
-## Auto-fix Results
- Deleted: stale, low-confidence
- Retagged: orphans
- Skipped: duplicates (use /mem0:dream), contradictions (use /mem0:dream)
-```
+To fix issues found by `--deep`, run `/mem0:dream` for automated consolidation (merges, prunes, conflict resolution).
diff --git a/mem0-plugin/skills/mem0-import/SKILL.md b/mem0-plugin/skills/import/SKILL.md
similarity index 92%
rename from mem0-plugin/skills/mem0-import/SKILL.md
rename to mem0-plugin/skills/import/SKILL.md
index 34d9cdd59..1c843cc0a 100644
--- a/mem0-plugin/skills/mem0-import/SKILL.md
+++ b/mem0-plugin/skills/import/SKILL.md
@@ -1,11 +1,6 @@
---
-name: mem0-import
-description: >
- Import memories from a mem0 export file back into the current project.
- Reads a YAML-frontmatter Markdown file produced by /mem0:export and
- adds each memory block to mem0.
- TRIGGER: user runs /mem0:import, or asks "import memories", "restore memories",
- "load memories from file", "reimport backup".
+name: import
+description: Import memories from an export file into this project
---
# Mem0 Import
@@ -142,10 +137,7 @@ Tools: `cursorrules`, `copilot`, `cline`, `continue`.
### T4: Report
```
-Import complete.
- Cursor: memories
- Copilot: memories
- Total: memories imported into project
+Imported memories into (cursor: , copilot: )
```
Notes: `infer=False`, tagged `metadata.source=-import`, sections <50 chars
diff --git a/mem0-plugin/skills/list-projects/SKILL.md b/mem0-plugin/skills/list-projects/SKILL.md
new file mode 100644
index 000000000..01ca996f0
--- /dev/null
+++ b/mem0-plugin/skills/list-projects/SKILL.md
@@ -0,0 +1,54 @@
+---
+name: list-projects
+description: List all projects that have stored memories
+---
+
+# 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 and extracting distinct `app_id` values.
+
+Call `get_memories` with:
+- `filters={"user_id": ""}`
+- `page_size=200`
+
+Do NOT pass `app_id` — we want memories across ALL projects.
+
+If the response indicates more pages, paginate until all are fetched (up to 1000
+memories max to avoid excessive API calls).
+
+### 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 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.
+```
diff --git a/mem0-plugin/skills/mem0-dream/SKILL.md b/mem0-plugin/skills/mem0-dream/SKILL.md
deleted file mode 100644
index d0a8bccc2..000000000
--- a/mem0-plugin/skills/mem0-dream/SKILL.md
+++ /dev/null
@@ -1,396 +0,0 @@
----
-name: mem0-dream
-description: >
- Memory consolidation pass. Fetches all project memories, finds near-duplicates,
- merges them, flags contradictions, prunes stale entries per retention policy.
- Outputs a diff for user approval before applying changes.
- TRIGGER: user runs /mem0:dream, or asks "consolidate memories", "clean up memories",
- "merge duplicate memories", "run dream".
----
-
-# 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.
-
----
-
-## Step 1: Load Retention Policies
-
-Determine the active retention policy by running the parser script. Use the
-appropriate `PLUGIN_ROOT` variable for the current platform (`${CLAUDE_PLUGIN_ROOT}`,
-`${CODEX_PLUGIN_ROOT}`, or `${CURSOR_PLUGIN_ROOT}`):
-
-```bash
-python3 "/scripts/parse_mem0_config.py" ""
-```
-
-Parse the JSON output (a dict of `category → days | null`). If the script fails
-or returns `{}`, 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(
- 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 (item 15)
-
-Print a structured diff to the terminal before making any changes. Use exactly
-this format:
-
-```
-## Dream — Memory Consolidation Report
-
-### Merge proposals ( pairs)
-MERGE [mem0:] + [mem0:] → NEW
- - Original 1: ""
- - Original 2: ""
- - Merged: ""
-
-### Contradictions ( pairs)
-CONFLICT [mem0:] vs [mem0:]
- - A: "" (, confidence: )
- - B: "" (, confidence: )
- Which is current? [A/B/skip]
-
-### Prune candidates ( memories)
-PRUNE [mem0:] — , d old (policy: d)
-
----
-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:
- - `messages=[{"role": "user", "content": ""}]`
- - `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:
-- Identify the loser (the non-chosen memory).
-- First call `get_memory()` to read its current text content.
-- Then call `update_memory(, data=)` to preserve the text while updating it.
-- **Important:** `update_memory` requires the `data` (text) parameter. A metadata-only call may error or wipe the content. Always read first, then update with the original text.
-
-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: pairs → new memories
- Pruned: memories deleted
- Flagged: contradictions 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.
-
-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, store a reminder memory:
- ```python
- add_memory(
- messages=[{"role": "user", "content": "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,
- )
- ```
-
-## Forget mode (targeted deletion)
-
-When invoked with `--forget` (e.g., `/mem0:dream --forget auth module decisions`
-or `/mem0:dream --forget `), skip consolidation and go straight to
-search-confirm-delete:
-
-### F1: Parse input
-
-The argument after `--forget` is either:
-- A search query: `/mem0:dream --forget auth module decisions`
-- A memory ID: `/mem0:dream --forget `
-
-If no argument after `--forget`, ask: "What should I forget? Provide a search query or memory ID."
-
-### F2: 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": ""}]}`
- - `limit=10`
-- Show numbered list:
- ```
- Found memories matching "":
- 1. (type: , created: ) [ID: ]
- 2. ...
- ```
-
-### F3: Confirm
-
-Ask: "Delete which memories? Enter numbers (e.g., 1,3,5), 'all', or 'cancel'."
-For a single memory ID: "Delete this memory? [y/N]"
-
-**Never delete without confirmation.**
-
-### F4: Delete and report
-
-Call `delete_memory` for each confirmed entry. Report: `Deleted memories.`
-
-### Undo recent writes
-
-If the user says "undo last N memories" or "undo last write":
-1. Run `python3 "$SCRIPT_DIR/session_stats.py" peek` to get `recent_ids`.
-2. Show last N entries, ask for confirmation.
-3. Delete confirmed entries via `delete_memory`.
-
----
-
-## Scheduling recurring dreams
-
-When invoked with `--schedule` (e.g., `/mem0:dream --schedule weekly`), register a
-cloud routine via Claude Code's built-in `/schedule` command so the dream runs
-automatically without any local cron or launchd setup.
-
-### Step S1: Parse schedule frequency
-
-Accept natural-language frequency after `--schedule`:
-
-| User input | Cron equivalent | Description |
-|---|---|---|
-| `weekly` or `--schedule weekly` | Every Sunday 3:00 AM local | Default weekly consolidation |
-| `daily` | Every day 3:00 AM local | For high-volume projects |
-| `biweekly` | Every other Sunday 3:00 AM local | Lower frequency option |
-| Custom (e.g., `"every Monday 9am"`) | Pass verbatim to `/schedule` | Let Claude Code resolve it |
-
-### Step S2: Create the routine
-
-Use Claude Code's `/schedule` command to create a cloud routine. The routine runs
-`/mem0:dream --auto` on the specified schedule against the current repository:
-
-```
-/schedule /mem0:dream --auto
-```
-
-For example:
-- `/schedule weekly /mem0:dream --auto` — runs every week
-- `/schedule daily at 3am /mem0:dream --auto` — runs every day at 3 AM
-- `/schedule every Monday 9am /mem0:dream --auto` — runs every Monday at 9 AM
-
-The `/schedule` command handles all the cloud infrastructure: repository cloning,
-environment setup, and cron scheduling. The routine runs as a full Claude Code
-cloud session with access to the mem0 MCP tools.
-
-### Step S3: Confirm to user
-
-After the routine is created, print:
-
-```
-Dream scheduled:
-Routine name: mem0-dream-
-Next run:
-
-Manage at: https://claude.ai/code/routines
-Edit: /schedule list → /schedule update
-Cancel: /schedule list → delete the routine
-```
-
-### Managing scheduled dreams
-
-| Action | Command |
-|---|---|
-| List all routines | `/schedule list` |
-| Run dream now | `/schedule run` (select the dream routine) |
-| Change frequency | `/schedule update` (select the dream routine) |
-| Pause | Toggle off at claude.ai/code/routines |
-| Delete | Delete at claude.ai/code/routines or `/schedule update` |
-
-### Fallback for non-cloud users
-
-If `/schedule` is unavailable (API key auth, no claude.ai subscription), fall back
-to local options:
-
-1. **macOS launchd plist** — generate and install:
- ```bash
- cat > ~/Library/LaunchAgents/com.mem0.dream.plist << 'PLIST'
-
-
-
-
- Labelcom.mem0.dream
- ProgramArguments
-
- claude
- -p
- /mem0:dream --auto
- --allowedTools
- mcp__mem0__*
-
- StartCalendarInterval
-
- Weekday0
- Hour3
- Minute0
-
- StandardOutPath/tmp/mem0-dream.log
- StandardErrorPath/tmp/mem0-dream.err
- WorkingDirectoryPROJECT_DIR
-
-
- PLIST
- launchctl load ~/Library/LaunchAgents/com.mem0.dream.plist
- ```
- Replace `PROJECT_DIR` with the actual project path.
-
-2. **Linux cron** — add entry:
- ```bash
- (crontab -l 2>/dev/null; echo "0 3 * * 0 cd PROJECT_DIR && claude -p '/mem0:dream --auto' >> /tmp/mem0-dream.log 2>&1") | crontab -
- ```
-
-Print which method was used and how to verify:
-```
-Dream scheduled (local: launchd/cron): weekly Sundays 3am
-Verify: launchctl list | grep mem0 # macOS
- crontab -l | grep mem0 # Linux
-```
diff --git a/mem0-plugin/skills/mem0-list-projects/SKILL.md b/mem0-plugin/skills/mem0-list-projects/SKILL.md
deleted file mode 100644
index d5dfd6746..000000000
--- a/mem0-plugin/skills/mem0-list-projects/SKILL.md
+++ /dev/null
@@ -1,71 +0,0 @@
----
-name: mem0-list-projects
-description: >
- List all project scopes (app_ids) that have stored memories for the current
- user. Essential for /mem0:switch-project discoverability and cross-project
- workflows.
- TRIGGER: user runs /mem0:list-projects, or asks "what projects does mem0 know",
- "list my mem0 projects", "show all projects", "which app_ids exist".
----
-
-# 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 and extracting distinct `app_id` values.
-
-Call `get_memories` with:
-- `user_id=`
-- `page_size=200`
-
-Do NOT pass `app_id` — we want memories across ALL projects.
-
-If the response indicates more pages, paginate until all are fetched (up to 1000
-memories max to avoid excessive API calls).
-
-### 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 project, count:
-- Total memories
-- Most recent `created_at` date
-- Top 3 `metadata.type` values by frequency
-
-### Step 3: Display
-
-```
-## mem0 Projects for
-
-| Project | Memories | Last Active | Top Categories |
-|---------|----------|-------------|---------------|
-| | | | decision, convention, anti_pattern |
-| | | | task_learning, environmental |
-| ... | | | |
-
-Active project: ← (current)
-Total: projects, total memories
-```
-
-Mark the current project with `← (current)`.
-
-### Step 4: Empty state
-
-If zero memories found:
-```
-No projects found for user .
-Run /mem0:onboard in a project directory to get started.
-```
-
-### Step 5: Suggest next actions
-
-```
-Switch project: /mem0:switch-project
-Search across all: /mem0:tour --all-projects
-```
diff --git a/mem0-plugin/skills/mem0-onboard/SKILL.md b/mem0-plugin/skills/mem0-onboard/SKILL.md
deleted file mode 100644
index e05ab8b64..000000000
--- a/mem0-plugin/skills/mem0-onboard/SKILL.md
+++ /dev/null
@@ -1,108 +0,0 @@
----
-name: mem0-onboard
-description: >
- Post-install onboarding wizard for the mem0 plugin.
- Detects CLAUDE.md, AGENTS.md, .cursorrules, .windsurfrules, mem0.md
- and offers to import them. Installs coding categories. Shows active identity.
- TRIGGER: user runs /mem0:onboard, or mentions "setup mem0", "configure mem0 plugin".
----
-
-# Mem0 Onboarding Wizard
-
-Run this wizard to set up the mem0 plugin for the current project. Complete in ~30 seconds.
-
-## Step 0: Ensure mem0ai SDK is installed
-
-The plugin installs the `mem0ai` Python SDK automatically on session start via a venv in `${CLAUDE_PLUGIN_DATA}/venv`. If Step 4 (categories) fails with an import error, run:
-
-```bash
-"${CLAUDE_PLUGIN_ROOT}/scripts/ensure_deps.sh"
-```
-
-This is silent and idempotent — safe to run anytime.
-
-## Step 1: Verify API key and MCP connection
-
-Check that mem0 MCP tools are available. Use 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 **any mem0 search tool found**: Proceed to Step 2. The API key is working.
-- If **NOT found**: The MCP server failed to connect. Tell the user:
- 1. "MCP server not connected. Make sure `MEM0_API_KEY` is exported in your shell."
- 2. Show: `export MEM0_API_KEY="m0-your-key-here"` then restart Claude Code.
- 3. If they need a key: https://app.mem0.ai/dashboard/api-keys or `mem0 init --agent --json`
- 4. **STOP here.** Do not proceed — all other steps need MCP tools.
-
-## Step 2: Show identity
-
-Report the active identity to the user:
-- Call `search_memories` with `query="project setup"`, `user_id=`, `filters={"AND": [{"user_id": ""}, {"app_id": ""}]}`, `limit=1` to verify connectivity.
-- Print: `Connected. user=, project=, branch=`
-- If the search fails, troubleshoot the API key.
-
-## Step 3: Detect and import project files
-
-Check for these files in the project root:
-1. `CLAUDE.md`
-2. `AGENTS.md`
-3. `.cursorrules`
-4. `.windsurfrules`
-5. `mem0.md`
-
-For each file found, ask the user: "Found `` ( bytes). Import into mem0? [Y/n]"
-
-If user says yes (or default):
-- Read the file content
-- Call `add_memory` with:
- - `messages=[{"role": "user", "content": "## Project Profile: \n\nProject: \n\n"}]`
- - `user_id=`
- - `app_id=`
- - `metadata={"type": "project_profile", "file": "", "source": "onboard", "branch": ""}`
- - `infer=False`
-
-## Step 4: Install coding categories
-
-Ask: "Install coding categories optimized for development workflows? [Y/n]"
-
-If yes, run the setup script using the plugin's venv python:
-
-```bash
-VENV_PY="${CLAUDE_PLUGIN_DATA}/venv/bin/python3"
-if [ -x "${VENV_PY}" ]; then
- "${VENV_PY}" "${CLAUDE_PLUGIN_ROOT}/scripts/setup_coding_categories.py" --apply
-else
- python3 "${CLAUDE_PLUGIN_ROOT}/scripts/setup_coding_categories.py" --apply
-fi
-```
-
-If the script fails with "mem0ai SDK not found", run the dependency installer first:
-```bash
-"${CLAUDE_PLUGIN_ROOT}/scripts/ensure_deps.sh"
-```
-Then retry the categories script.
-
-## Step 5: Mark project as onboarded
-
-The marker file is already created by `on_session_start.sh` on first display of the onboard prompt. Touch it here for idempotency:
-
-```bash
-_SAFE_PID=$(printf '%s' "" | tr '/:' '--')
-mkdir -p ~/.mem0 && touch ~/.mem0/.onboarded_${_SAFE_PID}
-```
-
-This is silent — no user-facing output needed.
-
-## Step 6: Summary
-
-Print a summary:
-```
-Onboarding complete.
- user_id:
- project_id: (app_id)
- imported: files
- 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.
-```
diff --git a/mem0-plugin/skills/mem0-pin/SKILL.md b/mem0-plugin/skills/mem0-pin/SKILL.md
deleted file mode 100644
index ade371bad..000000000
--- a/mem0-plugin/skills/mem0-pin/SKILL.md
+++ /dev/null
@@ -1,97 +0,0 @@
----
-name: mem0-pin
-description: >
- Pin important memories so they surface prominently and are protected from
- consolidation. New pins use immutable: true (SDK v2.1.33+) to prevent
- deduplication from modifying or removing the memory. Existing memories get
- metadata.pinned: true as a search-time filter; full consolidation protection
- requires delete + re-add. TRIGGER: user runs /mem0:pin , or
- says "pin this memory", "mark as important", "always remember this".
----
-
-# Mem0 Pin
-
-Pin a memory to mark it as high-priority.
-
-## 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, `user_id`, `app_id`, `limit=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 `content` (text) field
-- `original_metadata` — the existing `metadata` dict
-
-This is required because `update_memory` replaces the full memory — a metadata-only call would wipe the text content.
-
-### Step 3: Pin it
-
-There are two sub-cases depending on whether the user wants to pin an **existing** memory or create a **new** pinned memory from scratch.
-
-#### 3a: Pinning a new memory (preferred path for full protection)
-
-Use `add_memory` with `immutable: true` as a top-level parameter. The `immutable` flag is a first-class platform parameter (added in SDK v2.1.33) that prevents the memory from being modified or removed by consolidation/deduplication. `metadata.pinned: true` is also included as a search-time filter aid.
-
-```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=`
-- `data=` (preserve the existing content)
-- `metadata=` merge `original_metadata` with `{"pinned": true}`
-
-```python
-updated_meta = {**original_metadata, "pinned": True}
-update_memory(memory_id=, data=, metadata=updated_meta)
-```
-
-**Important:** `update_memory` requires the `data` (text) parameter. Passing only metadata may error or wipe content. Always read first, then update with the full text and explicit metadata.
-
-**Consolidation-protection limitation:** `update_memory` cannot set the `immutable` flag on an existing memory. If the user needs full consolidation protection for an existing memory, they must delete it and re-add it using `add_memory` with `immutable: true` (path 3a above). In the confirm step (Step 4), note this limitation if the user pinned an existing memory via `update_memory`.
-
-### Step 4: Confirm
-
-For new pins created via `add_memory` with `immutable: true`:
-```
-Pinned: "..."
-Memory ID:
-Pinned with consolidation protection (immutable). This memory will not be modified or removed by deduplication.
-```
-
-For existing memories pinned via `update_memory`:
-```
-Pinned: "..."
-Memory ID:
-Pinned memories surface first when relevant to a search.
-Note: consolidation protection (immutable flag) requires delete + re-add. Say "re-pin this with full protection" to do that.
-```
-
-### Unpin
-
-If the user says "unpin" or `/mem0:unpin`:
-1. Call `get_memory` to read current content and metadata.
-2. Set `metadata.pinned = false` explicitly:
- ```python
- updated_meta = {**original_metadata, "pinned": False}
- update_memory(memory_id=, data=, metadata=updated_meta)
- ```
-3. Print: `Unpinned: "..."`
diff --git a/mem0-plugin/skills/mem0-stats/SKILL.md b/mem0-plugin/skills/mem0-stats/SKILL.md
deleted file mode 100644
index 5f9215f4a..000000000
--- a/mem0-plugin/skills/mem0-stats/SKILL.md
+++ /dev/null
@@ -1,149 +0,0 @@
----
-name: mem0-stats
-description: >
- Show memory statistics for the current session and project lifetime.
- Combines local session counters with API-fetched totals.
- TRIGGER: user runs /mem0:stats, or asks "how many memories", "mem0 stats",
- "memory usage", "show memory count".
----
-
-# Mem0 Stats
-
-Show session and lifetime memory statistics.
-
-## Execution
-
-### Step 1: Gather session stats
-
-Run the session stats reporter:
-
-```bash
-SCRIPT_DIR="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}/scripts"
-python3 "$SCRIPT_DIR/session_stats.py" peek 2>/dev/null || echo "{}"
-```
-
-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
-
-Call `get_memories` with:
-- `user_id=`
-- `app_id=`
-- `page_size=100`
-
-Count the returned memories. Group them by:
-1. `categories[0]` (platform-assigned) — primary grouping
-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).
-
-### Step 3: Display
-
-Print a compact dashboard with an ASCII histogram for category distribution:
-
-```
-## mem0 Stats
-
-### This Session
- Memories written:
- Searches run:
- Categories touched:
-
-### Project Lifetime ()
- Total memories:
-
- By category:
- decision ████████████████ 24
- convention ██████████░░░░░░ 15
- anti_pattern ████░░░░░░░░░░░░ 6
- task_learning ███░░░░░░░░░░░░░ 5
- user_preference ██░░░░░░░░░░░░░░ 3
- session_state █░░░░░░░░░░░░░░░ 2
-
- By age:
- < 7 days ████████████████ 5
- 7–30 days ██████████░░░░░░ 12
- 30–90 days ████░░░░░░░░░░░░ 10
- > 90 days ██░░░░░░░░░░░░░░ 8
-
- By access count:
- Never accessed ████████████████ 18
- 1–5 accesses ████████░░░░░░░░ 10
- 6–20 accesses ████░░░░░░░░░░░░ 4
- 20+ accesses █░░░░░░░░░░░░░░░ 3
-
- Oldest memory:
- Newest memory:
-
-### Health
- API latency: ms
- User:
- Project:
- Branch:
-```
-
-**Histogram rules:**
-- Max bar width: 16 characters. Scale all bars relative to the highest count.
-- Use `█` for filled and `░` for empty. Right-align the count number.
-- Sort categories by count descending. Omit categories with 0 memories.
-- If only 1-2 categories exist, still show the histogram — it provides visual context.
-- **Age buckets:** Compute from `created_at`. Buckets: <7d, 7–30d, 30–90d, >90d.
-- **Access count buckets:** Read `metadata.access_count` (default 0 if absent). Buckets: 0, 1–5, 6–20, 20+.
-
-Skip any section with zero data.
-
-## 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>"}}]}`, `limit=20`
-2. `query="bugs errors fixes"`, same time filter, `limit=20`
-3. `query="patterns conventions learnings"`, same time filter, `limit=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:
-
-```
-### Weekly Digest ( to )
-
-New Memories This Week:
- :
- -