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:
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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 ".")" \
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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:
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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.
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user