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