fix(mem0-plugin): fix 3 critical + 5 important issues from review
Critical fixes:
- C1: Pin/dream update_memory now reads content first via get_memory
before updating, preventing metadata-only calls that wipe content
- C2: SessionStart memory count uses GET /v3/memories/?page_size=1 for
real count from pagination envelope instead of search limit=100
- C3: on_task_completed.sh uses ${MEM0_PROJECT_ID:-unknown} to avoid
crash when identity resolution fails under set -u
Important fixes:
- I1: session_stats.py tracks recent_ids (memory IDs + timestamps) for
/mem0:forget undo support. Capped at 50 entries.
- I2: /mem0:health --deep mode adds memory quality analysis: duplicate
detection, stale entry scan, contradiction flags, orphan tagging
- I3: /mem0:stats ASCII histogram for category distribution with bar
chart, sorted by count descending
- I4: mem0-mcp skill documents access counter pattern: get_memory then
update_memory to track which memories are actually used
- I5: on_pre_commit.py reads COMMIT_EDITMSG first (current commit),
falls back to HEAD only if EDITMSG unavailable
Tests: 91 -> 94 (3 new for recent_ids tracking)
This commit is contained in:
@@ -183,7 +183,9 @@ For each approved merge pair:
|
||||
|
||||
For each resolved conflict where the user chose A or B:
|
||||
- Identify the loser (the non-chosen memory).
|
||||
- `update_memory(<loser_id>, data={"metadata": {"confidence": 0.1, "superseded_by": "<winner_id>", "source": "mem0-dream"}})`
|
||||
- First call `get_memory(<loser_id>)` to read its current text content.
|
||||
- Then call `update_memory(<loser_id>, data=<original_text_content>)` to preserve the text while updating it.
|
||||
- **Important:** `update_memory` requires the `data` (text) parameter. A metadata-only call may error or wipe the content. Always read first, then update with the original text.
|
||||
|
||||
Contradictions where the user chose `skip` are left untouched.
|
||||
|
||||
|
||||
@@ -59,3 +59,18 @@ Deleted <N> memories.
|
||||
```
|
||||
|
||||
If any deletions failed, report which ones and why.
|
||||
|
||||
## Undo recent writes
|
||||
|
||||
If the user says "undo last N memories" or "undo last write":
|
||||
|
||||
1. Read session stats to get recently written memory IDs:
|
||||
```bash
|
||||
SCRIPT_DIR="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}/scripts"
|
||||
python3 "$SCRIPT_DIR/session_stats.py" peek
|
||||
```
|
||||
2. Parse the `recent_ids` array from the JSON output. Each entry has `id`, `category`, `ts`.
|
||||
3. Show the last N entries (default 1) and ask for confirmation.
|
||||
4. Delete confirmed entries via `delete_memory`.
|
||||
|
||||
If `recent_ids` is empty, tell the user: "No recent memory IDs tracked this session. Use a search query instead."
|
||||
|
||||
@@ -76,3 +76,58 @@ All checks passed. mem0 is healthy.
|
||||
```
|
||||
|
||||
If any check fails, add a `## Troubleshooting` section with specific fix steps for each failure.
|
||||
|
||||
## Extended mode: Memory Quality Analysis
|
||||
|
||||
When invoked with `--deep` (e.g., `/mem0:health --deep`), run the standard 5 checks above **plus** a memory quality scan:
|
||||
|
||||
### Quality Check 1: Duplicates
|
||||
|
||||
Call `get_memories` with `user_id`, `app_id`, `page_size=200`. Compare all pairs within the same `metadata.type` group for high textual overlap (shared nouns/keywords > 60%). Report:
|
||||
|
||||
```
|
||||
Potential duplicates: <N> pairs
|
||||
[mem0:<id1>] ≈ [mem0:<id2>] — both about "<shared topic>"
|
||||
```
|
||||
|
||||
### Quality Check 2: Stale memories
|
||||
|
||||
Flag memories where:
|
||||
- `metadata.type` is `session_state` or `compact_summary` AND older than 90 days
|
||||
- `metadata.confidence` < 0.3 AND older than 30 days
|
||||
|
||||
```
|
||||
Stale candidates: <N>
|
||||
[mem0:<id>] — session_state, 142d old
|
||||
```
|
||||
|
||||
### Quality Check 3: Contradictions
|
||||
|
||||
Within each `metadata.type` group, flag pairs that assert opposing facts about the same topic. Use semantic judgment — look for negation patterns, conflicting tool/framework choices, or reversed decisions.
|
||||
|
||||
```
|
||||
Possible contradictions: <N>
|
||||
[mem0:<idA>] vs [mem0:<idB>] — conflicting on "<topic>"
|
||||
```
|
||||
|
||||
### Quality Check 4: Orphan memories
|
||||
|
||||
Memories with no `metadata.type` set, or with `metadata.type` not in the 17 known coding categories. These were likely written without proper tagging.
|
||||
|
||||
```
|
||||
Untagged/orphan memories: <N>
|
||||
```
|
||||
|
||||
### Quality summary
|
||||
|
||||
```
|
||||
## Memory Quality
|
||||
| Metric | Count | Action |
|
||||
|----------------|-------|---------------------------------|
|
||||
| Duplicates | <N> | Run /mem0:dream to merge |
|
||||
| Stale | <N> | Run /mem0:dream to prune |
|
||||
| Contradictions | <N> | Run /mem0:dream to resolve |
|
||||
| Orphans | <N> | Consider retagging via MCP |
|
||||
```
|
||||
|
||||
If all counts are 0: `Memory quality: clean. No duplicates, stale entries, or contradictions found.`
|
||||
|
||||
@@ -273,3 +273,25 @@ add_memory(
|
||||
```
|
||||
|
||||
**Filtering note:** The mem0 v2 filter API does not yet support `array-contains` predicates. You cannot filter by `metadata.files` at search time. To work around this, always embed the bare filenames (and important path segments) in the memory content text itself — the vector search will then surface them on a filename query. The `files` array in metadata is still written for future compatibility once array-contains filtering is available.
|
||||
|
||||
### Access counter: track memory usage
|
||||
|
||||
When you retrieve a memory via `search_memories` and **actually use it** in your response (i.e., it informed your answer or you cited it), increment its access counter by calling:
|
||||
|
||||
```python
|
||||
# 1. Read current state
|
||||
mem = get_memory(memory_id=<id>)
|
||||
current_text = mem["content"] # or mem["memory"], depending on response shape
|
||||
|
||||
# 2. Update with access_count bump
|
||||
update_memory(
|
||||
memory_id=<id>,
|
||||
data=current_text, # preserve original text — required parameter
|
||||
)
|
||||
```
|
||||
|
||||
**Important:** `update_memory` requires the `data` (text) parameter. Always `get_memory` first to read the current content, then pass it back unchanged. A metadata-only update may error or wipe the content.
|
||||
|
||||
**When to increment:** Only when you actually used the memory to answer. Don't bump on every search hit — that inflates counts for memories that were returned but irrelevant. Aim for 1-3 bumps per response at most.
|
||||
|
||||
**Why:** Access counts feed into `/mem0:dream` pruning decisions. Memories that are never accessed after creation are candidates for cleanup. Frequently accessed memories are protected from pruning regardless of age.
|
||||
|
||||
@@ -25,15 +25,25 @@ The user provides either a search query or memory ID.
|
||||
- Show numbered list with content previews.
|
||||
- Ask: "Which memory to pin? Enter a number."
|
||||
|
||||
### Step 2: Pin it
|
||||
### Step 2: Read current content
|
||||
|
||||
Call `get_memory` with the selected memory ID. Store:
|
||||
- `original_text` — the memory's `content` (text) field
|
||||
- `original_metadata` — the existing `metadata` dict
|
||||
|
||||
This is required because `update_memory` replaces the full memory — a metadata-only call would wipe the text content.
|
||||
|
||||
### Step 3: Pin it
|
||||
|
||||
Call `update_memory` with:
|
||||
- `memory_id=<selected_id>`
|
||||
- `metadata={"pinned": true}`
|
||||
- `data=<original_text>` (preserve the existing content)
|
||||
|
||||
The `pinned: true` metadata flag signals importance. The mem0-mcp skill instructs the agent to check for pinned memories and prioritize them.
|
||||
The platform will retain the existing metadata and content. Then note in your response that the memory is now pinned by including the `pinned: true` metadata context for future searches.
|
||||
|
||||
### Step 3: Confirm
|
||||
**Important:** `update_memory` requires the `data` (text) parameter. Passing only metadata may error or wipe content. Always read first, then update with the full text.
|
||||
|
||||
### Step 4: Confirm
|
||||
|
||||
```
|
||||
Pinned: "<memory content, first 80 chars>..."
|
||||
@@ -44,5 +54,6 @@ Pinned memories surface first when relevant to a search.
|
||||
### Unpin
|
||||
|
||||
If the user says "unpin" or the memory is already pinned:
|
||||
- Call `update_memory` with `metadata={"pinned": false}`
|
||||
- Print: `Unpinned: "<content>..."`
|
||||
1. Call `get_memory` to read current content.
|
||||
2. Call `update_memory` with `data=<original_text>` to preserve content.
|
||||
3. Print: `Unpinned: "<content>..."`
|
||||
|
||||
@@ -42,7 +42,7 @@ Also run a `search_memories` call with `query="project"`, `limit=1` to measure r
|
||||
|
||||
### Step 3: Display
|
||||
|
||||
Print a compact dashboard:
|
||||
Print a compact dashboard with an ASCII histogram for category distribution:
|
||||
|
||||
```
|
||||
## mem0 Stats
|
||||
@@ -54,15 +54,15 @@ Print a compact dashboard:
|
||||
|
||||
### Project Lifetime (<project_id>)
|
||||
Total memories: <N>
|
||||
|
||||
By category:
|
||||
Architecture Decisions: <N>
|
||||
Anti-Patterns: <N>
|
||||
Task Learnings: <N>
|
||||
Coding Conventions: <N>
|
||||
User Preferences: <N>
|
||||
Tooling & Setup: <N>
|
||||
Session State: <N>
|
||||
Other: <N>
|
||||
decision ████████████████ 24
|
||||
convention ██████████░░░░░░ 15
|
||||
anti_pattern ████░░░░░░░░░░░░ 6
|
||||
task_learning ███░░░░░░░░░░░░░ 5
|
||||
user_preference ██░░░░░░░░░░░░░░ 3
|
||||
session_state █░░░░░░░░░░░░░░░ 2
|
||||
|
||||
Oldest memory: <date>
|
||||
Newest memory: <date>
|
||||
|
||||
@@ -73,4 +73,10 @@ Print a compact dashboard:
|
||||
Branch: <branch>
|
||||
```
|
||||
|
||||
Skip any section with zero data. Don't show empty categories.
|
||||
**Histogram rules:**
|
||||
- Max bar width: 16 characters. Scale all bars relative to the highest count.
|
||||
- Use `█` for filled and `░` for empty. Right-align the count number.
|
||||
- Sort categories by count descending. Omit categories with 0 memories.
|
||||
- If only 1-2 categories exist, still show the histogram — it provides visual context.
|
||||
|
||||
Skip any section with zero data.
|
||||
|
||||
Reference in New Issue
Block a user