fix(mem0-plugin): use app_id entity scoping, fix tour, add branch tagging

Tour was filtering on metadata.type which missed platform-categorized
memories. Rewritten to use get_memories (list all) + group by platform
categories field. All write hooks now use app_id as top-level entity
param instead of metadata.project_id. Branch stamped on every write.
This commit is contained in:
kartik-mem0
2026-05-21 16:38:48 +05:30
parent 606ede7c0a
commit cf6db6ef88
14 changed files with 169 additions and 86 deletions
+14 -10
View File
@@ -22,7 +22,7 @@ import urllib.request
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _identity import resolve_user_id from _identity import resolve_user_id
from _project import resolve_project_id from _project import resolve_branch, resolve_project_id
log = logging.getLogger("mem0-auto-import") log = logging.getLogger("mem0-auto-import")
log.setLevel(logging.DEBUG) log.setLevel(logging.DEBUG)
@@ -77,8 +77,15 @@ def save_hashes(hashes: dict[str, str]) -> None:
log.warning("Could not save hash store: %s", e) log.warning("Could not save hash store: %s", e)
def post_memory(api_key: str, content: str, user_id: str, filename: str, project_id: str) -> bool: 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.""" """POST a project profile memory to the Mem0 REST API."""
metadata = {
"type": "project_profile",
"file": filename,
"source": "auto-import",
}
if branch:
metadata["branch"] = branch
body = { body = {
"messages": [ "messages": [
{ {
@@ -87,12 +94,8 @@ def post_memory(api_key: str, content: str, user_id: str, filename: str, project
} }
], ],
"user_id": user_id, "user_id": user_id,
"metadata": { "app_id": project_id,
"type": "project_profile", "metadata": metadata,
"file": filename,
"project_id": project_id,
"source": "auto-import",
},
"infer": False, "infer": False,
} }
@@ -128,8 +131,9 @@ def main() -> None:
cwd = os.environ.get("MEM0_CWD", "").strip() or os.getcwd() cwd = os.environ.get("MEM0_CWD", "").strip() or os.getcwd()
user_id = resolve_user_id() user_id = resolve_user_id()
project_id = resolve_project_id(cwd) project_id = resolve_project_id(cwd)
branch = resolve_branch(cwd)
log.debug("Auto-import started: cwd=%s project=%s user=%s", cwd, project_id, user_id) log.debug("Auto-import started: cwd=%s project=%s user=%s branch=%s", cwd, project_id, user_id, branch)
hashes = load_hashes() hashes = load_hashes()
updated = False updated = False
@@ -169,7 +173,7 @@ def main() -> None:
log.debug("Cannot read %s: %s", filename, e) log.debug("Cannot read %s: %s", filename, e)
continue continue
if post_memory(api_key, content, user_id, filename, project_id): if post_memory(api_key, content, user_id, filename, project_id, branch):
hashes[hash_key] = current_hash hashes[hash_key] = current_hash
updated = True updated = True
# on API failure we don't update the hash — retry next session # on API failure we don't update the hash — retry next session
@@ -97,16 +97,18 @@ def find_compact_summary(lines: list[str]) -> str:
def store_summary(api_key: str, summary: str, user_id: str, session_id: str, project_id: str = "", branch: str = "") -> bool: def store_summary(api_key: str, summary: str, user_id: str, session_id: str, project_id: str = "", branch: str = "") -> bool:
expires = (date.today() + timedelta(days=COMPACT_SUMMARY_EXPIRY_DAYS)).isoformat() expires = (date.today() + timedelta(days=COMPACT_SUMMARY_EXPIRY_DAYS)).isoformat()
metadata = {
"type": "compact_summary",
"source": "session-start-compact",
"session_id": session_id,
}
if branch:
metadata["branch"] = branch
body = { body = {
"messages": [{"role": "user", "content": summary}], "messages": [{"role": "user", "content": summary}],
"user_id": user_id, "user_id": user_id,
"metadata": { "app_id": project_id,
"type": "compact_summary", "metadata": metadata,
"source": "session-start-compact",
"session_id": session_id,
"project_id": project_id,
"branch": branch,
},
"infer": False, "infer": False,
"expiration_date": expires, "expiration_date": expires,
} }
+9 -7
View File
@@ -171,18 +171,20 @@ def build_content(state: dict, source: str) -> str:
def store_memory(api_key: str, content: str, user_id: str, source: str, session_id: str = "", project_id: str = "", branch: str = "") -> bool: def store_memory(api_key: str, content: str, user_id: str, source: str, session_id: str = "", project_id: str = "", branch: str = "") -> bool:
"""Store session state as a memory via the Mem0 REST API.""" """Store session state as a memory via the Mem0 REST API."""
expires = (date.today() + timedelta(days=SESSION_STATE_EXPIRY_DAYS)).isoformat() expires = (date.today() + timedelta(days=SESSION_STATE_EXPIRY_DAYS)).isoformat()
metadata = {
"type": "session_state",
"source": source,
"session_id": session_id,
}
if branch:
metadata["branch"] = branch
body = { body = {
"messages": [ "messages": [
{"role": "user", "content": content} {"role": "user", "content": content}
], ],
"user_id": user_id, "user_id": user_id,
"metadata": { "app_id": project_id,
"type": "session_state", "metadata": metadata,
"source": source,
"session_id": session_id,
"project_id": project_id,
"branch": branch,
},
"expiration_date": expires, "expiration_date": expires,
} }
+2 -1
View File
@@ -55,7 +55,8 @@ Tool call shape:
add_memory( add_memory(
messages=[{"role":"user","content":"<the summary above>"}], messages=[{"role":"user","content":"<the summary above>"}],
user_id="<the active user_id from the SessionStart bootstrap>", user_id="<the active user_id from the SessionStart bootstrap>",
metadata={"type":"session_state","source":"pre-compaction","project_id":"<the active project_id>","branch":"<the active branch>"}, app_id="<the active project_id / app_id from SessionStart>",
metadata={"type":"session_state","source":"pre-compaction","branch":"<active branch>"},
infer=False, infer=False,
) )
``` ```
+23 -6
View File
@@ -38,11 +38,11 @@ if command -v python3 >/dev/null 2>&1; then
import json, os, urllib.request, urllib.error import json, os, urllib.request, urllib.error
api_key = os.environ.get('MEM0_API_KEY', '') api_key = os.environ.get('MEM0_API_KEY', '')
user_id = os.environ.get('MEM0_RESOLVED_USER_ID', 'default') user_id = os.environ.get('MEM0_RESOLVED_USER_ID', 'default')
project_id = os.environ.get('MEM0_PROJECT_ID', '') app_id = os.environ.get('MEM0_PROJECT_ID', '')
body = json.dumps({ body = json.dumps({
'query': 'project context', 'query': 'project context',
'user_id': user_id, 'user_id': user_id,
'filters': {'AND': [{'user_id': user_id}, {'metadata': {'project_id': project_id}}]}, 'filters': {'AND': [{'user_id': user_id}, {'app_id': app_id}]},
'limit': 100, 'limit': 100,
}).encode() }).encode()
req = urllib.request.Request( req = urllib.request.Request(
@@ -72,14 +72,30 @@ echo "## Mem0 Active"
echo "" echo ""
echo "\`user=$MEM0_RESOLVED_USER_ID | project=$MEM0_PROJECT_ID | branch=$MEM0_BRANCH | memories=$MEM0_COUNT\`" echo "\`user=$MEM0_RESOLVED_USER_ID | project=$MEM0_PROJECT_ID | branch=$MEM0_BRANCH | memories=$MEM0_COUNT\`"
echo "" echo ""
echo "Always include \`user_id\` + \`metadata.project_id\` in every \`search_memories\` filter and \`add_memory\` call:" echo "Always include \`user_id\` + \`app_id\` in every \`search_memories\` filter and \`add_memory\` call:"
echo "- user_id: \`$MEM0_RESOLVED_USER_ID\`" echo "- user_id: \`$MEM0_RESOLVED_USER_ID\`"
echo "- project_id: \`$MEM0_PROJECT_ID\`" echo "- app_id: \`$MEM0_PROJECT_ID\` (project scope — passed as top-level \`app_id\`, NOT in metadata)"
echo "- branch: \`$MEM0_BRANCH\` (include in session-state / compact-summary metadata)"
echo "" echo ""
if [ "$SOURCE" = "startup" ]; then if [ "$SOURCE" = "startup" ]; then
cat <<'EOF' # First-run detection: auto-trigger onboarding for new projects
_ONBOARD_MARKER="$HOME/.mem0/.onboarded_${MEM0_PROJECT_ID}"
if [ ! -f "$_ONBOARD_MARKER" ]; then
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.
EOF
else
cat <<'EOF'
## Mem0 Session Bootstrap ## Mem0 Session Bootstrap
You have access to persistent memory via the mem0 MCP tools. Before doing anything else: You have access to persistent memory via the mem0 MCP tools. Before doing anything else:
@@ -90,6 +106,7 @@ You have access to persistent memory via the mem0 MCP tools. Before doing anythi
IMPORTANT: Do NOT skip this step. Always bootstrap context first. IMPORTANT: Do NOT skip this step. Always bootstrap context first.
EOF EOF
fi
# Auto-import declarative project files in background # Auto-import declarative project files in background
MEM0_CWD="$(echo "$INPUT" | jq -r '.cwd // "."' 2>/dev/null || echo ".")" \ MEM0_CWD="$(echo "$INPUT" | jq -r '.cwd // "."' 2>/dev/null || echo ".")" \
+1 -1
View File
@@ -54,7 +54,7 @@ Memories can be as detailed as needed — include full context, reasoning, code
If nothing notable happened in this interaction, it's fine to skip. Only store genuinely useful learnings. If nothing notable happened in this interaction, it's fine to skip. Only store genuinely useful learnings.
Always include `"project_id"` in the metadata of any memory you store. Always include `app_id` (the active project_id from SessionStart) as a top-level parameter in every `add_memory` call.
EOF EOF
# Capture transcript state in the background via Mem0 REST API # Capture transcript state in the background via Mem0 REST API
+1 -1
View File
@@ -51,7 +51,7 @@ ${REPORT_BLOCK}Before finishing, check if there are important learnings from thi
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. 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.
Always include \`"project_id"\` in the metadata of any memory you store. Always include \`app_id\` (the active project_id from SessionStart) as a top-level parameter in every \`add_memory\` call.
If nothing notable happened in this interaction, it's fine to skip. Only store genuinely useful learnings. If nothing notable happened in this interaction, it's fine to skip. Only store genuinely useful learnings.
EOF EOF
+1 -1
View File
@@ -35,7 +35,7 @@ ${REPORT_BLOCK}Before finishing, check if there are important learnings from thi
4. Did you learn anything about the user's preferences? -> Store with metadata \`{"type": "user_preference"}\` 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"}\` 5. Were there environment/setup discoveries? -> Store with metadata \`{"type": "environmental"}\`
Always include \`"project_id"\` in the metadata of any memory you store. Always include \`app_id\` (the active project_id from session start) as a top-level parameter in every \`add_memory\` call.
If nothing notable happened, it's fine to skip. Only store genuinely useful learnings. If nothing notable happened, it's fine to skip. Only store genuinely useful learnings.
EOF EOF
+1 -1
View File
@@ -31,7 +31,7 @@ Extract key learnings from this completed task and store them using the mem0 \`a
Memories can be as detailed as needed — include full context, reasoning, code snippets, and examples. 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. Only store genuinely useful learnings — skip if the task was trivial.
Include \`"project_id": "$MEM0_PROJECT_ID"\` in metadata for all memories. Include \`app_id\` = \`"$MEM0_PROJECT_ID"\` as a top-level parameter in every \`add_memory\` call (not in metadata).
EOF EOF
exit 0 exit 0
+6 -6
View File
@@ -60,12 +60,12 @@ improve your answer. The agent -- not this hook -- owns this decision.
- Phrase queries as **nouns** ("auth module decisions"), not full sentences. - Phrase queries as **nouns** ("auth module decisions"), not full sentences.
- Filter shape: the root must be a logical operator (\`AND\` / \`OR\` / \`NOT\`) - Filter shape: the root must be a logical operator (\`AND\` / \`OR\` / \`NOT\`)
with an array, and metadata uses a **nested** object (not dotted keys). with an array, and metadata uses a **nested** object (not dotted keys).
Combine \`user_id\` with one \`metadata.type\` clause per call: Combine \`user_id\` + \`app_id\` with one \`metadata.type\` clause per call:
- \`{"AND": [{"user_id": "$USER_ID"}, {"metadata": {"project_id": "$MEM0_PROJECT_ID"}}, {"metadata": {"type": "decision"}}]}\` -- design / architecture - \`{"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "decision"}}]}\` -- design / architecture
- \`{"AND": [{"user_id": "$USER_ID"}, {"metadata": {"project_id": "$MEM0_PROJECT_ID"}}, {"metadata": {"type": "anti_pattern"}}]}\` -- debugging, error handling - \`{"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "anti_pattern"}}]}\` -- debugging, error handling
- \`{"AND": [{"user_id": "$USER_ID"}, {"metadata": {"project_id": "$MEM0_PROJECT_ID"}}, {"metadata": {"type": "user_preference"}}]}\` -- tooling, stack, style - \`{"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "user_preference"}}]}\` -- tooling, stack, style
- \`{"AND": [{"user_id": "$USER_ID"}, {"metadata": {"project_id": "$MEM0_PROJECT_ID"}}, {"metadata": {"type": "convention"}}]}\` -- established patterns - \`{"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "convention"}}]}\` -- established patterns
- Or scope with just \`{"AND": [{"user_id": "$USER_ID"}, {"metadata": {"project_id": "$MEM0_PROJECT_ID"}}]}\` when no metadata filter fits. - Or scope with just \`{"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}]}\` when no metadata filter fits.
- Empty results are normal -- proceed without context. - Empty results are normal -- proceed without context.
EOF EOF
+16 -14
View File
@@ -18,17 +18,16 @@ Decide whether persistent memory context would improve your response, then act a
## Project scoping ## Project scoping
Every memory operation MUST be scoped to the current project: Every memory operation MUST be scoped to the current project using `app_id` (entity-scoped memory):
- **On `add_memory`:** Always include `metadata.project_id` (the active project_id from SessionStart). - **On `add_memory`:** Always pass `app_id=<active_project_id>` as a **top-level parameter** (not in metadata).
- **On `search_memories`:** Always include `{"metadata": {"project_id": "<your_project_id>"}}` in the AND filter. - **On `search_memories`:** Always include `{"app_id": "<your_project_id>"}` in the AND filter.
- **Session-state memories:** Also include `metadata.branch` (the active branch from SessionStart).
Full filter template: Full filter template:
```python ```python
filters={"AND": [ filters={"AND": [
{"user_id": "<your_user_id>"}, {"user_id": "<your_user_id>"},
{"metadata": {"project_id": "<your_project_id>"}}, {"app_id": "<your_project_id>"},
{"metadata": {"type": "decision"}} {"metadata": {"type": "decision"}}
]} ]}
``` ```
@@ -67,7 +66,7 @@ Two rules from the v2 filter spec:
1. The root **must** be a logical operator (`AND` / `OR` / `NOT`) with an array. A bare `{"user_id": "..."}` won't work. 1. The root **must** be a logical operator (`AND` / `OR` / `NOT`) with an array. A bare `{"user_id": "..."}` won't work.
2. Metadata uses a **nested** object, not a dotted key. `{"metadata": {"type": "decision"}}`, never `{"metadata.type": "decision"}`. Only top-level metadata keys are filterable. 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` with one metadata clause per call: Combine `user_id` + `app_id` with one metadata clause per call:
| `metadata.type` clause | Use for | | `metadata.type` clause | Use for |
|--------|---------| |--------|---------|
@@ -78,7 +77,7 @@ Combine `user_id` with one metadata clause per call:
Full filter (replace `<your_user_id>` and `<your_project_id>` with the active values from SessionStart): Full filter (replace `<your_user_id>` and `<your_project_id>` with the active values from SessionStart):
```python ```python
filters={"AND": [{"user_id": "<your_user_id>"}, {"metadata": {"project_id": "<your_project_id>"}}, {"metadata": {"type": "decision"}}]} filters={"AND": [{"user_id": "<your_user_id>"}, {"app_id": "<your_project_id>"}, {"metadata": {"type": "decision"}}]}
``` ```
### Worked example ### Worked example
@@ -91,16 +90,16 @@ search_memories(query="Refactor the auth module to use JWT")
# Hits whatever shares words. Misses prior decisions and preferences. # Hits whatever shares words. Misses prior decisions and preferences.
``` ```
Do (parallel — substitute the active `user_id` and `project_id` for the placeholders): Do (parallel — substitute the active `user_id` and `app_id` for the placeholders):
```python ```python
search_memories(query="auth module decisions", search_memories(query="auth module decisions",
filters={"AND": [{"user_id": "<your_user_id>"}, {"metadata": {"project_id": "<your_project_id>"}}, {"metadata": {"type": "decision"}}]}) filters={"AND": [{"user_id": "<your_user_id>"}, {"app_id": "<your_project_id>"}, {"metadata": {"type": "decision"}}]})
search_memories(query="JWT", search_memories(query="JWT",
filters={"AND": [{"user_id": "<your_user_id>"}, {"metadata": {"project_id": "<your_project_id>"}}]}) filters={"AND": [{"user_id": "<your_user_id>"}, {"app_id": "<your_project_id>"}]})
search_memories(query="auth refactor failures", search_memories(query="auth refactor failures",
filters={"AND": [{"user_id": "<your_user_id>"}, {"metadata": {"project_id": "<your_project_id>"}}, {"metadata": {"type": "anti_pattern"}}]}) filters={"AND": [{"user_id": "<your_user_id>"}, {"app_id": "<your_project_id>"}, {"metadata": {"type": "anti_pattern"}}]})
search_memories(query="auth", search_memories(query="auth",
filters={"AND": [{"user_id": "<your_user_id>"}, {"metadata": {"project_id": "<your_project_id>"}}, {"metadata": {"type": "user_preference"}}]}) filters={"AND": [{"user_id": "<your_user_id>"}, {"app_id": "<your_project_id>"}, {"metadata": {"type": "user_preference"}}]})
``` ```
## After completing significant work ## After completing significant work
@@ -114,6 +113,8 @@ Extract key learnings and store them using the `add_memory` tool:
- **Environment/setup discoveries** -> Include metadata `{"type": "environmental"}` - **Environment/setup discoveries** -> Include metadata `{"type": "environmental"}`
- **Conventions established** -> Include metadata `{"type": "convention"}` - **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. > `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 ### Expiration: high-churn vs durable
@@ -133,7 +134,7 @@ When the user is asking about *current* state ("where were we", "what's the acti
```python ```python
# Last 90 days only # Last 90 days only
{"AND": [{"user_id": "<id>"}, {"metadata": {"project_id": "<your_project_id>"}}, {"metadata": {"type": "session_state"}}, {"created_at": {"gte": "<90 days ago, YYYY-MM-DD>"}}]} {"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. 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.
@@ -148,7 +149,8 @@ When you've done the extraction work yourself — pre-compaction summaries, deci
add_memory( add_memory(
messages=[{"role": "user", "content": "<your structured fact>"}], messages=[{"role": "user", "content": "<your structured fact>"}],
user_id="<active user_id>", user_id="<active user_id>",
metadata={"type": "decision"}, app_id="<active project_id>",
metadata={"type": "decision", "branch": "<active branch>"},
infer=False, infer=False,
) )
``` ```
+15 -5
View File
@@ -31,7 +31,7 @@ echo "${MEM0_API_KEY:+SET}" || echo "NOT_SET"
## Step 2: Show identity ## Step 2: Show identity
Report the active identity to the user: Report the active identity to the user:
- Call `search_memories` with `query="project setup"`, `user_id=<active_user_id>`, `filters={"AND": [{"user_id": "<active_user_id>"}, {"metadata": {"project_id": "<active_project_id>"}}]}`, `limit=1` to verify connectivity. - Call `search_memories` with `query="project setup"`, `user_id=<active_user_id>`, `filters={"AND": [{"user_id": "<active_user_id>"}, {"app_id": "<active_project_id>"}]}`, `limit=1` to verify connectivity.
- Print: `Connected. user=<user_id>, project=<project_id>, branch=<branch>` - Print: `Connected. user=<user_id>, project=<project_id>, branch=<branch>`
- If the search fails, troubleshoot the API key. - If the search fails, troubleshoot the API key.
@@ -51,7 +51,8 @@ If user says yes (or default):
- Call `add_memory` with: - Call `add_memory` with:
- `messages=[{"role": "user", "content": "## Project Profile: <filename>\n\nProject: <project_id>\n\n<file_content>"}]` - `messages=[{"role": "user", "content": "## Project Profile: <filename>\n\nProject: <project_id>\n\n<file_content>"}]`
- `user_id=<active_user_id>` - `user_id=<active_user_id>`
- `metadata={"type": "project_profile", "file": "<filename>", "project_id": "<active_project_id>", "source": "onboard"}` - `app_id=<active_project_id>`
- `metadata={"type": "project_profile", "file": "<filename>", "source": "onboard", "branch": "<active_branch>"}`
- `infer=False` - `infer=False`
## Step 4: Install coding categories ## Step 4: Install coding categories
@@ -71,14 +72,23 @@ python3 "<PLUGIN_ROOT>/scripts/setup_coding_categories.py" --apply
If the script reports an error, show the error message and suggest checking the API key. If the script reports an error, show the error message and suggest checking the API key.
## Step 5: Summary ## Step 5: Mark project as onboarded
Create a marker file so SessionStart won't re-trigger onboarding next session:
```bash
mkdir -p ~/.mem0 && touch ~/.mem0/.onboarded_<active_project_id>
```
This is silent — no user-facing output needed.
## Step 6: Summary
Print a summary: Print a summary:
``` ```
Onboarding complete. Onboarding complete.
user_id: <user_id> user_id: <user_id>
project_id: <project_id> project_id: <project_id> (app_id)
branch: <branch>
imported: <N> files imported: <N> files
categories: <installed or skipped> categories: <installed or skipped>
@@ -40,7 +40,7 @@ The user provides a project name as an argument: `/mem0:switch-project <project-
(Replace `<PROJECT_NAME>` with the user's chosen project name.) (Replace `<PROJECT_NAME>` with the user's chosen project name.)
3. Verify by searching for existing memories: 3. Verify by searching for existing memories:
- Call `search_memories` with `query="project"`, `filters={"AND": [{"user_id": "<id>"}, {"metadata": {"project_id": "<PROJECT_NAME>"}}]}`, `limit=1` - Call `search_memories` with `query="project"`, `filters={"AND": [{"user_id": "<id>"}, {"app_id": "<PROJECT_NAME>"}]}`, `limit=1`
4. Print: 4. Print:
``` ```
+70 -25
View File
@@ -13,34 +13,79 @@ Show the user what mem0 has stored for the current project.
## Execution ## Execution
1. Run the following `search_memories` calls in parallel (all with the active `user_id` and `metadata.project_id`): ### Step 1: Fetch ALL memories for this project
- `query="architecture decisions"`, `filters={"AND": [{"user_id": "<id>"}, {"metadata": {"project_id": "<pid>"}}, {"metadata": {"type": "decision"}}]}`, `limit=5` Call `get_memories` with:
- `query="anti patterns failures"`, `filters={"AND": [{"user_id": "<id>"}, {"metadata": {"project_id": "<pid>"}}, {"metadata": {"type": "anti_pattern"}}]}`, `limit=5` - `user_id=<active_user_id>`
- `query="task learnings strategies"`, `filters={"AND": [{"user_id": "<id>"}, {"metadata": {"project_id": "<pid>"}}, {"metadata": {"type": "task_learning"}}]}`, `limit=5` - `app_id=<active_project_id>`
- `query="coding conventions"`, `filters={"AND": [{"user_id": "<id>"}, {"metadata": {"project_id": "<pid>"}}, {"metadata": {"type": "convention"}}]}`, `limit=5`
- `query="user preferences"`, `filters={"AND": [{"user_id": "<id>"}, {"metadata": {"project_id": "<pid>"}}, {"metadata": {"type": "user_preference"}}]}`, `limit=5`
- `query="project profile"`, `filters={"AND": [{"user_id": "<id>"}, {"metadata": {"project_id": "<pid>"}}, {"metadata": {"type": "project_profile"}}]}`, `limit=5`
- `query="tooling setup environment"`, `filters={"AND": [{"user_id": "<id>"}, {"metadata": {"project_id": "<pid>"}}, {"metadata": {"type": "environmental"}}]}`, `limit=5`
2. Group results by category. For each category with results, print: This returns every memory scoped to the project — no semantic filtering, no missed results.
``` If `get_memories` doesn't support `app_id` as a direct parameter, use:
## <category_name> (<count> memories) - `filters={"AND": [{"user_id": "<id>"}, {"app_id": "<pid>"}]}`
- <memory_content_truncated_to_100_chars> (score: <similarity_score>)
- ...
```
3. For categories with zero results, print: `<category_name>: (empty)` Pass `page_size=100` (or the maximum allowed) to get a full picture.
4. Print totals at the end: ### Step 2: Run supplementary semantic searches
```
---
Total: <N> memories across <M> categories for project <project_id>
```
5. If ALL categories are empty, print: In parallel, run these `search_memories` calls to get relevance-ranked results for key topics:
```
No memories stored yet for project <project_id>. - `query="architecture decisions design choices"`, `filters={"AND": [{"user_id": "<id>"}, {"app_id": "<pid>"}]}`, `limit=10`
Run /mem0:onboard to import project files, or start working — mem0 captures learnings automatically. - `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`
**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`.
### Step 3: Merge and group
Merge all results by memory ID (deduplicate). For each memory, determine its group using this priority:
1. **Platform `categories` field** (array on each memory, auto-assigned by Mem0). Use the first category value.
2. **`metadata.type` field** (if present, set explicitly by hooks/agent). Use as fallback if no `categories`.
3. **"other"** bucket for memories with neither.
Map category names to display names:
| Platform category / metadata.type | Display name |
|---|---|
| `architecture decisions`, `architecture_decisions`, `decision` | Architecture Decisions |
| `anti patterns`, `anti_patterns`, `anti_pattern` | Anti-Patterns |
| `task learnings`, `task_learnings`, `task_learning` | Task Learnings |
| `coding conventions`, `coding_conventions`, `convention` | Coding Conventions |
| `user preferences`, `user_preferences`, `user_preference` | User Preferences |
| `project profile`, `project_profile` | Project Profile |
| `tooling setup`, `tooling_setup`, `environmental` | Tooling & Setup |
| `technology`, `professional_details` | Tooling & Setup |
| `session_state` | Session State |
| `compact_summary` | Compact Summaries |
| anything else | Other |
### Step 4: Display results
For each group that has results, print:
```
## <display_name> (<count> memories)
- <memory_content_truncated_to_120_chars> (score: <similarity_score_if_available>)
- ...
```
Show up to 5 memories per group. If a group has more than 5, show top 5 by recency (or similarity score if from a search call) and note `... and <N> more`.
For groups with zero results, skip them entirely — don't print empty groups.
### Step 5: Print totals
```
---
Total: <N> unique memories across <M> categories for project <project_id>
Branch: <active_branch>
```
### Step 6: Empty state
If zero memories found for this project, print:
```
No memories stored yet for project <project_id>.
Run /mem0:onboard to import project files, or start working — mem0 captures learnings automatically.
```