A community-driven registry for the Claude Code ecosystem. Not affiliated with Anthropic.
Are you the author? Sign in to claim
A Claude Code plugin that gives Claude persistent memory across sessions — stores lessons and decisions as markdown in y
Persistent, self-evolving memory for Claude Code. Stop re-explaining your project every session.
For LLM/AI tool discovery, see llms.txt.
███╗ ███╗███████╗███╗ ███╗███████╗███╗ ███╗
████╗ ████║██╔════╝████╗ ████║██╔════╝████╗ ████║
██╔████╔██║█████╗ ██╔████╔██║█████╗ ██╔████╔██║
██║╚██╔╝██║██╔══╝ ██║╚██╔╝██║██╔══╝ ██║╚██╔╝██║
██║ ╚═╝ ██║███████╗██║ ╚═╝ ██║███████╗██║ ╚═╝ ██║
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
persistent memory for Claude Code
memem is a Claude Code plugin that gives Claude persistent memory across sessions. An event-triggered miner (Stop-hook → detached mine_delta subprocess) extracts durable lessons (decisions, conventions, bug fixes, preferences) from each new conversation turn, stores them as markdown in an Obsidian vault, and automatically surfaces relevant ones as an Active Memory Slice working state. An explicit narrative assembly path still exists, but the default runtime context is slice-first.
It's local-first: no cloud services, no API keys required, no vendor lock-in. Everything lives in ~/obsidian-brain/memem/memories/ as human-readable markdown.
The miner daemon is gone. miner_daemon.py, miner-wrapper.sh, miner_circuit_breaker.py, miner_errors.py, and miner_protocol.py (~1,500 LOC) have been deleted. Mining now triggers on every Claude Code Stop event via a detached subprocess.
hooks/stop-mine.sh) spawns mine_delta as a detached background process on every Stop event. Hook overhead is ~50ms; extraction happens in background after the hook returns.memem/mine_delta.py — new module (~200 LOC): reads the JSONL session file from a byte offset tracked per session, filters to new turns since the last invocation, calls the same Haiku extract_from_text function, and marks the session in ~/.memem/.mined_sessions.SessionStart hook now scans for JSONL files older than 10 min that aren't in .mined_sessions and spawns up to 3 parallel mine_delta processes. Catches sessions where Stop never fired (Claude crash, kill -9, network drop).mine_delta acquires an fcntl.flock on a lock file per session so concurrent Stop events on the same session don't race.extract_from_text function from mining.py are used. The 18-query benchmark still passes at ≥70% precision.BREAKING — schema rebuild from 18 sections → 2 (Working + Relevant). Retrieval pipeline rewritten from ~12,400 LOC to ~210 LOC (POC v3b architecture). Net delete: 87 files, +915 / -19,941 LOC.
memem/retrieve.py (~145 LOC) + memem/render.py (~65 LOC) — query → embed → cosine top-K + FTS-conditional supplement for version/date literals, then a 2-section renderer. Pure embedding similarity, no scope filter, no kind classifier, no LLM judge, no daemon.## Working (current state) + ## Relevant (ranked list). The v1.13 schema (Anchors / Episodic / Skills / Cases / Working / Pending) is gone.active_memory_slice MCP tool slimmed from 8 params to 2 (query, task_mode). Backward-incompatible.MEMEM_USE_LLM_JUDGE, MEMEM_USE_EMBEDDINGS, MEMEM_RENDER_LEGACY, MEMEM_LLM_JUDGE_TIMEOUT, MEMEM_AUTO_SLICE_DAEMON — all no-op now).slice_daemon and MEMEM_AUTO_SLICE_DAEMON removed. Retrieval is now in-process via memem.retrieve; the hook spawns python directly per prompt. After upgrade run pkill -f slice_daemon once to clear any old process.embeddings.npy via tmpfile + os.replace, embedding_ids.json written first so readers never see torn-write or shape mismatch.Two release pair (v1.9.3 + v1.9.4) targeting silent-corruption paths. All changes are no-ops on the happy path.
atomic_write_text helper (tempfile + fsync + os.replace) applied to 5 previously non-atomic data paths (embedding ID map, tournament cache, lesson frontmatter, dreamer output, mined-sessions reset). MEMEM_FSYNC=0 opts out per-call. Power-loss / NFS-jitter / SIGKILL no longer torn-writes vault data.graph.db and search.db now use journal_mode=WAL + synchronous=NORMAL + busy_timeout=5000, matching session_state_db.py since v1.6. Concurrent reads from the slice engine no longer race with miner writes. New memem --integrity-check CLI command (also called from --doctor) runs PRAGMA integrity_check on all three DBs.--- frontmatter are no longer silently ingested with schema_version=0. New MEMEM_FRONTMATTER_STRICT env var: quarantine (default — move to ~/.memem/quarantine/<hash>_<name>), skip (log + ignore), or raise.commit_deltas hashes (scope_id, dry_run, auto_only, deltas, DELTA_WRITEBACK_VERSION) on entry; matching hits return cached result with deduped: True markers. Cache at ~/.memem/writeback-idempotency.json. Dry-runs and partial-failure batches are not cached. force_writeback=True bypasses the lookup. RMW guarded by fcntl.flock.mine_session's in-process timeout cap. Now the daemon itself increments timeout_failures and permanently skips after MEMEM_MAX_SESSION_TIMEOUTS (default 3).Four layered gating heuristics between the UserPromptSubmit hook and the active-slice engine, plus a new MEMEM_INJECTION_MODE env (auto / hybrid / tool). Hybrid mode reduces hook overhead on trivial turns via: (1) trivial-query regex EN+ZH, (2) per-session turn cadence (MEMEM_INJECT_CADENCE, default 2), (3) empty-streak exponential backoff (MEMEM_EMPTY_STREAK_MAX, default 8), (4) topic-shift cosine via cached query embedding (MEMEM_TOPIC_SHIFT_THRESHOLD, default 0.85). Persistent slice daemon since v1.8 eliminates cold-start cost. See CLAUDE.md for the full tunables table.
memory_save accepts an optional layer param (Claude can override) and auto-classifies otherwise. The slice engine pins L0 (project identity) on every prompt and gates L3 (rare archival) behind explicit search.memory_search, memory_get, memory_timeline, memory_recall, and context_assemble all return slice-formatted output via a single render_slice_markdown dispatcher. context_assemble composes via active_memory_slice rather than rolling its own briefing.A 16-module refactor closed the entire spawn-storm class of bugs that had previously taken down hosts. The miner now uses start_new_session=True + os.killpg for process-group cleanup on timeout, an inverted TransientError/PermanentError taxonomy with PermanentError as default, persisted attempt counters with DLQ at MAX_FAILURES, a SIGTERM-drained graceful shutdown, SQLite WAL state storage, a hand-rolled circuit breaker, structured JSON logs with RotatingFileHandler, and a 5-in-60s wrapper crash guard.
Use memem if:
CLAUDE.md is a single hand-edited file per project. memem gives you:
memem uses layered recall plus a slice-first runtime kernel inspired by claude-mem and mem0. Instead of treating memory as one big briefing, it first turns recall results into an explicit working state:
Session start / user prompt
┌─────────────────────────────┐
│ Candidate generation │
│ • memories / graph │
│ • playbooks │
│ • runtime environment │
│ • current artifacts │
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ Activation judgement │
│ • goals │
│ • constraints │
│ • decisions / failures │
│ • artifacts / tensions │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ Active Memory Slice │ → rendered markdown working state
│ generate_prompt_context() │ used by hooks, MCP, and CLI
└─────────────────────────────┘
The lower-level recall tools still exist for explicit drilling:
memory_search(query) -> compact indexmemory_get(ids=[...]) -> full contentmemory_timeline(id) -> chronological threadcontext_assemble(query, project) -> optional secondary narrative briefingMemory layers (auto-classified at save AND mining time; Claude can override):
| Layer | Purpose | Slice behavior |
|---|---|---|
| L0 | Project identity — tech stack, repo structure, core conventions | Always pinned in every active slice for that project (anchor score 0.95) |
| L1 | Generic conventions — testing, style, commit patterns | Ranked + scored alongside L2 |
| L2 | Domain-specific — most memories (default) | Ranked + scored (default search hits) |
| L3 | Rare/archival — niche failure modes, one-off lessons | Excluded from auto-recall; only via explicit memory_search/memory_get |
A heuristic (mining.py:classify_layer) assigns layers based on importance, structural tags, content length, and the per-project L0 cap. memory_save(content, ..., layer=N) accepts an explicit override (0-3) when Claude judges better than the heuristic.
Token efficiency: session start injects L0 verbatim plus a compact index for L1-L2 (~50 tokens per entry: ID + L + title + snippet). Claude drills into specific memories via memory_get(ids=[...]) only when it needs full detail.
Active Memory Slice runtime kernel:
For ongoing work, active_memory_slice(query, task_mode?) is the default
runtime path. It uses memory_search/FTS/graph/playbooks/transcripts plus
runtime environment and current artifacts as candidate generation, then
activates a structured working state:
Memory Vault
→ Candidate Generation
→ Activation Judgement
→ Active Memory Slice
→ Delta Proposals
→ Memory Vault
The slice explicitly separates goals, constraints, background, decisions,
preferences, failure patterns, artifacts, open tensions, and candidate deltas.
If you pass session_id together with runtime context such as task_mode and
repo_path, memem also carries forward continuity across slices and records
slice history under ~/.memem/.
Default runtime behavior is still non-mutating. Delta proposals are validated
and surfaced in the slice, but safe writeback only runs when you opt in via
writeback_preview=True or auto_commit_safe=True.
Opt-in features:
MEMEM_SHOW_BANNER=1 — show a one-line status banner at session start (off by default)MEMEM_PRETOOL_GATING=1 — enrich Read tool calls with memories about the target file (off by default)Hybrid retrieval gating (v1.9+) — opt in to reduce hook overhead on trivial turns:
MEMEM_INJECTION_MODE — auto (default — no gating, current behavior), hybrid (apply gating heuristics), tool (silence hook, MCP only)MEMEM_INJECT_CADENCE=2 — when hybrid is on, run full slice every Nth turnMEMEM_TOPIC_SHIFT_THRESHOLD=0.85 — cosine-similarity threshold for reusing the previous turn's slice (hybrid only)MEMEM_EMPTY_STREAK_MAX=8 — cap on the exponential backoff after consecutive empty slicesSelective recall (v1.9.6+) — suppress context injection when the slice is low-confidence or out-of-vault:
MEMEM_RECALL_MIN_CONFIDENCE=0.45 — minimum activation confidence required to emit context. Below this, the hook emits a "0 items (low confidence)" systemMessage and suppresses additionalContext.MEMEM_RECALL_MIN_ITEM_SCORE=0.0 — per-item composite-score floor for recall results (0.0 = disabled). L0 project-identity anchors are always exempt.MEMEM_RECALL_OOV_THRESHOLD=0.0 — out-of-vault detection threshold (0.0 = disabled). When set (e.g. 0.3), queries with no L0 keyword overlap and all candidate scores below threshold emit "0 items (out of vault)" and suppress context. Env-var changes take effect on the next hook invocation (no daemon to restart in v2.1.0+).Recommended for high-frequency sessions: export MEMEM_INJECTION_MODE=hybrid
Copy-paste:
claude plugin marketplace add TT-Wang/memem
claude plugin install memem@memem-marketplace
If you already added the marketplace once, future installs only need the second command.
Then:
That's it. On first run, bootstrap.sh self-heals everything:
uv python install 3.11 if your system Python is too olduv if missing (via the official Astral installer).venv (hash-cached against uv.lock)~/.memem/ and ~/obsidian-brain/~/.memem/.capabilities (used for degraded-mode decisions)First run: ~5 seconds. Every run after: ~100ms. No separate pip install step.
Nothing mines until you opt in. memem is strictly opt-in as of v0.9.0 — install does not start the miner or touch your sessions. Type /memem to see status and choose what to do next. You can start mining two ways:
/memem-mine — mine new sessions only (from now on)/memem-mine-history — mine everything, including past history (uses Haiku API credits)Or just tell Claude "start mining new sessions" / "start mining everything including history" — it knows what to do.
/memem-mine if you only want memory from new sessions going forward/memem-mine-history if you want memem to process your old Claude Code sessions tooIf you are unsure, start with /memem-mine. It is the safer and cheaper default.
At session start, the SessionStart hook tries to prime a slice-first working state for the current project scope. On each user prompt, the UserPromptSubmit hook regenerates the slice for the current query. If you just installed memem and have no relevant context yet, the hooks stay quiet and Claude proceeds normally.
You work normally. When each conversation turn completes, the Stop hook spawns mine_delta in the background to extract memories from the new turns using Claude Haiku and write them to your vault. No daemon, no 5-minute wait — memories appear seconds after each turn.
During the session: every user prompt goes through active_memory_slice, which builds a structured working-state briefing from the relevant memories, playbooks, transcripts, graph neighbors, environment facts, and current artifacts. The hooks automatically pass session id and working directory, and the prompt hook infers a task mode when the host does not provide one, so ongoing work can carry constraints, artifacts, and tensions forward across slices. You see an active slice prompt with goals, constraints, background, decisions, failure patterns, open tensions, and artifacts. Claude starts with the current working state instead of a generic briefing.
claude plugin marketplace add TT-Wang/memem
claude plugin install memem@memem-marketplace
Then in Claude Code:
/memem
And choose one:
/memem-mine
or
/memem-mine-history
It saves durable knowledge, not session logs:
tests/ not spec/", "commit messages use imperative mood", "never import from internal/ outside its package")bcrypt.compare is async — must await", "timezone math must use dayjs.utc() or DST shifts the result by an hour")JWT_SECRET defaults to 'secret' if unset — tracked in #123", "pnpm install hangs on corporate VPN, use --network-timeout=600000")nvm use", "Redis must be running on :6380 not :6379")repo/ layer, never raw SQL in handlers")It does NOT save:
transcript_search, not stored as memories)| Store | Path | Purpose |
|---|---|---|
| Memories | ~/obsidian-brain/memem/memories/*.md | Source of truth (human-readable markdown) |
| Playbooks | ~/obsidian-brain/memem/playbooks/*.md | Per-project curated briefings |
| Search DB | ~/.memem/search.db | SQLite FTS5 index (machine-fast lookup) |
| Graph DB | ~/.memem/graph.db | Rebuildable typed/scored memory-edge index |
| Telemetry | ~/.memem/telemetry.json | Access tracking (atomic writes) |
| Event log | ~/.memem/events.jsonl | Append-only audit trail |
| Capabilities | ~/.memem/.capabilities | Degraded-mode flags written by bootstrap |
| Bootstrap log | ~/.memem/bootstrap.log | First-run diagnostics |
You can point memem elsewhere via MEMEM_DIR and MEMEM_OBSIDIAN_VAULT env vars.
All recall tools return slice-formatted markdown via a unified render_slice_markdown dispatcher (introduced in v1.1) so output structure is consistent across tools.
| Tool | What it does |
|---|---|
memory_save(content, title, tags, layer?) | Store a lesson. Security-scanned for prompt injection and credential exfil before writing. layer is optional (0-3); auto-classifies via classify_layer if omitted. |
memory_search(query, limit, scope_id) | [L1] Compact index slice — IDs + layer + title + 1-line snippet. Use first to narrow candidates. |
memory_get(ids, scope_id) | [L2] Full content slice for specific memory IDs. Use after memory_search. |
memory_timeline(memory_id, scope_id) | [L3] Chronological thread via related[] graph + same-project window. |
memory_recall(query, scope_id, limit) | Legacy alias — search + full content in one slice. |
memory_list(scope_id) | List all memories with stats, grouped by project. |
memory_import(source_path) | Bulk import from files, directories, or chat exports. |
transcript_search(query) | Search raw Claude Code session JSONL logs (not the mined memories). |
context_assemble(query, project) | Composite briefing: calls active_memory_slice 1-2 times (project + general scope when sparse), merges into one assembled slice. |
memory_graph(memory_id) | Inspect typed/scored graph neighbors for one memory. |
memory_graph_audit() | Report graph quality issues: orphans, dead links, hubs, stale edges. |
memory_graph_rebuild(scope_id) | Rebuild the graph side index from the Obsidian vault. |
active_memory_slice(query, task_mode?) | v2.0.0: thin wrapper over retrieve() + render_slice(). Returns markdown with ## Working + ## Relevant (top-K by cosine + FTS supplement for version/date literals). |
Use the CLI when you want raw slice JSON, continuity debugging, or explicit writeback preview:
python3 -m memem.server slice "continue auth rollout" --scope memem --session-id sess-42 --cwd "$PWD" --task-mode coding --json --no-llm
python3 -m memem.server slice "continue auth rollout" --scope memem --session-id sess-42 --cwd "$PWD" --task-mode coding --writeback-preview --json --no-llm
python3 -m memem.server slice "continue auth rollout" --scope memem --session-id sess-42 --cwd "$PWD" --task-mode coding --auto-commit-safe --json --no-llm
Semantics:
slice is read-side and non-mutating--writeback-preview runs the delta pipeline in dry-run mode--auto-commit-safe commits only deltas classified as auto-safe/memem — welcome, status, help/memem-status — memory count, projects, search DB size, miner health/memem-doctor — preflight health check with fix instructions for any blocker/memem-mine — opt in to event-triggered mining (touches ~/.memem/.miner-opted-in; new sessions mined automatically via the Stop hook)/memem-mine-history — opt-in + backfill all pre-install Claude Code sessionsclaude CLI isn't on my PATH?memem enters degraded mode — it still works, just without Haiku-powered context assembly and smart recall. You get FTS-only keyword recall instead of query-tailored briefings. Every session shows [memem] N memories · miner OK · assembly degraded (claude CLI missing — FTS-only recall) at the top of the context, so you know why.
This is by design: missing optional dependencies should degrade, not fail.
Run /memem-doctor. It runs the same preflight the bootstrap shim runs (Python version, mcp importable, claude CLI on PATH, directory writability, uv available) plus a SQLite integrity check on all three WAL DBs (v1.9.3+), then prints a report labelled HEALTHY, DEGRADED, or FAILING with explicit fix instructions for each blocker.
For deeper debugging:
tail -f ~/.memem/bootstrap.log # first-run shim log
cat ~/.memem/events.jsonl # memory operation audit trail
cat ~/.memem/mine_delta.log # stop-hook mining log (v2.1.0+)
python3 -m memem.server --status # detailed status dump
python3 -m memem.server --integrity-check # PRAGMA integrity_check on every DB
Claude Code Stop event fires → stop-mine.sh hook spawns mine_delta (detached, ~50ms)
→ mine_delta reads session JSONL from byte offset (new turns only)
→ Filters to human messages + assistant prose (strips tool calls, system reminders)
→ One Haiku call with the delta context: "extract durable lessons"
→ Haiku returns JSON array of memory candidates
→ Each candidate runs: security scan → dedup check → contradiction detection → save
→ Offset advanced; session marked in ~/.memem/.mined_sessions
→ SessionStart stale-sweep catches any sessions where Stop never fired (crash, kill -9)
First message in a new session → auto-recall.sh hook fires
→ Reads ~/.memem/.capabilities for status banner
→ Builds an active memory slice from recall candidates + graph/playbook/transcript context
→ Emits a structured "Active Memory Slice" prompt block
→ If the slice engine is unavailable → falls back to compact recall
→ Either way, Claude starts its reply with active work-state context already loaded
memem is split into small, focused modules:
models.py — data types, path constantssecurity.py — prompt injection + credential exfil scanningtelemetry.py — access tracking, event log (atomic writes, fcntl-locked)search_index.py — SQLite FTS5 indexgraph_index.py — typed/scored related-memory graph side indexretrieve.py — v2.0.0: cosine top-K + FTS-conditional supplement for version/date literals. Mtime-invalidated vault index + embedding caches.render.py — v2.0.0: 2-section renderer (## Working + ## Relevant).obsidian_store.py — memory I/O, dedup scoring, contradiction detection, layer auto-classification on saverecall.py — slice-format recall tools (memory_search/memory_get/memory_timeline/memory_recall) — surgically rewritten in v2.0.0 with inline _render_recall_markdown (the legacy active_slice renderer is gone)playbook.py — per-project playbook grow + refineassembly.py — context_assemble composes via recall pipelinecapabilities.py — runtime feature detection for degraded modestorage.py — server-lifecycle helpers (PID management, miner auto-start)server.py — thin MCP entrypoint (FastMCP imported lazily)cli.py — command dispatcher for non-MCP entrypointsmining.py — session mining pipeline (Haiku extraction, extract_from_text)mine_delta.py — v2.1.0: event-triggered delta miner; reads new turns since last offset, calls extract_from_text, marks session completesession_state.py / session_state_db.py — SQLite WAL state for the miner (auto-migrates from JSONL on first run)Multi-signal recall scoring:
Related-memory graph:
The Obsidian markdown files remain the source of truth. The related: [...]
frontmatter stays intentionally simple so memories are portable and readable.
memem also builds ~/.memem/graph.db, a local SQLite side index with typed,
scored edges such as same_topic, supports, depends_on, supersedes, and
contradicts. Recall uses this graph when available and falls back to the
Markdown related field if the graph has not been built yet.
Useful maintenance commands:
memem graph rebuild
memem graph audit
memem graph stats
memem graph neighbors <memory-id>
Memory schema (markdown frontmatter):
---
id: uuid
schema_version: 1
title: "descriptive title"
project: project-name
tags: [mined, project-name]
related: [id1, id2, id3]
created: 2026-04-13
updated: 2026-04-13
source_type: mined | user | import
source_session: abc12345
importance: 1-5
status: active | deprecated
valid_to: # set when deprecated
contradicts: [id1] # flagged conflicts
---
| Env var | Default | Purpose |
|---|---|---|
MEMEM_DIR | ~/.memem | State directory (PID files, search DB, logs) |
MEMEM_OBSIDIAN_VAULT | ~/obsidian-brain | Vault location |
MEMEM_EXTRA_SESSION_DIRS | (none) | Colon-separated extra session dirs to mine |
MEMEM_MINER_SETTLE_SECONDS | 1800 | (legacy) Settle-window seconds. In v2.1.0 both the Stop hook AND --mine-all bypass this gate; retained only for forward-compat with future tooling that may opt into it. |
MEMEM_SKIP_SYNC | 0 | Bootstrap skips uv sync when set to 1 (dev only) |
memem works without Obsidian — it just writes markdown. But Obsidian gives you graph view and backlinks for free:
~/obsidian-brain as a vaultmemem/memories/, playbooks in memem/playbooks/related fielduv (auto-installed by bootstrap.sh on first run)claude CLI on PATH (optional — required for Haiku-powered assembly; degraded mode works without it)git clone https://github.com/TT-Wang/memem.git
cd memem
pip install -e ".[dev]"
pytest # ~391 tests (14 skipped)
ruff check . # lint
mypy memem # type check
See CONTRIBUTING.md for the PR process and CHANGELOG.md for version history.
memory_save patterns land in memem's recall index, so next week's
run starts with last week's lessons already loaded.MIT
Run Claude Code as an MCP server so any agent can delegate coding tasks to it
Browser automation using accessibility snapshots instead of screenshots
English-first Korean equity intelligence MCP — DART filings, foreign-holder 5%-rule flows, activist filings, KRX news. F
Unity MCP acts as a bridge between AI assistants and your Unity Editor. Give your LLM tools to manage assets, control sc