feat(mem0-plugin): improve auto-triggering — pre-fetch, dedup, skill enforcement v0.2.3 (#5237)
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user