fix(plugin): expire stale state captures + document plugin-update restart

Memory expiration + recency
  Hook-side captures (session_state in on_pre_compact.py and
  compact_summary in capture_compact_summary.py) now set
  expiration_date = today + 90 days. These types describe a single
  moment of project state and become stale clutter after a quarter.
  Durable types written by the agent (decision, anti_pattern,
  convention, user_preference, task_learning, environmental) stay
  unexpired by design -- a decision from a year ago is still a
  decision.

  Skill updated with two new sections:
    - "Expiration: high-churn vs durable" -- table mapping each
      metadata.type to whether it should carry an expiration_date.
    - "Recency filter on recall" -- agent should layer a
      created_at >= <90 days ago> filter on recall when the user
      asks about *current* state, and skip it when looking for
      durable facts.

Plugin-update / MCP reconnect
  When the plugin updates, the MCP server connection in the running
  client goes stale -- there's no in-process reconnection path from
  inside the plugin. Documenting the workaround is the realistic
  fix: README now has an "Updating the plugin" section explaining
  /restart (Claude Code), quit-and-relaunch (Cursor), restart
  (Codex). The auth header re-reads MEM0_API_KEY from the new
  shell, so users don't have to re-enter the key.
This commit is contained in:
Mgeeeek
2026-05-08 21:39:28 +05:30
parent 2946fc9da5
commit 8b36f7c50e
4 changed files with 46 additions and 0 deletions
+12
View File
@@ -157,6 +157,18 @@ After installing, confirm the MCP server is connected:
- **Mem0 SDK Skill** — Guides the AI on how to integrate the Mem0 SDK (Python & TypeScript) into your applications.
- **Memory Protocol Skill** — Codex-specific skill that instructs the agent to retrieve relevant memories at task start, store learnings on completion, and capture session state before context loss. Complements the lifecycle hooks on Codex.
## Updating the plugin
When the plugin updates (new version pulled from the marketplace, or a fresh local install), the MCP server connection in your existing Claude Code / Cursor / Codex session is left holding a stale handle and stops responding. **Restart your client to reconnect:**
- **Claude Code:** run `/restart` in the prompt, or close and reopen the CLI.
- **Cursor:** quit and relaunch.
- **Codex:** restart the editor session.
Your `MEM0_API_KEY` doesn't need to be re-entered — the auth header is re-read from your environment on the new session. The plugin's MCP config uses `${MEM0_API_KEY}` interpolation at session start, not at install time, so as long as the env var is set persistently (in your shell profile or `~/.claude/settings.json` `env` block), reconnection is automatic on restart.
If reconnection still fails after a restart, check that `MEM0_API_KEY` is reachable in the new shell (`echo $MEM0_API_KEY`) and confirm you're using a key that starts with `m0-` (from https://app.mem0.ai/dashboard/api-keys, not a legacy token).
## Optional: tune categories for coding workflows
mem0 auto-tags every memory with one or more `categories` from a project-level list. The default list is consumer-oriented (`food`, `hobbies`, `music` …) — useful for chat assistants, less so for code. A one-shot script in this plugin replaces it with a coding-focused taxonomy:
@@ -22,6 +22,7 @@ import os
import sys
import urllib.error
import urllib.request
from datetime import date, timedelta
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _identity import resolve_user_id
@@ -45,6 +46,8 @@ if os.environ.get("MEM0_DEBUG"):
API_URL = "https://api.mem0.ai"
MAX_TAIL_LINES = 2000
MAX_SUMMARY_CHARS = 50000
# Compact summaries describe a single session's state -- stale after a quarter.
COMPACT_SUMMARY_EXPIRY_DAYS = 90
def tail_lines(filepath: str, n: int) -> list[str]:
@@ -92,6 +95,7 @@ def find_compact_summary(lines: list[str]) -> str:
def store_summary(api_key: str, summary: str, user_id: str, session_id: str) -> bool:
expires = (date.today() + timedelta(days=COMPACT_SUMMARY_EXPIRY_DAYS)).isoformat()
body = {
"messages": [{"role": "user", "content": summary}],
"user_id": user_id,
@@ -101,6 +105,7 @@ def store_summary(api_key: str, summary: str, user_id: str, session_id: str) ->
"session_id": session_id,
},
"infer": False,
"expiration_date": expires,
}
data = json.dumps(body).encode("utf-8")
+7
View File
@@ -20,6 +20,7 @@ import os
import sys
import urllib.error
import urllib.request
from datetime import date, timedelta
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _identity import resolve_user_id
@@ -45,6 +46,10 @@ MAX_TAIL_LINES = 500
MAX_USER_MESSAGES = 30
MAX_BASH_COMMANDS = 20
MAX_ASSISTANT_TEXT = 10000
# session_state captures churn fast (active codebase, files in flight). Past
# ~3 months they're stale noise. Durable facts (decisions, conventions) are
# stored separately by the agent without an expiration_date.
SESSION_STATE_EXPIRY_DAYS = 90
def tail_lines(filepath: str, n: int) -> list[str]:
@@ -164,6 +169,7 @@ def build_content(state: dict, source: str) -> str:
def store_memory(api_key: str, content: str, user_id: str, source: str, session_id: str = "") -> bool:
"""Store session state as a memory via the Mem0 REST API."""
expires = (date.today() + timedelta(days=SESSION_STATE_EXPIRY_DAYS)).isoformat()
body = {
"messages": [
{"role": "user", "content": content}
@@ -174,6 +180,7 @@ def store_memory(api_key: str, content: str, user_id: str, source: str, session_
"source": source,
"session_id": session_id,
},
"expiration_date": expires,
}
data = json.dumps(body).encode("utf-8")
+22
View File
@@ -99,6 +99,28 @@ Extract key learnings and store them using the `add_memory` tool:
> `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
Some memory types are state snapshots that go stale fast; others are durable facts that should outlive the session that created them. Mark the difference with `expiration_date` on writes.
| Type | Expiration | Why |
|---|---|---|
| `session_state`, `compact_summary` | `expiration_date` ≈ today + 90 days | Describe a single moment of project state. Useless after a quarter; clutter the recall surface. |
| `decision`, `anti_pattern`, `convention`, `user_preference`, `task_learning`, `environmental` | omit `expiration_date` | Durable facts. A decision made last year is still a decision; same for a convention or a user preference. |
`add_memory` accepts `expiration_date` as a string (`"YYYY-MM-DD"`). The two server-side hooks (`on_pre_compact.py`, `capture_compact_summary.py`) already set this for the types they write. When you write directly via the MCP tool, follow the same rule.
### Recency filter on recall
When the user is asking about *current* state ("where were we", "what's the active task", "the latest decision on X"), filter recall to recent memories so stale snapshots don't surface:
```python
# Last 90 days only
{"AND": [{"user_id": "<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.
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.
### Use `infer=False` for already-structured content