feat(mem0-plugin): improve auto-triggering — pre-fetch, dedup, skill enforcement v0.2.3 (#5237)
This commit is contained in:
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
|
||||
<CodeGroup>
|
||||
```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
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
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`
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Mem0 MCP Setup" icon="puzzle-piece" href="/platform/mem0-mcp">
|
||||
|
||||
@@ -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"
|
||||
<CodeGroup>
|
||||
```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
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Installation
|
||||
|
||||
### Option A — Direct MCP (Recommended)
|
||||
|
||||
@@ -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"
|
||||
<CodeGroup>
|
||||
```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
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Warning>
|
||||
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.
|
||||
</Warning>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"url": "https://mcp.mem0.ai/mcp/",
|
||||
"headers": {
|
||||
"Authorization": "Token ${MEM0_API_KEY}"
|
||||
}
|
||||
},
|
||||
"authorizationUrl": "https://mcp.mem0.ai/authorize"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
```
|
||||
[<type>] <content, max 80 chars> [mem0:<short_id>]
|
||||
```
|
||||
|
||||
## 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
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()))
|
||||
@@ -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 <<EOF
|
||||
# Extract query (first 80 chars of error line)
|
||||
ERROR_QUERY=$(echo "$ERROR_LINE" | cut -c1-80)
|
||||
|
||||
## Error detected in command output
|
||||
|
||||
\`$COMMAND\` produced an error:
|
||||
> $ERROR_LINE
|
||||
|
||||
EOF
|
||||
|
||||
if [ -n "$FILE_DISPLAY" ]; then
|
||||
cat <<EOF
|
||||
**Files in stack trace:**
|
||||
$FILE_DISPLAY
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
Search mem0 for prior occurrences — this error may have been seen before:
|
||||
- \`search_memories(query="$(echo "$ERROR_LINE" | cut -c1-60)", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "anti_pattern"}}]})\`
|
||||
- \`search_memories(query="$(echo "$ERROR_LINE" | cut -c1-60)", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "bug_fix"}}]})\`
|
||||
EOF
|
||||
|
||||
if [ -n "$TRACE_FILES" ]; then
|
||||
FIRST_FILE=$(echo "$TRACE_FILES" | head -1 | sed 's/:[0-9]*//')
|
||||
cat <<EOF
|
||||
- \`search_memories(query="$FIRST_FILE", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}]})\`
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
If mem0 returns relevant context, use it to debug faster.
|
||||
If you solve this, store the fix as an \`anti_pattern\` or \`bug_fix\` memory for next time.
|
||||
EOF
|
||||
|
||||
# Telemetry
|
||||
# Telemetry (fire regardless of API key)
|
||||
python3 "$SCRIPT_DIR/telemetry.py" bash_error --error_detected 2>/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
|
||||
|
||||
Executable
+65
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 <<BANNER
|
||||
## Mem0 — First Run Setup
|
||||
|
||||
\`user=${MEM0_RESOLVED_USER_ID:-$USER} | project=${MEM0_PROJECT_ID:-unknown} | branch=${MEM0_BRANCH:-unknown} | auth=none\`
|
||||
|
||||
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):
|
||||
|
||||
\`\`\`
|
||||
Mem0 Active | user=${MEM0_RESOLVED_USER_ID:-$USER} | project=${MEM0_PROJECT_ID:-unknown} | branch=${MEM0_BRANCH:-unknown} | auth=none
|
||||
\`\`\`
|
||||
|
||||
BANNER
|
||||
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. Set up API key (get from dashboard, export to .zshrc, verify)
|
||||
2. MCP OAuth login (authenticate via browser at mcp.mem0.ai)
|
||||
3. Verify connectivity and show identity
|
||||
4. Detect and import project files (CLAUDE.md, AGENTS.md, .cursorrules, etc.)
|
||||
5. Install coding categories optimized for development
|
||||
6. 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.
|
||||
EOF
|
||||
exit 0
|
||||
elif [ -f "$_ONBOARD_MARKER" ]; then
|
||||
# State 2: OAuth-authed — onboarded previously without API key
|
||||
MEM0_AUTH_MODE="oauth"
|
||||
else
|
||||
# State 3: Not first startup, not onboarded — inactive
|
||||
cat <<BANNER
|
||||
## Mem0 Inactive
|
||||
|
||||
\`user=${MEM0_RESOLVED_USER_ID:-$USER} | project=${MEM0_PROJECT_ID:-unknown} | branch=${MEM0_BRANCH:-unknown} | auth=none\`
|
||||
|
||||
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):
|
||||
|
||||
\`\`\`
|
||||
Mem0 Inactive | user=${MEM0_RESOLVED_USER_ID:-$USER} | project=${MEM0_PROJECT_ID:-unknown} | branch=${MEM0_BRANCH:-unknown} | auth=none
|
||||
\`\`\`
|
||||
|
||||
Set MEM0_API_KEY to enable persistent memory. Get a key at https://app.mem0.ai/dashboard/api-keys or run \`/mem0:onboard\` to set up.
|
||||
BANNER
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for failed dependency installation and warn the user
|
||||
@@ -51,8 +96,9 @@ if [ -f "${_DATA_DIR}/.install-failed" ]; then
|
||||
fi
|
||||
|
||||
# Fetch project-scoped memory count (best-effort, don't block on failure, 5s timeout)
|
||||
# Skip REST call when OAuth-authed — no API key to authenticate with
|
||||
MEM0_COUNT="?"
|
||||
if command -v python3 >/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 <<BANNER
|
||||
## Mem0 Active
|
||||
|
||||
\`user=$MEM0_RESOLVED_USER_ID | project=$MEM0_PROJECT_ID | branch=$MEM0_BRANCH | memories=$MEM0_COUNT | auth=$MEM0_AUTH_MODE\`
|
||||
|
||||
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):
|
||||
|
||||
\`\`\`
|
||||
Mem0 Active | user=$MEM0_RESOLVED_USER_ID | project=$MEM0_PROJECT_ID | branch=$MEM0_BRANCH | memories=$MEM0_COUNT
|
||||
\`\`\`
|
||||
|
||||
Always include \`user_id\` + \`app_id\` in every \`search_memories\` filter and \`add_memory\` call:
|
||||
- user_id: \`$MEM0_RESOLVED_USER_ID\`
|
||||
- app_id: \`$MEM0_PROJECT_ID\` (project scope — passed as top-level \`app_id\`, NOT in metadata)
|
||||
|
||||
BANNER
|
||||
|
||||
# Load mem0.md project config if present (best-effort, non-blocking)
|
||||
MEM0_PROJECT_CONFIG=""
|
||||
@@ -121,32 +169,12 @@ if [ "$SOURCE" = "startup" ]; then
|
||||
_SAFE_PID=$(printf '%s' "$MEM0_PROJECT_ID" | tr '/:' '--')
|
||||
_ONBOARD_MARKER="$HOME/.mem0/.onboarded_${_SAFE_PID}"
|
||||
if [ ! -f "$_ONBOARD_MARKER" ]; then
|
||||
mkdir -p "$HOME/.mem0" 2>/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
|
||||
|
||||
|
||||
@@ -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 "")
|
||||
|
||||
@@ -25,16 +25,7 @@ _PROJECT="${MEM0_PROJECT_ID:-unknown}"
|
||||
cat <<EOF
|
||||
Task completed: "$TASK_SUBJECT"
|
||||
|
||||
Extract key learnings from this completed task and store them using the mem0 \`add_memory\` tool:
|
||||
|
||||
1. What strategy worked well? -> 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <<EOF
|
||||
## Memory check
|
||||
if [ -n "$HAS_RESUME" ]; then
|
||||
RESUME_RESULTS=$(PYTHONPATH="$SCRIPT_DIR" MEM0_SEARCH_USER="$USER_ID" python3 -c "
|
||||
import os, sys
|
||||
sys.path.insert(0, os.environ.get('PYTHONPATH', '.'))
|
||||
from _search import search_memories, format_results_for_context
|
||||
|
||||
Before responding, decide whether persistent memory context from mem0 would
|
||||
improve your answer. The agent -- not this hook -- owns this decision.
|
||||
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')
|
||||
|
||||
**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
|
||||
state = search_memories(api_key, user_id, project_id, 'session state current task', metadata_type='session_state', top_k=3)
|
||||
decisions = search_memories(api_key, user_id, project_id, 'recent decisions and learnings', metadata_type='decision', top_k=3)
|
||||
|
||||
**Skip WHEN:**
|
||||
- the prompt is an acknowledgement or continuation
|
||||
- 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
|
||||
all_r = state + decisions
|
||||
seen = set()
|
||||
unique = []
|
||||
for m in all_r:
|
||||
mid = m.get('id', '')
|
||||
if mid not in seen:
|
||||
seen.add(mid)
|
||||
unique.append(m)
|
||||
|
||||
if unique:
|
||||
print(format_results_for_context(unique, heading='Session context recovered from mem0'))
|
||||
print()
|
||||
print('Use these memories to resume work. Do NOT ask the user to repeat context that is already in these memories.')
|
||||
else:
|
||||
print('No session state found in mem0. Ask the user what they want to continue.')
|
||||
" 2>/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 <<EOF
|
||||
Search mem0 when the user references past work, asks decision questions, hits errors, or starts non-trivial tasks. Skip for acknowledgements, new info (store instead), or pure factual questions.
|
||||
|
||||
**Search tips:** Use noun-phrase queries, run 2-4 parallel calls with different \`metadata.type\` filters (decision, anti_pattern, user_preference, convention). Always include \`user_id\` + \`app_id\` in filters. Empty results are normal.
|
||||
EOF
|
||||
touch "$RUBRIC_FLAG" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -n "$HAS_ERROR" ]; then
|
||||
cat <<EOF
|
||||
|
||||
**ERROR DETECTED in prompt.** You SHOULD search mem0 for prior occurrences:
|
||||
- \`search_memories(query="<error class or message>", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "anti_pattern"}}]})\`
|
||||
- \`search_memories(query="<module or file from stack trace>", 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 <<EOF
|
||||
|
||||
**FILE PATHS detected:** \`$FILE_PATHS\`
|
||||
Search mem0 for context about these files using the \`contains\` operator on \`metadata.files\`:
|
||||
- \`search_memories(query="<filename>", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata.files": {"contains": "<filename>"}}]})\`
|
||||
- Also run a broader text search without the files filter as fallback:
|
||||
- \`search_memories(query="<filename without extension>", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}]})\`
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
**If searching, do it well:**
|
||||
- Run **2-4 parallel** \`search_memories\` calls with different angles, not one
|
||||
query that echoes the user's prompt.
|
||||
- Phrase queries as **nouns** ("auth module decisions"), not full sentences.
|
||||
- Filter shape: the root must be a logical operator (\`AND\` / \`OR\` / \`NOT\`)
|
||||
with an array, and metadata uses a **nested** object (not dotted keys).
|
||||
Combine \`user_id\` + \`app_id\` with one \`metadata.type\` clause per call:
|
||||
- \`{"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "decision"}}]}\` -- design / architecture
|
||||
- \`{"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "anti_pattern"}}]}\` -- debugging, error handling
|
||||
- \`{"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "user_preference"}}]}\` -- tooling, stack, style
|
||||
- \`{"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "convention"}}]}\` -- established patterns
|
||||
- Or scope with just \`{"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}]}\` when no metadata filter fits.
|
||||
- **Recency boost:** For state-related queries ("where were we", "current task", "latest"), add a \`created_at\` filter: \`{"created_at": {"gte": "<90 days ago YYYY-MM-DD>"}}\`. Skip recency for durable facts (conventions, decisions).
|
||||
- Empty results are normal -- proceed without context.
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -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 <N> memories for "<task summary>"
|
||||
- [decision] <content> [mem0:<short_id>]
|
||||
- [convention] <content> [mem0:<short_id>]
|
||||
- [anti_pattern] <content> [mem0:<short_id>]
|
||||
```
|
||||
|
||||
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
|
||||
@@ -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 "<PLUGIN_ROOT>/scripts/parse_mem0_config.py" "<cwd>"
|
||||
```
|
||||
|
||||
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": "<active_user_id>"}, {"app_id": "<active_project_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 <project_id>. 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 (<N>):
|
||||
[mem0:<id1>] + [mem0:<id2>] → "<merged content, 100 chars>"
|
||||
|
||||
Conflicts (<N>):
|
||||
[mem0:<idA>] vs [mem0:<idB>] — "<topic>" [A/B/skip]
|
||||
|
||||
Prune (<N>):
|
||||
[mem0:<id>] — <type>, <age>d old
|
||||
|
||||
Proposed: <N> merges, <N> prunes, <N> 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(<id1>)`
|
||||
2. `delete_memory(<id2>)`
|
||||
3. `add_memory` with:
|
||||
- `messages=[{"role": "user", "content": "<merged content>"}]`
|
||||
- `user_id=<active_user_id>`
|
||||
- `app_id=<active_project_id>` (top-level, not in metadata)
|
||||
- `metadata={"type": "<original type>", "branch": "<active_branch>", "confidence": <higher of the two original scores>, "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(<loser_id>)` to read its current text content.
|
||||
- Then call `update_memory(<loser_id>, text=<original_text_content>)` 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(<memory_id>)`
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Print Summary
|
||||
|
||||
After all changes are applied, print:
|
||||
|
||||
```
|
||||
Dream complete — merged: <N>, pruned: <N>, conflicts resolved: <N>, skipped: <N>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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=<id> merged=<N> pruned=<N> conflicts_skipped=<N>
|
||||
```
|
||||
4. If contradictions were detected but skipped, store a reminder memory:
|
||||
```python
|
||||
add_memory(
|
||||
messages=[{"role": "user", "content": "mem0-dream detected <N> contradiction(s) requiring manual review. Run /mem0:dream to resolve them interactively."}],
|
||||
user_id="<active_user_id>",
|
||||
app_id="<active_project_id>",
|
||||
metadata={"type": "task_learning", "source": "mem0-dream-auto", "branch": "<active_branch>"},
|
||||
infer=False,
|
||||
)
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- `/mem0:forget` — targeted deletion of specific memories (search + confirm + delete)
|
||||
- `/mem0:health --deep` — quick quality scan without applying changes
|
||||
@@ -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
|
||||
@@ -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 <memory_id>`
|
||||
|
||||
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: "<memory content first 120 chars>" (created <date>)`
|
||||
|
||||
**If search query provided:**
|
||||
- Call `search_memories` with:
|
||||
- `query=<user's query>`
|
||||
- `filters={"AND": [{"user_id": "<active_user_id>"}, {"app_id": "<project_id>"}]}`
|
||||
- `top_k=10`
|
||||
- Show numbered list:
|
||||
```
|
||||
Found <N> memories matching "<query>":
|
||||
1. <content, 120 chars> (type: <type>, created: <date>) [ID: <short_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 <N> 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 <search query>` to find specific ones."
|
||||
@@ -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: <N>
|
||||
|
||||
```
|
||||
## Memory Quality
|
||||
| Metric | Count | Action |
|
||||
|----------------|-------|---------------------------------|
|
||||
| Duplicates | <N> | Run /mem0:dream to merge |
|
||||
| Stale | <N> | Run /mem0:dream to prune |
|
||||
| Contradictions | <N> | Run /mem0:dream to resolve |
|
||||
| Orphans | <N> | Consider retagging via MCP |
|
||||
|
||||
Duplicates: <N> · Stale: <N> · Contradictions: <N> · Orphans: <N>
|
||||
```
|
||||
|
||||
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: <N> stale, <N> low-confidence
|
||||
Retagged: <N> orphans
|
||||
Skipped: <N> duplicates (use /mem0:dream), <N> contradictions (use /mem0:dream)
|
||||
```
|
||||
To fix issues found by `--deep`, run `/mem0:dream` for automated consolidation (merges, prunes, conflict resolution).
|
||||
@@ -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: <N> memories
|
||||
Copilot: <N> memories
|
||||
Total: <N> memories imported into project <project_id>
|
||||
Imported <N> memories into <project_id> (cursor: <N>, copilot: <N>)
|
||||
```
|
||||
|
||||
Notes: `infer=False`, tagged `metadata.source=<tool>-import`, sections <50 chars
|
||||
@@ -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": "<active_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
|
||||
|
||||
<app_id_1> <count> memories (last: <date>) ← current
|
||||
<app_id_2> <count> memories (last: <date>)
|
||||
|
||||
<N> projects, <M> 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.
|
||||
```
|
||||
@@ -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 "<PLUGIN_ROOT>/scripts/parse_mem0_config.py" "<cwd>"
|
||||
```
|
||||
|
||||
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="<active_user_id>",
|
||||
app_id="<active_project_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 <project_id>. 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 (<N> pairs)
|
||||
MERGE [mem0:<id1>] + [mem0:<id2>] → NEW
|
||||
- Original 1: "<content of memory 1, truncated to 120 chars>"
|
||||
- Original 2: "<content of memory 2, truncated to 120 chars>"
|
||||
- Merged: "<drafted merged content>"
|
||||
|
||||
### Contradictions (<N> pairs)
|
||||
CONFLICT [mem0:<idA>] vs [mem0:<idB>]
|
||||
- A: "<content>" (<created_at date>, confidence: <score>)
|
||||
- B: "<content>" (<created_at date>, confidence: <score>)
|
||||
Which is current? [A/B/skip]
|
||||
|
||||
### Prune candidates (<N> memories)
|
||||
PRUNE [mem0:<id>] — <metadata.type>, <age>d old (policy: <policy_days>d)
|
||||
|
||||
---
|
||||
Proposed: <N> merges, <N> prunes, <N> 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(<id1>)`
|
||||
2. `delete_memory(<id2>)`
|
||||
3. `add_memory` with:
|
||||
- `messages=[{"role": "user", "content": "<merged content>"}]`
|
||||
- `user_id=<active_user_id>`
|
||||
- `app_id=<active_project_id>` (top-level, not in metadata)
|
||||
- `metadata={"type": "<original type>", "branch": "<active_branch>", "confidence": <higher of the two original scores>, "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(<loser_id>)` to read its current text content.
|
||||
- Then call `update_memory(<loser_id>, data=<original_text_content>)` 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(<memory_id>)`
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Print Summary
|
||||
|
||||
After all changes are applied, print:
|
||||
|
||||
```
|
||||
Dream complete.
|
||||
Merged: <N> pairs → <N> new memories
|
||||
Pruned: <N> memories deleted
|
||||
Flagged: <N> contradictions resolved, <N> 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=<id> merged=<N> pruned=<N> conflicts_skipped=<N>
|
||||
```
|
||||
4. If contradictions were detected but skipped, store a reminder memory:
|
||||
```python
|
||||
add_memory(
|
||||
messages=[{"role": "user", "content": "mem0-dream detected <N> contradiction(s) requiring manual review. Run /mem0:dream to resolve them interactively."}],
|
||||
user_id="<active_user_id>",
|
||||
app_id="<active_project_id>",
|
||||
metadata={"type": "task_learning", "source": "mem0-dream-auto", "branch": "<active_branch>"},
|
||||
infer=False,
|
||||
)
|
||||
```
|
||||
|
||||
## Forget mode (targeted deletion)
|
||||
|
||||
When invoked with `--forget` (e.g., `/mem0:dream --forget auth module decisions`
|
||||
or `/mem0:dream --forget <memory_id>`), 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 <memory_id>`
|
||||
|
||||
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: "<memory content first 120 chars>" (created <date>)`
|
||||
|
||||
**If search query provided:**
|
||||
- Call `search_memories` with:
|
||||
- `query=<user's query>`
|
||||
- `filters={"AND": [{"user_id": "<id>"}, {"app_id": "<project_id>"}]}`
|
||||
- `limit=10`
|
||||
- Show numbered list:
|
||||
```
|
||||
Found <N> memories matching "<query>":
|
||||
1. <content, 120 chars> (type: <type>, created: <date>) [ID: <short_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 <N> 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 <frequency> /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: <frequency>
|
||||
Routine name: mem0-dream-<project_id>
|
||||
Next run: <next scheduled time>
|
||||
|
||||
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'
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key><string>com.mem0.dream</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>claude</string>
|
||||
<string>-p</string>
|
||||
<string>/mem0:dream --auto</string>
|
||||
<string>--allowedTools</string>
|
||||
<string>mcp__mem0__*</string>
|
||||
</array>
|
||||
<key>StartCalendarInterval</key>
|
||||
<dict>
|
||||
<key>Weekday</key><integer>0</integer>
|
||||
<key>Hour</key><integer>3</integer>
|
||||
<key>Minute</key><integer>0</integer>
|
||||
</dict>
|
||||
<key>StandardOutPath</key><string>/tmp/mem0-dream.log</string>
|
||||
<key>StandardErrorPath</key><string>/tmp/mem0-dream.err</string>
|
||||
<key>WorkingDirectory</key><string>PROJECT_DIR</string>
|
||||
</dict>
|
||||
</plist>
|
||||
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
|
||||
```
|
||||
@@ -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=<active_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 <user_id>
|
||||
|
||||
| Project | Memories | Last Active | Top Categories |
|
||||
|---------|----------|-------------|---------------|
|
||||
| <app_id_1> | <count> | <date> | decision, convention, anti_pattern |
|
||||
| <app_id_2> | <count> | <date> | task_learning, environmental |
|
||||
| ... | | | |
|
||||
|
||||
Active project: <current_project_id> ← (current)
|
||||
Total: <N> projects, <M> total memories
|
||||
```
|
||||
|
||||
Mark the current project with `← (current)`.
|
||||
|
||||
### Step 4: Empty state
|
||||
|
||||
If zero memories found:
|
||||
```
|
||||
No projects found for user <user_id>.
|
||||
Run /mem0:onboard in a project directory to get started.
|
||||
```
|
||||
|
||||
### Step 5: Suggest next actions
|
||||
|
||||
```
|
||||
Switch project: /mem0:switch-project <name>
|
||||
Search across all: /mem0:tour --all-projects
|
||||
```
|
||||
@@ -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=<active_user_id>`, `filters={"AND": [{"user_id": "<active_user_id>"}, {"app_id": "<active_project_id>"}]}`, `limit=1` to verify connectivity.
|
||||
- Print: `Connected. user=<user_id>, project=<project_id>, branch=<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 `<filename>` (<size> 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: <filename>\n\nProject: <project_id>\n\n<file_content>"}]`
|
||||
- `user_id=<active_user_id>`
|
||||
- `app_id=<active_project_id>`
|
||||
- `metadata={"type": "project_profile", "file": "<filename>", "source": "onboard", "branch": "<active_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' "<active_project_id>" | 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: <user_id>
|
||||
project_id: <project_id> (app_id)
|
||||
imported: <N> files
|
||||
categories: <installed or skipped>
|
||||
|
||||
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.
|
||||
```
|
||||
@@ -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 <query or ID>, 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": "<text to pin>"}],
|
||||
user_id=<user_id>,
|
||||
immutable=True,
|
||||
metadata={"pinned": True},
|
||||
)
|
||||
```
|
||||
|
||||
#### 3b: Pinning an existing memory
|
||||
|
||||
Call `update_memory` with:
|
||||
- `memory_id=<selected_id>`
|
||||
- `data=<original_text>` (preserve the existing content)
|
||||
- `metadata=` merge `original_metadata` with `{"pinned": true}`
|
||||
|
||||
```python
|
||||
updated_meta = {**original_metadata, "pinned": True}
|
||||
update_memory(memory_id=<selected_id>, data=<original_text>, 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 content, first 80 chars>..."
|
||||
Memory ID: <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 content, first 80 chars>..."
|
||||
Memory ID: <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=<id>, data=<original_text>, metadata=updated_meta)
|
||||
```
|
||||
3. Print: `Unpinned: "<content>..."`
|
||||
@@ -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=<active_user_id>`
|
||||
- `app_id=<active_project_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: <N>
|
||||
Searches run: <N>
|
||||
Categories touched: <list>
|
||||
|
||||
### Project Lifetime (<project_id>)
|
||||
Total memories: <N>
|
||||
|
||||
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: <date>
|
||||
Newest memory: <date>
|
||||
|
||||
### Health
|
||||
API latency: <N>ms
|
||||
User: <user_id>
|
||||
Project: <project_id>
|
||||
Branch: <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": "<id>"}, {"app_id": "<pid>"}, {"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 (<start_date> to <today>)
|
||||
|
||||
New Memories This Week: <N>
|
||||
<category>: <count>
|
||||
- <memory summary, 80 chars> (<date>)
|
||||
- ...
|
||||
|
||||
Activity Pattern
|
||||
Most active day: <day> (<N> memories)
|
||||
Categories touched: <list>
|
||||
|
||||
Highlights
|
||||
<2-3 sentence summary of most important decisions/learnings this week>
|
||||
```
|
||||
|
||||
### W4: Write digest file
|
||||
|
||||
Write to `~/.mem0/weekly-digest.md` (overwrite). Append one-line to
|
||||
`~/.mem0/digest-history.log`:
|
||||
```
|
||||
<YYYY-MM-DD> | <project_id> | +<new_count> memories | top: <top_category>
|
||||
```
|
||||
|
||||
### W5: Empty state
|
||||
|
||||
If no new memories in 7 days:
|
||||
```
|
||||
No new memories in the past week for <project_id>.
|
||||
Total project memories: <N>.
|
||||
```
|
||||
@@ -1,17 +1,6 @@
|
||||
---
|
||||
name: mem0
|
||||
description: >
|
||||
Mem0 Platform SDK for adding persistent memory to AI applications.
|
||||
TRIGGER when: user mentions "mem0", "MemoryClient", "memory layer",
|
||||
"remember user preferences", "persistent context", "personalization",
|
||||
or needs to add long-term memory to chatbots, agents, or AI apps.
|
||||
Covers Python SDK (mem0ai), TypeScript SDK (mem0ai), and framework integrations
|
||||
(LangChain, CrewAI, OpenAI Agents SDK, Pipecat, LlamaIndex, AutoGen, LangGraph).
|
||||
Also covers the open-source self-hosted Memory class.
|
||||
This is the DEFAULT mem0 skill for ambiguous queries.
|
||||
DO NOT TRIGGER when: user asks about CLI commands, terminal usage, or shell
|
||||
scripts (use mem0-cli), or Vercel AI SDK / @mem0/vercel-ai-provider / createMem0
|
||||
(use mem0-vercel-ai-sdk).
|
||||
description: Mem0 SDK reference — Python, TypeScript, and integrations
|
||||
license: Apache-2.0
|
||||
metadata:
|
||||
author: mem0ai
|
||||
@@ -25,7 +14,6 @@ compatibility: Requires Python 3.10+ or Node.js 18+, pip install mem0ai or npm i
|
||||
|
||||
> **Skill Graph:** This skill is part of the Mem0 skill graph:
|
||||
> - **mem0** (this skill) -- Platform Client SDK + OSS (Python + TypeScript)
|
||||
> - **[mem0-cli](https://github.com/mem0ai/mem0/tree/main/skills/mem0-cli)** -- Command-line interface
|
||||
> - **[mem0-vercel-ai-sdk](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk)** -- Vercel AI SDK provider
|
||||
|
||||
Mem0 is a managed memory layer for AI applications. It stores, retrieves, and manages user memories via API — no infrastructure to deploy. For self-hosted usage, see the OSS section in the client references below.
|
||||
@@ -46,7 +34,7 @@ export MEM0_API_KEY="m0-your-api-key"
|
||||
|
||||
Get an API key at: https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-skill
|
||||
|
||||
> **Don't have a `MEM0_API_KEY`?** Run `mem0 init --agent --json` (after `pip install mem0-cli` or `npm install -g @mem0/cli`) to mint an evaluation key without email or dashboard. The human can claim later with `mem0 init --email <your-email>`.
|
||||
> **Don't have a `MEM0_API_KEY`?** Sign up at https://app.mem0.ai and create one from the dashboard. Keys start with `m0-`.
|
||||
|
||||
## Step 2: Initialize the client
|
||||
|
||||
@@ -134,20 +122,27 @@ def chat(user_input: str, user_id: str) -> str:
|
||||
|
||||
## Common edge cases
|
||||
|
||||
- **Search returns empty:** Memories process asynchronously. Wait 2-3s after `add()` before searching. Also verify `user_id` matches exactly (case-sensitive) and use `filters={"user_id": "..."}` syntax.
|
||||
- **AND filter with user_id + agent_id returns empty:** Entities are stored separately. Use `OR` instead, or query separately.
|
||||
- **Duplicate memories:** Don't mix `infer=True` (default) and `infer=False` for the same data. Stick to one mode.
|
||||
- **Wrong import:** Always use `from mem0 import MemoryClient` (or `AsyncMemoryClient` for async). Do not use `from mem0 import Memory`.
|
||||
- **v3 defaults:** `top_k=20`, `threshold=0.1`, `rerank=False`. Adjust as needed for your use case.
|
||||
- **Search returns empty:** v3 processes `add()` asynchronously — returns an event ID immediately. Wait 2-3s before searching. Also verify `user_id` matches exactly (case-sensitive) and use `filters={"user_id": "..."}` syntax.
|
||||
- **AND filter with user_id + agent_id returns empty:** Entities are stored separately. `{"AND": [{"user_id": "alice"}, {"agent_id": "bot"}]}` returns nothing. Use `OR` instead, or query each separately.
|
||||
- **Duplicate memories:** Don't mix `infer=True` (default) and `infer=False` for the same data. `infer=True` extracts facts via LLM with dedup. `infer=False` stores raw — same text can be stored twice.
|
||||
- **Implicit null scoping:** `filters={"user_id": "alice"}` only returns memories where `agent_id`, `app_id`, `run_id` are ALL null. Wrap in `{"OR": [...]}` to include memories with non-null scoping fields.
|
||||
- **Platform vs OSS imports:** Platform: `from mem0 import MemoryClient`. OSS: `from mem0 import Memory`. Don't mix them — `MemoryClient` talks to `api.mem0.ai`, `Memory` runs locally.
|
||||
- **v3 defaults:** `top_k=20`, `threshold=0.1`, `rerank=False`. Adjust as needed.
|
||||
|
||||
## v2 Compatibility
|
||||
## v3 API (Current)
|
||||
|
||||
If you're using SDK v2.x, note these differences:
|
||||
- **Entity IDs:** Pass `user_id` as top-level kwarg to `search()` instead of inside `filters`
|
||||
- **Defaults:** `top_k=100`, no threshold, `rerank=True`
|
||||
- **Graph memory:** Available via `enable_graph=True`
|
||||
Mem0 v3 uses single-pass extraction, entity linking, and multi-signal retrieval.
|
||||
|
||||
See the [migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for details.
|
||||
**Key v3 changes from v2:**
|
||||
- **Endpoints:** `POST /v3/memories/add/`, `POST /v3/memories/search/`, `POST /v3/memories/` (paginated list)
|
||||
- **Extraction:** Single ADD-only pass — no more UPDATE/DELETE operations during extraction. Memories accumulate rather than consolidate.
|
||||
- **Entity linking:** Replaces graph memory. Auto-extracted during `add()`, no config needed. Remove `enable_graph` and `graph_store` from any old config.
|
||||
- **Defaults:** `top_k=20`, `threshold=0.1`, `rerank=False`
|
||||
- **Removed params:** `org_id`, `project_id`, `enable_graph` — all removed from SDK
|
||||
- **TypeScript:** Exclusively camelCase (`userId`, `agentId`, `appId`, `topK`)
|
||||
- **Add response:** Async — returns event ID immediately, poll via `GET /v1/event/{event_id}/`
|
||||
|
||||
See the [migration guide](https://docs.mem0.ai/migration/platform-v2-to-v3) for details.
|
||||
|
||||
## Live documentation search
|
||||
|
||||
@@ -189,5 +184,4 @@ Load these on demand for deeper detail:
|
||||
|
||||
| Skill | When to use | Link |
|
||||
|-------|-------------|------|
|
||||
| mem0-cli | Terminal commands, scripting, CI/CD, agent tool loops | [GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0-cli) |
|
||||
| mem0-vercel-ai-sdk | Vercel AI SDK provider with automatic memory | [GitHub](https://github.com/mem0ai/mem0/tree/main/skills/mem0-vercel-ai-sdk) |
|
||||
|
||||
@@ -69,7 +69,7 @@ If you were using `enable_graph=True` in v2:
|
||||
- Remove `graph_store` from OSS configuration
|
||||
- Entity relationships are now consumed through retrieval ranking, not exposed as a separate `relations` array
|
||||
|
||||
See the [v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for details.
|
||||
See the [v2 to v3 migration guide](https://docs.mem0.ai/migration/platform-v2-to-v3) for details.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
name: memory-reviewer
|
||||
description: Review memory quality — duplicates, contradictions, stale entries
|
||||
---
|
||||
|
||||
# Memory Reviewer
|
||||
|
||||
Audits memory quality for the active project. Finds duplicates, contradictions, and low-confidence entries.
|
||||
|
||||
## When to use
|
||||
|
||||
- User asks "check my memories", "memory quality", "any duplicates?"
|
||||
- User runs `/mem0:memory-reviewer` directly
|
||||
- After a session with 5+ memory writes (suggest proactively)
|
||||
- After `/mem0:health --deep` identifies issues
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Fetch all memories** for active project via `get_memories` with `user_id` and `app_id`. Paginate if needed — cap at 200 memories.
|
||||
|
||||
2. **Group by `metadata.type`**. Common types: `decision`, `convention`, `anti_pattern`, `task_learning`, `project_profile`, `user_preference`, `session_state`.
|
||||
|
||||
3. **Scan each group for issues:**
|
||||
|
||||
| Issue | Detection method |
|
||||
|---|---|
|
||||
| **Near-duplicates** | >60% noun overlap within same type. Compare memory text after stripping stop words. |
|
||||
| **Contradictions** | Opposing facts about same topic (e.g., "use PostgreSQL" vs "use MySQL" for same component) |
|
||||
| **Low-confidence** | `metadata.confidence < 0.3` |
|
||||
| **Missing type** | No `metadata.type` set |
|
||||
| **Stale** | `created_at` older than 180 days with no updates |
|
||||
|
||||
4. **Output compact summary:**
|
||||
|
||||
```
|
||||
memory-reviewer: project=<id> total=<N>
|
||||
duplicates: <N> found
|
||||
contradictions: <N> found
|
||||
low_confidence: <N> found
|
||||
untagged: <N> found
|
||||
stale: <N> found
|
||||
```
|
||||
|
||||
5. **If issues found**, list them with memory IDs:
|
||||
|
||||
```
|
||||
Issues:
|
||||
[duplicate] "<memory_a>" ≈ "<memory_b>" [mem0:<id_a>, mem0:<id_b>]
|
||||
[contradiction] "<memory_x>" vs "<memory_y>" [mem0:<id_x>, mem0:<id_y>]
|
||||
[low_conf] "<memory_z>" (confidence: 0.1) [mem0:<id_z>]
|
||||
```
|
||||
|
||||
6. **Suggest action**: "Run `/mem0:dream` to consolidate duplicates and resolve contradictions."
|
||||
|
||||
## Constraints
|
||||
|
||||
- **Read-only** — never modify or delete memories (that's `/mem0:dream`'s job)
|
||||
- **Max 200 memories** per scan
|
||||
- Report findings, let user decide on action
|
||||
@@ -0,0 +1,156 @@
|
||||
---
|
||||
name: onboard
|
||||
description: Set up mem0 for this project — API key setup, config, categories, identity
|
||||
---
|
||||
|
||||
# Mem0 Onboarding Wizard
|
||||
|
||||
Run this wizard to set up the mem0 plugin for the current project. Complete in ~60 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 5 (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: Set up API key
|
||||
|
||||
Check if `MEM0_API_KEY` is already set by running:
|
||||
|
||||
```bash
|
||||
echo $MEM0_API_KEY
|
||||
```
|
||||
|
||||
### If API key IS set (non-empty output)
|
||||
|
||||
Print: `- API key found.` and proceed to Step 2.
|
||||
|
||||
### If API key is NOT set (empty output)
|
||||
|
||||
Guide the user through API key setup. Show this message and walk them through it:
|
||||
|
||||
```
|
||||
Step 1: Setting up API key.
|
||||
|
||||
- MEM0_API_KEY not found. Let's set it up.
|
||||
|
||||
1. Get your API key from https://app.mem0.ai/dashboard/api-keys
|
||||
(or run: mem0 init --agent --json)
|
||||
|
||||
2. Add to shell profile:
|
||||
echo 'export MEM0_API_KEY="m0-your-key-here"' >> ~/.zshrc
|
||||
source ~/.zshrc
|
||||
|
||||
3. Verify:
|
||||
echo $MEM0_API_KEY
|
||||
# Should print your key
|
||||
```
|
||||
|
||||
After the user confirms they've set the key, verify it by running `echo $MEM0_API_KEY`. If still empty, repeat the instructions. If set, proceed to Step 2.
|
||||
|
||||
## Step 2: MCP OAuth login
|
||||
|
||||
Now authenticate the MCP server connection. Tell the user:
|
||||
|
||||
```
|
||||
Step 2: MCP OAuth login.
|
||||
|
||||
1. Type /mcp in Claude Code
|
||||
2. A browser window will open for authentication at mcp.mem0.ai
|
||||
3. Log in with your mem0 account
|
||||
4. Return here after authenticating in your browser
|
||||
```
|
||||
|
||||
After the user completes OAuth, verify MCP tools are available using ToolSearch with query `"mem0 search_memories"`. The exact tool name varies by install method (may be `mcp__mem0__search_memories` or `mcp__plugin_mem0_mem0__search_memories`).
|
||||
|
||||
- If MCP tools found: Print `- MCP connected.` and proceed to Step 3.
|
||||
- If NOT found: Troubleshoot before giving up:
|
||||
1. Check plugin is installed: run `/plugins` and confirm `mem0` appears
|
||||
2. Ask if the browser auth completed successfully
|
||||
3. Look for `mcp.mem0.ai` in the MCP server list via `/mcp`
|
||||
4. If all checks fail: "Restart Claude Code and run `/mem0:onboard` again."
|
||||
**STOP here** — do not proceed without MCP tools.
|
||||
|
||||
## Step 3: Verify connectivity and show identity
|
||||
|
||||
Call `search_memories` with `query="project setup"`, `user_id=<active_user_id>`, `filters={"AND": [{"user_id": "<active_user_id>"}, {"app_id": "<active_project_id>"}]}`, `limit=1` to verify connectivity.
|
||||
|
||||
Print:
|
||||
```
|
||||
- Connected
|
||||
user: <user_id>
|
||||
project: <project_id>
|
||||
branch: <branch>
|
||||
```
|
||||
|
||||
If the search fails, troubleshoot the API key and MCP connection.
|
||||
|
||||
## Step 4: 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 `<filename>` (<size> 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: <filename>\n\nProject: <project_id>\n\n<file_content>"}]`
|
||||
- `user_id=<active_user_id>`
|
||||
- `app_id=<active_project_id>`
|
||||
- `metadata={"type": "project_profile", "file": "<filename>", "source": "onboard", "branch": "<active_branch>"}`
|
||||
- `infer=False`
|
||||
|
||||
## Step 5: 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 6: Mark project as onboarded
|
||||
|
||||
```bash
|
||||
_SAFE_PID=$(printf '%s' "<active_project_id>" | tr '/:' '--')
|
||||
mkdir -p ~/.mem0 && touch ~/.mem0/.onboarded_${_SAFE_PID}
|
||||
```
|
||||
|
||||
This is silent — no user-facing output needed.
|
||||
|
||||
## Step 7: Summary
|
||||
|
||||
Print a summary:
|
||||
```
|
||||
- Onboarding complete.
|
||||
user_id: <user_id>
|
||||
project_id: <project_id> (app_id)
|
||||
imported: <N> files
|
||||
categories: <installed or skipped>
|
||||
|
||||
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.
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: peek
|
||||
description: Quick search memories — compact one-liner results
|
||||
---
|
||||
|
||||
# Mem0 Peek
|
||||
|
||||
Quick search with compact output. Lighter than `/mem0:tour`.
|
||||
|
||||
## Execution
|
||||
|
||||
### Step 1: Parse query
|
||||
|
||||
The user provides a search query: `/mem0:peek auth middleware`
|
||||
|
||||
If no query provided, ask: "What should I search for?"
|
||||
|
||||
### Step 2: Search
|
||||
|
||||
Run 2 parallel `search_memories` calls:
|
||||
|
||||
1. Broad: `query=<user's query>`, `filters={"AND": [{"user_id": "<id>"}, {"app_id": "<pid>"}]}`, `top_k=10`
|
||||
2. Targeted: `query=<user's query>`, `filters={"AND": [{"user_id": "<id>"}, {"app_id": "<pid>"}, {"metadata": {"type": "decision"}}]}`, `top_k=5`
|
||||
|
||||
### Step 3: Display
|
||||
|
||||
Deduplicate by ID, then show compact results:
|
||||
|
||||
```
|
||||
## mem0 peek: "<query>" (<N> results)
|
||||
|
||||
1. [decision] Auth module uses JWT with RS256 keys (2025-05-15) [mem0:a3f8b2c1]
|
||||
2. [anti_pattern] Don't use symmetric HS256 — leaked in env (2025-05-10) [mem0:7e2d9f4a]
|
||||
3. [convention] All middleware in src/middleware/ (2025-05-08) [mem0:c4d5e6f7]
|
||||
```
|
||||
|
||||
Format: `<number>. [<type>] <content, 80 chars> (<date>) [mem0:<short_id>]`
|
||||
|
||||
If no results:
|
||||
```
|
||||
No memories matching "<query>" for project <project_id>.
|
||||
```
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
name: pin
|
||||
description: Pin a memory to protect it from consolidation
|
||||
---
|
||||
|
||||
# Mem0 Pin
|
||||
|
||||
Pin a memory to mark it as high-priority and protect from pruning.
|
||||
|
||||
## Execution
|
||||
|
||||
### Step 1: Find the memory
|
||||
|
||||
The user provides either a search query or memory ID.
|
||||
|
||||
**If memory ID:**
|
||||
- Call `get_memory` with the ID.
|
||||
|
||||
**If search query:**
|
||||
- Call `search_memories` with the query, `filters={"AND": [{"user_id": "<id>"}, {"app_id": "<pid>"}]}`, `top_k=5`.
|
||||
- Show numbered list with content previews.
|
||||
- Ask: "Which memory to pin? Enter a number."
|
||||
|
||||
### Step 2: Read current content
|
||||
|
||||
Call `get_memory` with the selected memory ID. Store:
|
||||
- `original_text` — the memory's text content
|
||||
- `original_metadata` — the existing `metadata` dict
|
||||
|
||||
### Step 3: Pin it
|
||||
|
||||
#### 3a: Pinning a new memory (full protection)
|
||||
|
||||
Use `add_memory` with `immutable: true`:
|
||||
|
||||
```python
|
||||
add_memory(
|
||||
messages=[{"role": "user", "content": "<text to pin>"}],
|
||||
user_id=<user_id>,
|
||||
immutable=True,
|
||||
metadata={"pinned": True},
|
||||
)
|
||||
```
|
||||
|
||||
#### 3b: Pinning an existing memory
|
||||
|
||||
Call `update_memory` with:
|
||||
- `memory_id=<selected_id>`
|
||||
- `text=<original_text>` (preserve existing content)
|
||||
- `metadata=` merge `original_metadata` with `{"pinned": true}`
|
||||
|
||||
```python
|
||||
updated_meta = {**original_metadata, "pinned": True}
|
||||
update_memory(memory_id=<selected_id>, text=<original_text>, metadata=updated_meta)
|
||||
```
|
||||
|
||||
**Important:** `update_memory` requires the `text` parameter. Passing only metadata may error or wipe content.
|
||||
|
||||
### Step 4: Confirm
|
||||
|
||||
```
|
||||
Pinned: "<memory content, first 80 chars>..."
|
||||
Memory ID: <id>
|
||||
```
|
||||
|
||||
### Unpin
|
||||
|
||||
If the user says "unpin":
|
||||
1. Call `get_memory` to read current content and metadata.
|
||||
2. Set `metadata.pinned = false`:
|
||||
```python
|
||||
updated_meta = {**original_metadata, "pinned": False}
|
||||
update_memory(memory_id=<id>, text=<original_text>, metadata=updated_meta)
|
||||
```
|
||||
3. Print: `Unpinned: "<content>..."`
|
||||
@@ -1,17 +1,36 @@
|
||||
---
|
||||
name: mem0-mcp
|
||||
description: >
|
||||
Mem0 memory protocol for agents using the mem0 MCP tools (Claude Code, Cursor,
|
||||
Codex, and any other MCP-aware runtime). Decide deliberately when memory context
|
||||
would help, run targeted searches with metadata filters when it would, and store
|
||||
key learnings as work completes. Use the mem0 MCP tools (add_memory,
|
||||
search_memories, get_memories, etc.) for all memory operations.
|
||||
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.
|
||||
@@ -56,7 +75,7 @@ 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 found:
|
||||
Conflicting memories:
|
||||
- [mem0:<id1>] "<content1>" (confidence: <score1>, <date1>)
|
||||
- [mem0:<id2>] "<content2>" (confidence: <score2>, <date2>)
|
||||
Which is current?
|
||||
@@ -76,9 +95,9 @@ When you do search, run **2–4 parallel** `search_memories` calls at different
|
||||
|
||||
**Metadata filters** match the same `type` values written under "After completing significant work" below.
|
||||
|
||||
Two rules from the v2 filter spec:
|
||||
Filter rules:
|
||||
|
||||
1. The root **must** be a logical operator (`AND` / `OR` / `NOT`) with an array. A bare `{"user_id": "..."}` won't work.
|
||||
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:
|
||||
@@ -324,7 +343,7 @@ search_memories(
|
||||
{"metadata.files": {"contains": "src/middleware/auth.ts"}},
|
||||
]
|
||||
},
|
||||
limit=5,
|
||||
top_k=5,
|
||||
)
|
||||
```
|
||||
|
||||
@@ -348,12 +367,12 @@ current_meta["last_accessed"] = datetime.datetime.now(datetime.timezone.utc).iso
|
||||
# 3. Update with preserved content and bumped metadata
|
||||
update_memory(
|
||||
memory_id=<id>,
|
||||
data=current_text, # preserve original text — required parameter
|
||||
text=current_text, # preserve original text — required parameter
|
||||
metadata=current_meta, # pass updated access_count and last_accessed
|
||||
)
|
||||
```
|
||||
|
||||
**Important:** `update_memory` requires the `data` (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.
|
||||
**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.
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
name: mem0-remember
|
||||
description: >
|
||||
Quick-add a memory from the user's input. No extraction pass — stores verbatim.
|
||||
TRIGGER: user runs /mem0:remember <text>, or says "remember this", "save this",
|
||||
"store this fact", "don't forget that".
|
||||
name: remember
|
||||
description: Save a memory verbatim from your input
|
||||
---
|
||||
|
||||
# Mem0 Remember
|
||||
@@ -48,6 +45,8 @@ Call `add_memory` with:
|
||||
|
||||
Print:
|
||||
```
|
||||
Remembered as <type>: "<first 80 chars of content>..."
|
||||
Remembered as <type>: "<content, first 80 chars>"
|
||||
Memory ID: <id>
|
||||
```
|
||||
|
||||
Append `...` only if content was truncated (longer than 80 chars).
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
name: stats
|
||||
description: Show memory usage stats for this session and project
|
||||
---
|
||||
|
||||
# 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=<active_user_id>`
|
||||
- `app_id=<active_project_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 minimal dashboard. No ASCII bar charts — use a clean table layout:
|
||||
|
||||
```
|
||||
## mem0 stats
|
||||
|
||||
**Session** — 3 written, 5 searches, categories: decision, convention
|
||||
|
||||
**Project: my-project** — 55 memories, API: 84ms
|
||||
|
||||
| Category | Count |
|
||||
|----------------------|-------|
|
||||
| decision | 24 |
|
||||
| convention | 15 |
|
||||
| anti_pattern | 6 |
|
||||
| task_learning | 5 |
|
||||
| user_preference | 3 |
|
||||
| session_state | 2 |
|
||||
|
||||
**Age** — oldest: 2026-02-15, newest: 2026-05-23
|
||||
< 7 days: 5 · 7–30d: 12 · 30–90d: 10 · > 90d: 8
|
||||
|
||||
**Identity** — user: kartik · project: my-project · branch: main
|
||||
```
|
||||
|
||||
**Display rules:**
|
||||
- Category table: sort by count descending, omit categories with 0 memories
|
||||
- Age: single line with dot-separated buckets, computed from `created_at`
|
||||
- Session line: skip if no session data available
|
||||
- If only 1-2 total memories, skip the category table — just show the count
|
||||
- Keep everything compact — no decorative borders or filler
|
||||
|
||||
## Weekly digest mode
|
||||
|
||||
When invoked with `--weekly` (e.g., `/mem0:stats --weekly`), append a weekly
|
||||
activity digest after the standard stats dashboard:
|
||||
|
||||
### W1: Fetch recent memories
|
||||
|
||||
Call `search_memories` in parallel with time-scoped queries:
|
||||
1. `query="decisions made this week"`, `filters={"AND": [{"user_id": "<id>"}, {"app_id": "<pid>"}, {"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:
|
||||
|
||||
```
|
||||
### This week (May 16 – May 23)
|
||||
|
||||
+12 memories — most active: Wednesday (5)
|
||||
|
||||
| Category | New |
|
||||
|---------------|-----|
|
||||
| decision | 5 |
|
||||
| task_learning | 4 |
|
||||
| bug_fix | 3 |
|
||||
|
||||
**Highlights**
|
||||
- <2-3 sentence summary of most important decisions/learnings this week>
|
||||
```
|
||||
|
||||
### W4: Write digest file
|
||||
|
||||
Write to `~/.mem0/weekly-digest.md` (overwrite). Append one-line to
|
||||
`~/.mem0/digest-history.log`:
|
||||
```
|
||||
<YYYY-MM-DD> | <project_id> | +<new_count> memories | top: <top_category>
|
||||
```
|
||||
|
||||
### W5: Empty state
|
||||
|
||||
If no new memories in 7 days:
|
||||
```
|
||||
No new memories in the past week. Total: <N> memories in <project_id>.
|
||||
```
|
||||
+2
-6
@@ -1,10 +1,6 @@
|
||||
---
|
||||
name: mem0-switch-project
|
||||
description: >
|
||||
Manually override project_id for the current directory.
|
||||
Useful for monorepos, nested git dirs, or non-git directories.
|
||||
TRIGGER: user runs /mem0:switch-project <name>, or asks "switch mem0 project",
|
||||
"change project scope", "override project_id".
|
||||
name: switch-project
|
||||
description: Override the auto-detected project scope
|
||||
---
|
||||
|
||||
# Mem0 Switch Project
|
||||
@@ -1,10 +1,6 @@
|
||||
---
|
||||
name: mem0-tour
|
||||
description: >
|
||||
Show what mem0 knows about the current project. Dumps top memories
|
||||
grouped by category. Power-user-friendly proof of value.
|
||||
TRIGGER: user runs /mem0:tour, or asks "what do you know about this project",
|
||||
"show me my memories", "what has mem0 stored".
|
||||
name: tour
|
||||
description: Browse stored memories grouped by category
|
||||
---
|
||||
|
||||
# Mem0 Project Tour
|
||||
@@ -22,18 +18,14 @@ When invoked with `--all-projects` (e.g., `/mem0:tour --all-projects` or
|
||||
3. Group results by `app_id` first, then by category within each project.
|
||||
4. Display:
|
||||
```
|
||||
## Cross-Project Tour for <user_id>
|
||||
|
||||
### <app_id_1> (<N> memories)
|
||||
#### Architecture Decisions
|
||||
- <memory content>
|
||||
## <app_id_1> (<N> memories) ← current
|
||||
**Architecture Decisions** — <memory content>
|
||||
...
|
||||
|
||||
### <app_id_2> (<N> memories)
|
||||
## <app_id_2> (<N> memories)
|
||||
...
|
||||
|
||||
---
|
||||
Total: <N> memories across <M> projects
|
||||
<N> memories across <M> projects
|
||||
```
|
||||
5. Mark the current project with `← (current)` in the heading.
|
||||
|
||||
@@ -126,9 +118,7 @@ For groups with zero results, skip them entirely — don't print empty groups.
|
||||
### Step 5: Print totals
|
||||
|
||||
```
|
||||
---
|
||||
Total: <N> unique memories across <M> categories for project <project_id>
|
||||
Branch: <active_branch>
|
||||
<N> memories across <M> categories — project: <project_id>, branch: <active_branch>
|
||||
```
|
||||
|
||||
### Step 6: Empty state
|
||||
@@ -0,0 +1,105 @@
|
||||
"""Tests for on_file_read.sh hook — skip rules and dedup logic."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
SCRIPT = os.path.join(os.path.dirname(__file__), "..", "scripts", "on_file_read.sh")
|
||||
|
||||
|
||||
def _run_hook(file_path: str, env_overrides: dict | None = None) -> subprocess.CompletedProcess:
|
||||
env = {
|
||||
**os.environ,
|
||||
"MEM0_API_KEY": "",
|
||||
"CLAUDE_PLUGIN_OPTION_API_KEY": "",
|
||||
"CLAUDE_PLUGIN_OPTION_MEM0_API_KEY": "",
|
||||
"USER": "testuser",
|
||||
}
|
||||
if env_overrides:
|
||||
env.update(env_overrides)
|
||||
|
||||
payload = json.dumps({"tool_name": "Read", "tool_input": {"file_path": file_path}})
|
||||
return subprocess.run(
|
||||
["bash", SCRIPT],
|
||||
input=payload,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=env,
|
||||
timeout=8,
|
||||
)
|
||||
|
||||
|
||||
def test_skip_png():
|
||||
result = _run_hook("/project/logo.png")
|
||||
assert result.returncode == 0
|
||||
assert result.stdout == ""
|
||||
|
||||
|
||||
def test_skip_lockfile():
|
||||
result = _run_hook("/project/package-lock.json")
|
||||
assert result.returncode == 0
|
||||
assert result.stdout == ""
|
||||
|
||||
|
||||
def test_skip_min_js():
|
||||
result = _run_hook("/project/dist/bundle.min.js")
|
||||
assert result.returncode == 0
|
||||
assert result.stdout == ""
|
||||
|
||||
|
||||
def test_skip_node_modules():
|
||||
result = _run_hook("/project/node_modules/lodash/index.js")
|
||||
assert result.returncode == 0
|
||||
assert result.stdout == ""
|
||||
|
||||
|
||||
def test_skip_git_dir():
|
||||
result = _run_hook("/project/.git/config")
|
||||
assert result.returncode == 0
|
||||
assert result.stdout == ""
|
||||
|
||||
|
||||
def test_skip_pycache():
|
||||
result = _run_hook("/project/__pycache__/module.cpython-311.pyc")
|
||||
assert result.returncode == 0
|
||||
assert result.stdout == ""
|
||||
|
||||
|
||||
def test_skip_no_api_key(tmp_path):
|
||||
"""Code file but no API key — should exit cleanly with no output."""
|
||||
result = _run_hook("/project/src/app.py", {"MEM0_API_KEY": "", "HOME": str(tmp_path)})
|
||||
assert result.returncode == 0
|
||||
assert result.stdout == ""
|
||||
|
||||
|
||||
def test_skip_empty_file_path():
|
||||
"""Empty file_path — should exit cleanly."""
|
||||
result = _run_hook("")
|
||||
assert result.returncode == 0
|
||||
assert result.stdout == ""
|
||||
|
||||
|
||||
def test_dedup_repeated_reads(tmp_path):
|
||||
"""Same file read twice — second should be skipped (via dedup file)."""
|
||||
dedup_file = str(tmp_path / "mem0_recent_reads_testuser")
|
||||
env = {"MEM0_API_KEY": "fake-key", "USER": "testuser"}
|
||||
|
||||
# Write the file path into the dedup tracking file
|
||||
with open(dedup_file, "w") as f:
|
||||
f.write("/project/src/app.py\n")
|
||||
|
||||
# Patch RECENT_FILE by providing the USER env and ensuring /tmp has the file
|
||||
# Since the script uses /tmp/mem0_recent_reads_${USER}, we create it there
|
||||
tmp_dedup = "/tmp/mem0_recent_reads_testuser"
|
||||
try:
|
||||
with open(tmp_dedup, "w") as f:
|
||||
f.write("/project/src/app.py\n")
|
||||
|
||||
result = _run_hook("/project/src/app.py", env)
|
||||
assert result.returncode == 0
|
||||
assert result.stdout == ""
|
||||
finally:
|
||||
if os.path.exists(tmp_dedup):
|
||||
os.remove(tmp_dedup)
|
||||
@@ -0,0 +1,58 @@
|
||||
"""Tests for rubric deduplication in on_user_prompt.sh."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "scripts")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clean_rubric_flag(tmp_path, monkeypatch):
|
||||
"""Use a temp dir for the rubric flag file."""
|
||||
monkeypatch.setenv("MEM0_RUBRIC_DIR", str(tmp_path))
|
||||
yield
|
||||
|
||||
|
||||
def _run_hook(prompt: str, env_overrides: dict | None = None, session_id: str = "test-sess-001") -> str:
|
||||
"""Run on_user_prompt.sh with a simulated prompt and return stdout."""
|
||||
env = {
|
||||
**os.environ,
|
||||
"USER": "testuser",
|
||||
"MEM0_API_KEY": "test-key-123",
|
||||
"MEM0_RESOLVED_USER_ID": "testuser",
|
||||
"MEM0_PROJECT_ID": "test-project",
|
||||
"MEM0_BRANCH": "main",
|
||||
}
|
||||
if env_overrides:
|
||||
env.update(env_overrides)
|
||||
|
||||
input_json = json.dumps({"prompt": prompt, "session_id": session_id})
|
||||
result = subprocess.run(
|
||||
["bash", os.path.join(SCRIPTS_DIR, "on_user_prompt.sh")],
|
||||
input=input_json,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=env,
|
||||
timeout=10,
|
||||
)
|
||||
return result.stdout
|
||||
|
||||
|
||||
def test_first_prompt_gets_full_rubric():
|
||||
"""First substantial prompt of session gets full memory check rubric."""
|
||||
output = _run_hook("How should we refactor the auth module?")
|
||||
assert "Search mem0" in output
|
||||
assert "Search tips" in output
|
||||
assert "metadata.type" in output
|
||||
|
||||
|
||||
def test_second_prompt_gets_no_rubric():
|
||||
"""Second prompt of session emits nothing — rubric and tips only on first prompt."""
|
||||
_run_hook("How should we refactor the auth module?")
|
||||
output = _run_hook("What about the database layer?")
|
||||
assert output.strip() == ""
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Tests for _search.py — shared mem0 search API helper."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
def test_search_memories_returns_results():
|
||||
from _search import search_memories
|
||||
|
||||
fake_results = [
|
||||
{"id": "abc123", "memory": "Use Postgres for auth", "metadata": {"type": "decision"}},
|
||||
{"id": "def456", "memory": "Never use floats for money", "metadata": {"type": "anti_pattern"}},
|
||||
]
|
||||
|
||||
def mock_urlopen(req, timeout=None):
|
||||
resp = MagicMock()
|
||||
resp.read.return_value = json.dumps({"results": fake_results}).encode()
|
||||
resp.__enter__ = lambda s: s
|
||||
resp.__exit__ = MagicMock(return_value=False)
|
||||
return resp
|
||||
|
||||
with patch("urllib.request.urlopen", side_effect=mock_urlopen):
|
||||
results = search_memories("test-key", "user1", "proj1", "auth decisions")
|
||||
|
||||
assert len(results) == 2
|
||||
assert results[0]["id"] == "abc123"
|
||||
|
||||
|
||||
def test_search_memories_with_metadata_type():
|
||||
from _search import search_memories
|
||||
|
||||
captured_body = {}
|
||||
|
||||
def mock_urlopen(req, timeout=None):
|
||||
captured_body.update(json.loads(req.data.decode()))
|
||||
resp = MagicMock()
|
||||
resp.read.return_value = json.dumps({"results": []}).encode()
|
||||
resp.__enter__ = lambda s: s
|
||||
resp.__exit__ = MagicMock(return_value=False)
|
||||
return resp
|
||||
|
||||
with patch("urllib.request.urlopen", side_effect=mock_urlopen):
|
||||
search_memories("key", "user", "proj", "query", metadata_type="decision")
|
||||
|
||||
filters = captured_body["filters"]
|
||||
assert {"metadata": {"type": "decision"}} in filters["AND"]
|
||||
|
||||
|
||||
def test_search_memories_handles_api_error():
|
||||
from _search import search_memories
|
||||
|
||||
with patch("urllib.request.urlopen", side_effect=Exception("timeout")):
|
||||
results = search_memories("key", "user", "proj", "query")
|
||||
|
||||
assert results == []
|
||||
|
||||
|
||||
def test_search_memories_handles_list_response():
|
||||
from _search import search_memories
|
||||
|
||||
fake_results = [{"id": "abc", "memory": "test"}]
|
||||
|
||||
def mock_urlopen(req, timeout=None):
|
||||
resp = MagicMock()
|
||||
resp.read.return_value = json.dumps(fake_results).encode()
|
||||
resp.__enter__ = lambda s: s
|
||||
resp.__exit__ = MagicMock(return_value=False)
|
||||
return resp
|
||||
|
||||
with patch("urllib.request.urlopen", side_effect=mock_urlopen):
|
||||
results = search_memories("key", "user", "proj", "query")
|
||||
|
||||
assert len(results) == 1
|
||||
|
||||
|
||||
def test_search_memories_respects_top_k():
|
||||
from _search import search_memories
|
||||
|
||||
captured_body = {}
|
||||
|
||||
def mock_urlopen(req, timeout=None):
|
||||
captured_body.update(json.loads(req.data.decode()))
|
||||
resp = MagicMock()
|
||||
resp.read.return_value = json.dumps({"results": []}).encode()
|
||||
resp.__enter__ = lambda s: s
|
||||
resp.__exit__ = MagicMock(return_value=False)
|
||||
return resp
|
||||
|
||||
with patch("urllib.request.urlopen", side_effect=mock_urlopen):
|
||||
search_memories("key", "user", "proj", "query", top_k=5)
|
||||
|
||||
assert captured_body["top_k"] == 5
|
||||
|
||||
|
||||
def test_search_memories_no_api_key_returns_empty():
|
||||
from _search import search_memories
|
||||
|
||||
results = search_memories("", "user", "proj", "query")
|
||||
assert results == []
|
||||
|
||||
|
||||
def test_format_results_for_context():
|
||||
from _search import format_results_for_context
|
||||
|
||||
memories = [
|
||||
{"id": "abc12345-long-id", "memory": "Use Postgres for auth", "metadata": {"type": "decision"}},
|
||||
{"id": "def67890-long-id", "memory": "JWT tokens expire in 1h", "metadata": {"type": "convention"}},
|
||||
]
|
||||
|
||||
output = format_results_for_context(memories, heading="Relevant memories")
|
||||
assert "Relevant memories" in output
|
||||
assert "[decision]" in output
|
||||
assert "Use Postgres for auth" in output
|
||||
assert "abc12345" in output
|
||||
@@ -190,5 +190,6 @@ def test_resolve_api_key_returns_empty_when_neither_set(monkeypatch):
|
||||
from _identity import resolve_api_key
|
||||
|
||||
monkeypatch.delenv("MEM0_API_KEY", raising=False)
|
||||
monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_API_KEY", raising=False)
|
||||
monkeypatch.delenv("CLAUDE_PLUGIN_OPTION_MEM0_API_KEY", raising=False)
|
||||
assert resolve_api_key() == ""
|
||||
|
||||
Reference in New Issue
Block a user