From 19c19566303bd0d1a4d7de772a3e1d0fb7f48c30 Mon Sep 17 00:00:00 2001 From: gabrielstein-mem0 Date: Mon, 27 Apr 2026 13:58:34 -0700 Subject: [PATCH] refactor(plugin): hand mem0 search decisions to the agent The on_user_prompt hook previously ran a blind semantic search using the raw user prompt as the query and injected the top results unconditionally. That undermined the agent's judgment with low-quality context and made heavy API calls on prompts where memory wouldn't help. Replace with a decision rubric injected into context per turn. The agent decides whether memory would improve the response and, if so, runs targeted searches with metadata.type filters and proper query phrasing. Mirror the same rules in mem0-codex/SKILL.md so the skill (loaded once) and the hook (re-asserted per turn) agree. Skip gates preserved: prompts under 20 chars and missing MEM0_API_KEY. - on_user_prompt.sh: API call replaced with stdout rubric injection - hooks.json / codex-hooks.json: statusMessage updated to match new behavior - mem0-codex/SKILL.md: "On every new task" rewritten with search/skip rules, query phrasing guidance, metadata filter table, and a worked example --- mem0-plugin/hooks/codex-hooks.json | 2 +- mem0-plugin/hooks/hooks.json | 2 +- mem0-plugin/scripts/on_user_prompt.sh | 74 +++++++++++++------------- mem0-plugin/skills/mem0-codex/SKILL.md | 74 +++++++++++++++++++++++--- 4 files changed, 107 insertions(+), 45 deletions(-) diff --git a/mem0-plugin/hooks/codex-hooks.json b/mem0-plugin/hooks/codex-hooks.json index 0676308a9..23e6dbd32 100644 --- a/mem0-plugin/hooks/codex-hooks.json +++ b/mem0-plugin/hooks/codex-hooks.json @@ -18,7 +18,7 @@ { "type": "command", "command": "${CODEX_PLUGIN_ROOT}/scripts/on_user_prompt.sh", - "statusMessage": "Searching mem0 memories...", + "statusMessage": "Checking memory relevance...", "timeout": 5 } ] diff --git a/mem0-plugin/hooks/hooks.json b/mem0-plugin/hooks/hooks.json index d5b907427..897a978d9 100644 --- a/mem0-plugin/hooks/hooks.json +++ b/mem0-plugin/hooks/hooks.json @@ -57,7 +57,7 @@ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_user_prompt.sh", - "statusMessage": "Searching mem0 memories...", + "statusMessage": "Checking memory relevance...", "timeout": 5 } ] diff --git a/mem0-plugin/scripts/on_user_prompt.sh b/mem0-plugin/scripts/on_user_prompt.sh index 398ec7456..e8cff7357 100755 --- a/mem0-plugin/scripts/on_user_prompt.sh +++ b/mem0-plugin/scripts/on_user_prompt.sh @@ -1,61 +1,63 @@ #!/usr/bin/env bash # Hook: UserPromptSubmit # -# Fires on every user message. Searches mem0 for relevant memories -# and injects them into Claude's context before processing. +# Fires on every user message. Instead of pre-searching mem0 with the +# raw prompt, this injects a decision rubric telling the agent when +# and how to search itself. The agent has more context than this +# script does -- let it decide. # -# Input: JSON on stdin with prompt, session_id, cwd, transcript_path -# Output: Matching memories as context text (exit 0) -# -# Skips search for very short prompts (< 20 chars) and when -# MEM0_API_KEY is not set. Uses a 3s timeout to minimize latency. +# Input: JSON on stdin (prompt, session_id, cwd, transcript_path) +# Output: Decision rubric injected into Claude's context (exit 0) -# Intentionally omit -e so the script always exits 0 even if -# curl or jq fail — must never block the user's prompt. +# Intentionally omit -e so the script always exits 0 even if jq fails -- +# must never block the user's prompt. set -uo pipefail INPUT=$(cat) PROMPT=$(echo "$INPUT" | jq -r '.prompt // ""' 2>/dev/null || echo "") -# Skip trivial prompts — not worth a network call +# Acknowledgements and short replies don't warrant memory context if [ ${#PROMPT} -lt 20 ]; then exit 0 fi -API_KEY="${MEM0_API_KEY:-}" -if [ -z "$API_KEY" ]; then +# No API key means the agent can't search anyway +if [ -z "${MEM0_API_KEY:-}" ]; then exit 0 fi USER_ID="${MEM0_USER_ID:-${USER:-default}}" -# Build request body safely via jq to avoid injection -BODY=$(jq -n --arg query "$PROMPT" --arg user_id "$USER_ID" \ - '{query: $query, filters: {user_id: $user_id}, top_k: 5}') +cat </dev/null || echo "") +Before responding, decide whether persistent memory context from mem0 would +improve your answer. The agent -- not this hook -- owns this decision. -if [ -z "$RESPONSE" ]; then - exit 0 -fi +**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 -# Extract memories from response (API returns a flat array) -MEMORIES=$(echo "$RESPONSE" | jq -r ' - if type == "array" then . else .results // [] end | - if length == 0 then empty else - "## Relevant memories from mem0\n\n" + - (map(select(.memory != null) | "- " + .memory) | join("\n")) - end -' 2>/dev/null || echo "") +**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 -if [ -n "$MEMORIES" ]; then - echo "$MEMORIES" -fi +**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. +- Use \`metadata.type\` filters where they fit: + - \`{"metadata.type": "decision"}\` -- design / architecture questions + - \`{"metadata.type": "anti_pattern"}\` -- debugging, error handling + - \`{"metadata.type": "user_preference"}\` -- tooling, stack, style + - \`{"metadata.type": "convention"}\` -- established patterns in this project +- Scope every query with \`{"user_id": "$USER_ID"}\`. +- Empty results are normal -- proceed without context. +EOF exit 0 diff --git a/mem0-plugin/skills/mem0-codex/SKILL.md b/mem0-plugin/skills/mem0-codex/SKILL.md index 036f1a242..516826eca 100644 --- a/mem0-plugin/skills/mem0-codex/SKILL.md +++ b/mem0-plugin/skills/mem0-codex/SKILL.md @@ -1,10 +1,10 @@ --- name: mem0-codex description: > - Mem0 persistent memory integration for Codex. Automatically retrieve relevant - memories at the start of each task, store key learnings when tasks complete, - and capture session state before context is lost. Use the mem0 MCP tools - (add_memory, search_memories, get_memories, etc.) for all memory operations. + Mem0 persistent memory integration for Codex. Decide deliberately when memory + context would help, run targeted searches with metadata filters when it would, + and store key learnings as work completes. Use the mem0 MCP tools (add_memory, + search_memories, get_memories, etc.) for all memory operations. --- # Mem0 Memory Protocol for Codex @@ -13,9 +13,69 @@ You have access to persistent memory via the mem0 MCP tools. Follow this protoco ## On every new task -1. Call `search_memories` with a query related to the current task or project to load relevant context. -2. Review 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. +Decide whether persistent memory context would improve your response, then act accordingly. Don't search by default — search deliberately. + +### 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. + +### 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 `metadata.type` values written under "After completing significant work" below: + +| Filter | 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 | + +Combine with `user_id` using AND: +```python +filters={"AND": [{"user_id": "alice"}, {"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): +```python +search_memories(query="auth module decisions", + filters={"AND": [{"user_id": "alice"}, {"metadata.type": "decision"}]}) +search_memories(query="JWT", filters={"user_id": "alice"}) +search_memories(query="auth refactor failures", + filters={"AND": [{"user_id": "alice"}, {"metadata.type": "anti_pattern"}]}) +search_memories(query="auth", + filters={"AND": [{"user_id": "alice"}, {"metadata.type": "user_preference"}]}) +``` ## After completing significant work