cortex
This server provides persistent, local-first memory for Claude Code, enabling knowledge retention across sessions with all data stored as human-readable markdown (optionally in an Obsidian vault).
Save memories: Persist decisions, conventions, bug fixes, and preferences with automatic security scanning (prompt injection, credential exfiltration) and fuzzy deduplication.
Search memories: Retrieve relevant memories using natural-language queries, ranked by FTS relevance, recency, access history, and importance.
List all memories: Audit the full inventory of stored memories, including source type breakdowns (mined/user/import) and project scopes.
Bulk import: Seed the memory store from files, directories, or chat exports (e.g.
CLAUDE.md, decision logs).Search session transcripts: Find raw conversation excerpts from past Claude Code sessions to recover full context behind a mined memory or topic.
Assemble context briefings: Generate a concise, query-tailored markdown summary (~300–800 tokens) by synthesizing memories, project playbooks, and session transcripts via Claude Haiku (falls back to raw materials if unavailable).
Background mining: Automatically extract lessons from completed Claude Code sessions.
Memory layers (L0–L3): Structured recall behavior including always-pinned project identity and archival gating.
Related-memory graph: Inspect relationships between memories for deeper context.
Stores memories as human-readable markdown files in an Obsidian vault, enabling browsing, editing, and visualization through Obsidian's graph view and backlinks features.
memem
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 CodeWhat is memem?
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.
What's new in v2.9.1 (Path-Scope Activation)
v2.9.1 activates the path-scoped retrieval that shipped dormant in v2.9.0: recall now auto-derives paths_context from the current session so the paths: bonus actually fires without any caller action. The new recent_session_paths() in memem/transcripts.py resolves session_id → JSONL via a direct CWD-slug stat first (O(1)), falling back to next(base_dir.rglob(...), None) (short-circuit on first match); it then tail-reads the last 512 KB of the file (~5 ms even on a 64 MB session, vs ~390 ms for a full read), walks assistant turns most-recent-first, and extracts the top-N deduplicated file paths from Read/Edit/Write/NotebookEdit file_path inputs and Bash command first-line path tokens via _extract_paths_from_content_blocks(). The auto-derivation is wired into active_memory_slice (MCP tool), the auto-recall.sh UserPromptSubmit hook, and the cli.py slice path; caller-supplied paths_context still wins; derivation failures are logged at debug/warning and never propagate — any exception returns [] silently. No API or schema changes; 12 new tests in tests/test_recent_paths.py cover extraction, recency/dedup/limit, missing/malformed sessions, and end-to-end active_memory_slice integration. See CHANGELOG for full details.
What's new in v2.9.0 (Tool Diet + Transcript FTS5 + Path Scope)
v2.9.0 trims the MCP surface from 14 tools to 6 — removing memory_recall, memory_graph, memory_graph_audit, memory_graph_rebuild, memory_list, memory_import, context_assemble, and memory_remind from the MCP layer (CLI and library equivalents remain for all eight) — and cuts total tool-description schema size 57% (12,827 → 5,474 chars). transcript_search is backed by a persistent FTS5 index at ~/.memem/transcript_fts.db (one row per Q/A turn-pair, index_session() called incrementally from mine_delta; old single-row-per-session indexes auto-migrate; the grep fallback is bounded by size/count/time caps and never silently truncates). Path-scoped memories arrive via a new paths: frontmatter field and a 1.05× w_path bonus in retrieve() for memories whose path globs match paths_context; memory_save gains an optional paths param; active_memory_slice accepts paths_context; and the miner annotates candidates with paths: when ≥2 paths each appear ≥3 times. Telemetry isolation is hardened via MEMEM_TELEMETRY_SOURCE. Closed-loop evaluation tooling is wired: a canary --doctor check, --dual-engine replay, and deferred-gate comments in lessons.py / feedback.py. Benchmark 79.3% (119/150), all acceptance gates pass. See CHANGELOG for full details.
What's new in v2.8.0 (Vault Structure)
v2.8.0 retires the L0–L3 layer system and replaces it with a context model that reflects how memory actually works. The starting point was honest data: 462 memories had been auto-classified L0 ("always relevant"), which was not a layer, it was a full briefing that no session could absorb. The new model has three tiers: (1) profiles — schema-shaped always-injected documents per user (profile_user.md: Preferences / Conventions / Environment) and per project (profile_<project>.md: Identity / Stack & Structure / Conventions), stored at <vault>/memem/profiles/, populated by the miner's new PROFILE reconcile op and bootstrappable from your existing vault via --migrate-layers; (2) working rules — type:procedural memories (failure→fix patterns, corrections) ranked by citation count and injected as a ## Working rules block at session start (≤1200 chars); (3) episode index — the existing 25-entry episodic title index, unchanged. Consolidation logic moves from the deleted consolidation.py into the dreamer's cluster_merge category with a bug fix: only the members listed in supporting_ids are bi-temporally invalidated after a successful merge, not all cluster members unconditionally. The dreamer gains reflection_with_citations (synthesizes type:insight memories from episodic clusters) and tense_rewrite (corrects expired future-tense memories) as additive-safe categories that fire automatically every 25 substantive mining deltas via --dream --safe-auto. The 18-query benchmark improved to 80.0% (120/150) after L0 MMR pre-seeding was removed — the anchor mechanism was penalizing relevance, not helping it (measured during release validation; up from 79.3%/119/150 in v2.7.0). See CHANGELOG for full details.
What's new in v2.7.0 (Write Path + Instrumentation)
v2.7.0 makes the miner smarter about what it writes: instead of adding every extracted candidate blindly, it compares each one against its nearest vault neighbors in a single batched Haiku call and picks ADD, UPDATE, SUPERSEDE, or NOOP with safety rails (protected-target guard, truncation guard, ≤5 destructive ops per delta, global fallback to ADD-all on any exception). The previously unreachable bi-temporal invalidation path (invalid_at / replaced_by) is now exercised by SUPERSEDE ops. Every retrieval is now linked to a session id, and the miner scans assistant text for cited memory ids and writes {"type":"citation"} rows to .recall_log.jsonl — closing the feedback loop so --analyze-recalls shows citation rate per tool and the dreamer demotion guard is live again after sitting inert since v2.5. Additional improvements: key expansion (miner emits up to 8 synonyms/aliases per memory, FTS+BM25 indexed), tool-trace digest (Bash/Edit decisions are now minable), memory_save three-band dedup (merge instead of reject for 0.70–0.92 near-duplicates), --purge-contaminated --exclude, and flock-safe feedback EMA writes. Benchmark unchanged at 79.3%. See CHANGELOG for full details.
What's new in v2.6.0 (One Engine)
v2.6.0 unifies retrieval: a single three-way RRF engine (cosine + BM25 + FTS5) now serves every call path — hook, MCP tools, and CLI. The unbenchmarked heuristic engine that served memory_search/memory_recall since v2.4.0 is deleted (−218 LOC: 5-signal re-ranker, ngram union, file-scan fallback, and a 15% feedback weight reading a file nothing ever wrote). Deprecated and invalidated memories are now excluded from the retrieval index at vault-load time, fixing a leak via the hook path. The scope_id parameter changes from a hard filter to a soft ranking bonus — cross-project results that score well now appear. The 18-query benchmark is maintained at ≥74% precision (79.3%, measured during release validation). See the A/B comparison report for transparency on result-set divergence vs the prior engine, and CHANGELOG for full details.
What's new in v2.5.0 (Repair & Prune)
v2.5.0 is a maintenance release: 24 audited defects fixed and ~2,256 LOC of dead code removed. No new memory capabilities. Key fixes: self-mining contamination guard (stale-sweep now skips headless mining transcripts), RRF/MMR scoring bugs fixed (18-query benchmark measured during release validation: 74.7% → 78.7%), embedding index staleness fixed (incremental upsert + mtime invalidation + cross-process flock), double access-count stores eliminated (telemetry sidecar is now the single store), episode deduplication (one stable-id episode per session). Removed: compaction.py, reaper.py, attribution pipeline, storage.py, 8 dead settings knobs, and hybrid injection mode (was documented but never implemented). New CLI: python3 -m memem.server --purge-contaminated [--apply]. See CHANGELOG for full details.
What's new in v2.4.0 (passive mode + episode catalog + telemetry)
v2.4.0 flips the default injection mode from auto to tool: Claude no longer receives memory context on every prompt automatically. Instead, it pulls memory on demand via memory_search, memory_get, and active_memory_slice. This eliminates ~85% per-turn noise that was masking v2.3.0's ranking improvements. At session start, Claude now receives a ## Episode index section listing up to 25 episodic memories by title — a clean menu without a full content dump. Every retrieval is logged to ~/.memem/.recall_log.jsonl; run python3 -m memem.server --analyze-recalls to inspect recall patterns. All 5 MCP tool descriptions have been rewritten to be trigger-explicit so Claude knows when to call each tool. Existing users with MEMEM_INJECTION_MODE=auto in their shell profile are unaffected; see the CHANGELOG breaking change banner to restore old behavior.
What's new in v2.3.0 (hybrid retrieval)
active_memory_slice now uses a two-stage hybrid retrieval pipeline: BM25 + cosine Reciprocal Rank Fusion (RRF) builds a top-20 candidate pool, then Maximal Marginal Relevance (MMR, λ=0.7) selects the final 8 results to suppress near-duplicate memories. Access writeback is on by default (MEMEM_WRITEBACK_ENABLED=1); each recall fires a daemon thread that increments access_count in a JSON sidecar at ~/.memem/telemetry.json (NOT in memory frontmatter — deliberate, to keep load_vault_index's mtime cache stable). Net benchmark result: 75.3% precision (+1.3 pp vs v2.0.0 baseline), 133ms warm latency. Recency decay scoring was prototyped but reverted due to a negative-cosine ranking regression — see CHANGELOG for details.
What's new in v2.2.0 (episodic seeds)
Two architectural additions targeting the episodic-query gap vs everme. (a) retrieve.py parses temporal phrases in queries ("yesterday" / "last week" / "N days ago") and re-ranks candidates by created: proximity (+0.2 boost). Zero behavior change for non-temporal queries. (b) mine_delta.py emits one per-session "episode" memory after substantive Stop events (tagged type:episodic, Haiku-generated 100-word narrative). Benchmark is unchanged at 74% in this release — the gains are forward-looking and accrue as the vault accumulates v2.2.0-shaped episodes. Backward-compat is 100%.
What's new in v2.1.0 (event-triggered mining)
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.
Stop hook (
hooks/stop-mine.sh) spawnsmine_deltaas a detached background process on everyStopevent. 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 Haikuextract_from_textfunction, and marks the session in~/.memem/.mined_sessions.Stale-session sweep — the
SessionStarthook now scans for JSONL files older than 10 min that aren't in.mined_sessionsand spawns up to 3 parallelmine_deltaprocesses. Catches sessions where Stop never fired (Claude crash,kill -9, network drop).Per-session flock —
mine_deltaacquires anfcntl.flockon a lock file per session so concurrent Stop events on the same session don't race.Adaptive empty-streak backoff — if the last 3 consecutive Stop events yielded zero memories, the next 5 Haiku calls are skipped. Resets on any non-empty result.
Token cost is ~5–20× higher per session vs v2.0.0's session-end batching (many small Haiku calls instead of one big one), but mining feels real-time — memories appear seconds after each conversation turn.
Extraction quality unchanged — the same Haiku prompt and
extract_from_textfunction frommining.pyare used. The 18-query benchmark still passes at ≥70% precision.
What's new in v2.0.0 ("less is more")
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.
NEW
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.Slice schema collapsed to 2 sections:
## Working(current state) +## Relevant(ranked list). The v1.13 schema (Anchors / Episodic / Skills / Cases / Working / Pending) is gone.active_memory_sliceMCP tool slimmed from 8 params to 2 (query,task_mode). Backward-incompatible.Deleted (~14,500 LOC): 15 memem modules (active_slice*, activation, candidate_generation, kind_classifier, slice_daemon, slice_client, slice_history, delta*, working_memory, boundaries, artifact_context, environment_context), 36 legacy test files, all v1.13 env-var flags (
MEMEM_USE_LLM_JUDGE,MEMEM_USE_EMBEDDINGS,MEMEM_RENDER_LEGACY,MEMEM_LLM_JUDGE_TIMEOUT,MEMEM_AUTO_SLICE_DAEMON— all no-op now).Preserved: all 14 MCP tools (same names + return shapes), all 7 CLI flags, mining pipeline, vault format, embedding model + cache.
Benchmark (18 queries × 6 categories): 74% precision (vs v1.13's 24% — 3× improvement) | 98ms warm latency (vs v1.13's 675ms — 6× faster) | 24/8 cross-scope hits (lexie/SSH/HFT queries that v1.13 returned 0 results for).
Daemon retired:
slice_daemonandMEMEM_AUTO_SLICE_DAEMONremoved. Retrieval is now in-process viamemem.retrieve; the hook spawns python directly per prompt. After upgrade runpkill -f slice_daemononce to clear any old process.Hook envelope now uses tempfile (avoids ARG_MAX on large prompts).
Embedding writes are atomic:
embeddings.npyvia tmpfile +os.replace,embedding_ids.jsonwritten first so readers never see torn-write or shape mismatch.
What's new in v1.9.4 (data correctness pass)
Two release pair (v1.9.3 + v1.9.4) targeting silent-corruption paths. All changes are no-ops on the happy path.
Atomic writes everywhere — shared
atomic_write_texthelper (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=0opts out per-call. Power-loss / NFS-jitter / SIGKILL no longer torn-writes vault data.WAL on every SQLite DB —
graph.dbandsearch.dbnow usejournal_mode=WAL+synchronous=NORMAL+busy_timeout=5000, matchingsession_state_db.pysince v1.6. Concurrent reads from the slice engine no longer race with miner writes. Newmemem --integrity-checkCLI command (also called from--doctor) runsPRAGMA integrity_checkon all three DBs.Strict frontmatter validation — files without
---frontmatter are no longer silently ingested withschema_version=0. NewMEMEM_FRONTMATTER_STRICTenv var:quarantine(default — move to~/.memem/quarantine/<hash>_<name>),skip(log + ignore), orraise.Writeback idempotency cache —
commit_deltashashes(scope_id, dry_run, auto_only, deltas, DELTA_WRITEBACK_VERSION)on entry; matching hits return cached result withdeduped: Truemarkers. Cache at~/.memem/writeback-idempotency.json. Dry-runs and partial-failure batches are not cached.force_writeback=Truebypasses the lookup. RMW guarded byfcntl.flock.Daemon-side subprocess-timeout accounting (v1.9.2) — fixed an infinite-loop where a huge JSONL session would re-queue forever because the daemon's SIGKILL preempted
mine_session's in-process timeout cap. Now the daemon itself incrementstimeout_failuresand permanently skips afterMEMEM_MAX_SESSION_TIMEOUTS(default 3).
What's new in v1.9 (smart injection gating)
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.
What's new in v1.1
Layered memory becomes real end-to-end. Every memory now lives in one of four layers (L0/L1/L2/L3) at save time, not just at mining time.
memory_saveaccepts an optionallayerparam (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.Slice as universal recall format.
memory_search,memory_get,memory_timeline,memory_recall, andcontext_assembleall return slice-formatted output via a singlerender_slice_markdowndispatcher.context_assemblecomposes viaactive_memory_slicerather than rolling its own briefing.
What's new in v1.0 (miner hardening)
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.
Related MCP server: acheron-mcp-server
When should I use memem?
Use memem if:
You use Claude Code daily and keep re-explaining your project to every new session
You want durable memory you can browse and edit as markdown
You like local-first tools with zero vendor lock-in
You already use Obsidian (memem plugs straight into your vault)
How is memem different from CLAUDE.md?
CLAUDE.md is a single hand-edited file per project. memem gives you:
Automatic extraction — no manual note-taking, the miner captures lessons from every completed session
Query-aware context — only the memories relevant to your current question get injected, not a static dump
Self-evolving — memories merge, update, and deprecate automatically as your project evolves
Cross-project — works across every Claude Code project you use, not scoped to one repo
Security scanning — every write is scanned for prompt injection and credential exfiltration
Browsable — Obsidian vault with graph view and backlinks for free
Architecture — slice-first runtime
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 threadactive_memory_slice(query)-> on-demand working-state slice
Context model (v2.8+) — three tiers injected at session start:
Tier | What | Budget |
Profiles |
| ≤~600 tokens |
Working rules |
| ≤~300 tokens |
Episode index | Up to 25 recent | ~25 entries |
Everything else is available on demand via memory_search / memory_get. Legacy memories with layer: frontmatter are readable; memory_save(layer=N) is still accepted but deprecated.
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 VaultThe 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)
Injection mode (v1.9+, default changed in v2.4.0) — controls auto-injection behavior:
MEMEM_INJECTION_MODE—tool(default since v2.4.0 — silence hook, LLM pulls via MCP tools),auto(pre-v2.4.0 behavior — inject on every prompt). To restore the old default:export MEMEM_INJECTION_MODE=auto. Note:hybridwas removed in v2.5.0 (was documented but never implemented; treated asauto).
Selective recall:
MEMEM_RECALL_MIN_ITEM_SCORE=0.0— per-item composite-score floor for recall results (0.0 = disabled).
Migration note (v2.4.0): if you previously relied on per-turn auto-injection, set export MEMEM_INJECTION_MODE=auto in your shell profile. The new tool default reduces token overhead ~85% but requires the LLM to pull memory via the MCP tools when it judges context is needed.
How do I install memem?
Copy-paste:
claude plugin marketplace add TT-Wang/memem
claude plugin install memem@memem-marketplaceIf you already added the marketplace once, future installs only need the second command.
Then:
restart Claude Code if it was already open
open any project
send your first normal message
memem will show a welcome/status message and offer the mining options
That's it. On first run, bootstrap.sh self-heals everything:
Verifies Python ≥ 3.11 — or installs it via
uv python install 3.11if your system Python is too oldInstalls
uvif missing (via the official Astral installer)Syncs deps into a plugin-local
.venv(hash-cached againstuv.lock)Creates and canary-tests
~/.memem/and~/obsidian-brain/Writes
~/.memem/.capabilities(used for degraded-mode decisions)Execs the real MCP server
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.
Recommended first-run choice
choose
/memem-mineif you only want memory from new sessions going forwardchoose
/memem-mine-historyif you want memem to process your old Claude Code sessions too
If you are unsure, start with /memem-mine. It is the safer and cheaper default.
What happens on my first Claude Code session?
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: in tool mode (default since v2.4.0), Claude pulls memory on demand via memory_search, memory_get, and active_memory_slice when context is needed. In auto mode (export MEMEM_INJECTION_MODE=auto), every user prompt goes through active_memory_slice automatically, building a structured working-state briefing from relevant memories, playbooks, transcripts, and graph neighbors.
30-Second Setup
claude plugin marketplace add TT-Wang/memem
claude plugin install memem@memem-marketplaceThen in Claude Code:
/mememAnd choose one:
/memem-mineor
/memem-mine-historyWhat does memem save?
It saves durable knowledge, not session logs:
Architecture decisions with rationale ("we use RS256 JWTs because HS256 can't be verified by third parties without sharing the secret")
Conventions ("tests go in
tests/notspec/", "commit messages use imperative mood", "never import frominternal/outside its package")Bug fixes you might forget ("
bcrypt.compareis async — mustawait", "timezone math must usedayjs.utc()or DST shifts the result by an hour")User preferences ("prefer single commits, not stacked PRs", "terse responses — no trailing summaries", "ask before running migrations in prod")
Known issues & workarounds ("
JWT_SECRETdefaults to'secret'if unset — tracked in #123", "pnpm install hangs on corporate VPN, use--network-timeout=600000")Environment & tooling facts ("project uses Poetry, not pip", "CI runs on Node 20 but local defaults to 22 — pin with
nvm use", "Redis must be running on :6380 not :6379")Project structure & invariants ("auth middleware requires Redis", "all DB writes go through
repo/layer, never raw SQL in handlers")Failure patterns & post-mortems ("mocking the DB hid a broken migration last quarter — integration tests must hit a real DB", "don't ship on Fridays after the 2025-11 rollback incident")
Third-party quirks ("Stripe webhooks retry for 3 days — idempotency key is mandatory", "OpenAI streaming drops the final token if client closes early")
Domain knowledge ("a 'merchant' in our schema is what the legal team calls a 'counterparty'", "revenue is recognized at ship time, not order time")
It does NOT save:
Raw session transcripts (those are searchable via
transcript_search, not stored as memories)Trivial or obvious facts
Session outcomes ("today I worked on X")
Where does memem store my memories?
Store | Path | Purpose |
Memories |
| Source of truth (human-readable markdown) |
Playbooks |
| Per-project curated briefings |
Search DB |
| SQLite FTS5 index (machine-fast lookup) |
Graph DB |
| Rebuildable typed/scored memory-edge index |
Telemetry |
| Access tracking (atomic writes) |
Event log |
| Append-only audit trail |
Capabilities |
| Degraded-mode flags written by bootstrap |
Bootstrap log |
| First-run diagnostics |
You can point memem elsewhere via MEMEM_DIR and MEMEM_OBSIDIAN_VAULT env vars.
What are the MCP tools Claude can call?
As of v2.9.0, memem exposes 6 MCP tools (reduced from 14; removed tools are available via CLI or Python library — see note below).
Tool | Signature | What it does |
|
| Store one atomic durable lesson. Security-scanned for prompt injection and credential exfil. Three-band dedup: ≥0.92 rejects as duplicate, 0.70–0.92 merges into existing memory, <0.70 saves new. |
|
| Compact-index search (~50 tok/result) via three-way RRF (cosine + BM25 + FTS5). Use first to narrow candidates; returns IDs + titles + 1-line snippets. |
|
| Full content fetch by IDs (~500 tok/result). Use after |
|
| Chronological thread via |
|
| Search raw Claude Code session JSONL logs via persistent FTS5 index at |
|
| Query-shaped working-state slice (~150 ms). Uses three-way RRF + MMR diversification (λ=0.7, top-20 → 8 results). Auto-derives |
Removed in v2.9.0 — 8 tools were removed from the MCP surface; their replacements:
Removed tool | Replacement |
| Use |
| CLI: |
| CLI: |
| CLI: |
| CLI: |
| CLI: |
| Python: |
| Python: |
How do I inspect slices or writeback manually?
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-llmSemantics:
default
sliceis read-side and non-mutating--writeback-previewruns the delta pipeline in dry-run mode--auto-commit-safecommits only deltas classified as auto-safe
What slash commands does memem add?
/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 sessions
What if the claude 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.
How do I diagnose problems?
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 DBHow does the mining pipeline work?
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)How does the recall pipeline work?
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 loadedArchitecture
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 library (memory_search/memory_get/memory_timeline;memory_recallstill available as a library function) — surgically rewritten in v2.0.0 with inline_render_recall_markdown(the legacyactive_slicerenderer is gone)playbook.py— per-project playbook grow + refineassembly.py—context_assemblenarrative briefing (used by CLI--assemble-context; removed from MCP surface in v2.9.0)capabilities.py— runtime feature detection for degraded modeserver.py— thin MCP entrypoint (FastMCP imported lazily;storage.pyserver-lifecycle helpers folded in v2.5.0)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, callsextract_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:
50% FTS relevance
15% recency (0.995^hours decay)
15% access history (usage reinforcement)
20% importance (1-5 scale from Haiku)
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
---Configuration
Env var | Default | Purpose |
|
| State directory (PID files, search DB, logs) |
|
| Vault location |
| (none) | Colon-separated extra session dirs to mine |
|
| (legacy) Settle-window seconds. In v2.1.0 both the Stop hook AND |
|
| Bootstrap skips |
Setup Obsidian (optional, recommended)
memem works without Obsidian — it just writes markdown. But Obsidian gives you graph view and backlinks for free:
Download: https://obsidian.md (free)
Open
~/obsidian-brainas a vaultMemories appear in
memem/memories/, playbooks inmemem/playbooks/Use Graph View to see how memories link via the
relatedfield
Requirements
Claude Code
Python ≥ 3.11
uv(auto-installed by bootstrap.sh on first run)claudeCLI on PATH (optional — required for Haiku-powered assembly; degraded mode works without it)
Development
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 checkSee CONTRIBUTING.md for the PR process and CHANGELOG.md for version history.
Works great with
forge — Structured planning, parallel execution, and deep validation for Claude Code. memem + forge is the recommended pairing: forge plans and executes multi-file changes, memem remembers what worked across runs. Forge's
memory_savepatterns land in memem's recall index, so next week's run starts with last week's lessons already loaded.
License
MIT
Available Tools
6 toolscontext_assembleA
Assemble a query-tailored context briefing from all available knowledge.
This is the highest-value Cortex tool. It gathers the relevant subset of memories, the project's playbook, and related session transcripts, then uses Claude Haiku to synthesise a focused markdown briefing for the given query. The result is a ready-to-read summary, NOT a raw memory dump — usually 300-800 tokens of distilled relevant knowledge.
Behaviour:
Read-only with respect to the Cortex memory store. Bumps access telemetry on memories it reads (same as
memory_recall).No authentication required by Cortex itself. The optional Haiku synthesis step shells out to the local
claudeCLI, which may use Claude Code credentials the user already has signed in — Cortex does not handle those credentials directly.Rate limits: depend on the
claudeCLI backend in the healthy path. In degraded mode (claude CLI missing), there are no rate limits at all — Cortex just returns raw materials.Data access scope: reads ~/obsidian-brain/cortex/memories/, ~/obsidian-brain/cortex/playbooks/.md, ~/.cortex/search.db, and ~/.claude/projects/ transcripts. If the
claudeCLI is invoked, the gathered materials (up to 50KB) are sent to Haiku via that subprocess — which in turn sends them to Anthropic's API under the user's existing Claude Code session. In degraded mode nothing leaves the machine.Latency: 3-15 seconds with Haiku; <500ms in degraded mode.
Not idempotent at the Haiku level: the same query can produce slightly different briefings across calls due to Haiku sampling. The underlying memory retrieval step IS deterministic.
Failure modes: returns "" on genuinely empty vaults. Never raises to the caller; Haiku failures silently fall back to returning the raw materials.
Use context_assemble when:
Starting a new session and you want the assistant loaded with context before the first real question (the auto-recall hook does this on
UserPromptSubmit, but you can also call it manually)Onboarding to a project mid-session — ask "what do I know about X?"
Before making a decision in an area where prior decisions exist
Do NOT use for:
Simple keyword lookups (use
memory_recall— faster, no LLM call)Listing memories (use
memory_list)Finding a specific past conversation (use
transcript_search)
Degraded mode: if the claude CLI is not available on the host,
this tool falls back to returning the raw materials (playbook +
ranked memories) without Haiku synthesis, so it always returns
SOMETHING useful.
Returns: A markdown briefing tailored to the query. Length is typically 300-800 tokens, with headers, bullet lists, and cross-references to memory IDs where relevant.
Example: context_assemble( query="help me fix the auth flow on staging", project="my-webapp", ) → returns a brief covering: the RS256 JWT decision, the known bcrypt.compare gotcha, a link to the staging-specific env var issue from last month, etc.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | The question or task you want context for. Typically this is the user's first message in a new session — the assembler will pull relevant memories and synthesise a focused briefing tailored to what they asked. | |
| project | No | Project scope for the assembly. "default" pulls from cross-project memories. A specific project name pulls that project's playbook + memories + session history. | default |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description fully discloses behavioral traits: read-only nature, telemetry bumps, authentication details, rate limits, data access scope, latency, idempotency, failure modes. This level of detail exceeds what annotations would typically provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with sections, bullet points, and an example. It is front-loaded with a clear one-sentence summary. Every sentence adds essential information, matching the tool's complexity without unnecessary verbosity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers all essential aspects: purpose, usage guidelines, behavioral details, parameters with example, return value specification, failure modes, and degraded mode. An output schema exists, but the description still provides necessary context on return format and typical content length.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Input schema has 100% coverage with clear descriptions for both parameters. The description adds value by providing usage context, an example call, and clarifying the 'project' parameter's default behavior. However, the schema already does most of the work, so a 4 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'Assemble a query-tailored context briefing from all available knowledge.' It distinguishes from sibling tools by explaining it gathers memories, playbook, and transcripts, then synthesizes with Haiku. The verb 'assemble' and resource 'context briefing' are specific and unique.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit guidance on when to use (starting a session, onboarding, decision-making) and when not to use (simple keyword lookups → memory_recall, listing memories → memory_list, finding past conversation → transcript_search). Also mentions degraded mode behavior, ensuring the agent knows the tool always returns something useful.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_importA
Bulk-import memories from a file, directory, or chat export.
Use this to seed Cortex with existing notes, CLAUDE.md content,
documentation excerpts, or chat logs you want to make searchable.
Each imported item runs through the same security scan and deduplication
as memory_save, so clean imports even from messy sources.
Behaviour:
MUTATION. Writes one or more memory markdown files, updates FTS5 index, _index.md, and events.jsonl. Same atomic + fcntl-locked write path as
memory_save.No authentication required.
No rate limits, but latency scales with source size — importing a 100-item directory can take several seconds.
Data access scope: reads the supplied
source_pathfrom the local filesystem. Guarded against path traversal: the resolved path must be inside $HOME; anything outside is rejected. Nothing is sent over the network.Not idempotent: re-importing the same source triggers the dedup check, which rejects duplicates with a summary count.
Failure modes: invalid or non-existent source paths return a string error. Individual rejected items are counted in the summary and do not abort the whole import.
Use memory_import for:
Initial bootstrap from an existing
CLAUDE.mdor notes folderAbsorbing a team-wide decision log into a project scope
One-off batch captures from a conversation export
Do NOT use for:
Incremental per-conversation saves (use
memory_savefor single items)Mining Claude Code session logs (the background miner handles that automatically; no manual import needed)
Returns: A summary like "Imported 12 memories from 18 candidates (rejected 6 duplicates)". Errors are returned as human-readable messages.
Example: memory_import( source_path="/home/alice/notes/team-decisions.md", scope_id="my-webapp", )
| Name | Required | Description | Default |
|---|---|---|---|
| scope_id | No | Project scope to tag imported memories with. Defaults to "default" (cross-project). Use a specific project name to scope the import. | default |
| source_path | Yes | Absolute path to a file or directory containing content to import as memories. Supported formats: plain text files (.txt, .md) treated as single memories, directories recursively scanned, and structured exports (JSONL chat logs). Must be under $HOME for path-traversal safety. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Thoroughly describes mutation, atomic writes, security (path traversal guards), failure modes, dedup, idempotency, and latency. No annotations provided, so full burden is carried.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well-structured with sections, lists, and examples. Slightly verbose but each part justifies its length. Not overly terse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a mutation tool with 2 parameters and an output schema (mentioned), description covers behavior, usage, failure modes, return format, and example. Very complete given context signals.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema covers 100% of parameters with descriptions. Description adds context on source_path formats, scope_id default, and examples, going beyond schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states bulk import from file/directory/chat export. Distinguishes from sibling memory_save for single items and notes for incremental saves.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly lists when to use (initial bootstrap, absorbing decision logs, batch captures) and when not to use (incremental saves, session log mining). Names alternatives: memory_save and background miner.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_listA
List every memory in a scope, with counts and source breakdown.
Use this when you want to see the full inventory of what Cortex has stored — e.g. to audit which projects have the most memories, to check if a specific memory you wrote earlier is still present, or to find a memory whose exact title you remember but whose keywords are ambiguous.
Behaviour:
Read-only. No mutations at all, not even telemetry bumps.
No authentication required.
No rate limits. Latency scales linearly with memory count; typical sub-second on corpora up to ~10k.
Data access scope: reads ~/obsidian-brain/cortex/memories/ markdown files via filesystem glob. Nothing leaves the machine.
Idempotent and deterministic for a given filesystem state.
Failure modes: returns "No memories in scope: " for an empty scope. Never raises.
Use memory_list when:
You want to see everything, not a ranked subset
You need to audit the current memory inventory
You're about to run a cleanup/purge operation and want a preflight
You suspect
memory_recallis missing something and want to confirm
Do NOT use for:
Searching for specific content (use
memory_recall— faster, ranked)Assembling a context briefing (use
context_assemble)
Returns: A markdown listing with the total memory count, source type breakdown (mined/user/import), and one line per memory showing its short ID, title, and project. Memories are sorted newest-first by creation date.
Example output: 47 memories Sources: mined:32, user:12, import:3
- [63d6570e] JWT algorithm — RS256 only in prod | project:my-webapp
- [a8b12c44] Pytest fixtures must use tmp_path | project:default
- ...
| Name | Required | Description | Default |
|---|---|---|---|
| scope_id | No | Project scope to list. "default" lists every memory across all projects. A specific project name lists only memories tagged with that project. | default |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Provides extensive behavioral details: read-only, no auth, no rate limits, linear latency, filesystem access scope, idempotency, failure modes. No annotations provided, so description carries full burden and exceeds it.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well-structured with clear sections. Front-loaded with purpose and usage. Every sentence adds value. Appropriate length for the complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers all aspects: purpose, usage guidance, behavioral transparency, parameter semantics, output format with example. Complete for a simple tool with one optional parameter.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and description adds meaning: clarifies default scope lists all projects, specific project name filters. The extra context elevates beyond baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states the tool lists memories in a scope with counts and source breakdown. Differentiates from siblings like memory_recall and context_assemble. Describes specific use cases.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use (audit, check existence, preflight for cleanup) and when not to (searching, context briefing) with alternative tool names. Provides clear guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_recallA
Search Cortex memories for durable knowledge relevant to a query.
Use this when you need to recall prior decisions, conventions, bug fixes, user preferences, or lessons learned from past Claude Code sessions. It searches the full Obsidian-backed memory store via SQLite FTS5, ranks results with a multi-signal scorer (relevance + recency + usage + importance), and expands related-memory links.
Behaviour:
Read-only. Does not modify any memory, index, or sidecar file. Only side effect is a bump to the access-count telemetry sidecar (~/.cortex/telemetry.json), which influences future ranking.
No authentication required. Cortex is local-first; there are no credentials, tokens, or API keys.
No rate limits. Typical latency is under 100ms on corpora up to ~10k memories; pathological queries can take up to ~500ms.
Data access scope: reads from ~/obsidian-brain/cortex/memories/ and ~/.cortex/search.db. Nothing leaves the local machine.
Idempotent: calling twice with the same query returns the same results (modulo the access-count telemetry bump).
Failure modes: returns "No memories found for: " on empty result sets. Never raises to the caller; internal errors fall back to a slower file-scan path.
Use memory_recall when:
You need specific facts ("what auth library did we pick?")
You want to check if a topic has prior context before making a decision
You're debugging and want to find if this bug was fixed before
Do NOT use for:
Session-level what-I-did-today logs (use
transcript_searchinstead)On-demand query-tailored briefings (use
context_assembleinstead)Listing every memory (use
memory_listinstead)
Returns: Markdown-formatted memory entries, grouped under a "### Memories" header. Each entry has the memory title and body excerpt. If no matches are found, returns "No memories found for: ".
Example: memory_recall(query="jwt auth algorithm", limit=5) → returns the top 5 memories mentioning JWT auth, such as a memory documenting the decision to use RS256 in production.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of top-ranked memories to return. Results are ranked by 50% FTS relevance + 15% recency + 15% access history + 20% importance. Linked memories may be expanded beyond this limit. | |
| query | Yes | Natural-language query describing what you want to find. Can be a question ("how did we handle auth?"), a topic ("jwt rotation"), or a fragment of a decision you vaguely remember. Keyword match is fuzzy (FTS5 + synonym expansion) so approximate terms work. | |
| scope_id | No | Project scope to search within. Defaults to "default" which searches across all projects. Pass a specific project name to narrow the search to that project's memories only. | default |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Even without annotations, the description thoroughly covers behavior: read-only, no auth, no rate limits, idempotence, data scope, failure modes, and the minor side effect of access-count telemetry. It fully discloses all behavioral traits an agent needs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with clear sections and an example. It is fairly long but every sentence adds value. Minor redundancy could be trimmed, but overall it is efficient and front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's moderate complexity (3 parameters, output schema implied), the description covers purpose, usage, behavior, return value, failure mode, and an example. No gaps remain for an agent to infer.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, but the description adds significant value: explains the ranking formula for 'limit', fuzzy matching and synonym expansion for 'query', and project narrowing for 'scope_id'. This goes well beyond the schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool searches Cortex memories for relevant knowledge, with a specific verb 'Search' and a precise resource 'Cortex memories'. It differentiates from siblings by listing concrete use cases and explicitly naming alternative tools for different scenarios.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description includes explicit guidance on when to use (specific facts, prior context, debugging) and when not to use, with clear references to sibling tools 'transcript_search', 'context_assemble', and 'memory_list'. This helps the agent choose correctly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_saveA
Save a durable lesson, decision, or convention to persistent memory.
Use this to capture knowledge that should survive across Claude Code sessions: user preferences, architecture decisions with rationale, environment quirks, non-obvious bug fixes, or anything you'd otherwise have to re-explain in the next session.
Behaviour:
MUTATION. Writes a new markdown file under
/obsidian-brain/cortex/memories/, appends to the FTS5 index (/.cortex/search.db), updates ~/obsidian-brain/cortex/_index.md, and writes an append-only entry to ~/.cortex/events.jsonl. Writes are atomic (tmp + fsync + os.replace) and fcntl-locked.No authentication required. Local-first; no credentials.
No rate limits. Typical latency 50-200ms including the security scan, dedup check, and write sync.
Data access scope: writes stay entirely on the local filesystem. Nothing is sent over the network.
Not idempotent: calling twice with identical content triggers the dedup check and the second call returns a "Memory already exists" error instead of a duplicate write.
Failure modes: rejected inputs return a string error ("Memory already exists", "Memory rejected: "); they never raise to the caller.
Every save goes through:
Prompt-injection + credential-exfil security scan (rejects matches)
Fuzzy deduplication against existing memories (word+bigram+trigram overlap — rejects near-duplicates with clear reason)
Automatic related-memory linking (adds
relatedfrontmatter field)Write to Obsidian markdown file + FTS5 index + _index.md
Use memory_save for:
"We decided to use X because Y" (decision + rationale)
"User prefers small focused PRs, not big bundled ones" (preference)
"Database connection pool must be at least 20 for prod" (invariant)
"bcrypt.compare is async — always await" (gotcha)
Do NOT use for:
"Today I worked on X" (session logs — use transcript_search to find those)
Trivial facts easily re-discovered from reading code
Speculative or unverified claims
Duplicates of existing memories (the dedup check will reject them anyway)
Returns: A confirmation like "Memory saved: 63d6570e... 'JWT auth uses RS256'" on success, or an error message (starting with "Memory already exists" or "Memory rejected") on failure.
Example: memory_save( content="Use RS256 (not HS256) for JWT in production. HS256 " "requires sharing the signing secret across services " "which leaked via an env var export last quarter (#1247).", title="JWT algorithm — RS256 only in prod", tags="auth,jwt,security,postmortem", scope_id="my-webapp", )
| Name | Required | Description | Default |
|---|---|---|---|
| tags | No | Comma-separated tag list (e.g. "auth,jwt,security"). Tags are used for filtering in `memory_list` and contribute to recall scoring. Keep them lowercase and topical. | |
| title | No | Short descriptive title, <120 characters. If empty, the first 60 chars of content are used. A good title is a declarative summary ("Use RS256 JWTs in prod"), not a question. | |
| content | Yes | The full memory body, written as natural-language prose. Should be atomic (one idea per save): capture ONE decision, convention, lesson, or preference with enough context to be useful in a future session. Include the rationale, not just the outcome. Max ~2000 characters recommended. | |
| scope_id | No | Project scope for the memory. "default" for cross-project global memories (user preferences, general lessons). A specific project name for project-scoped memories (e.g. "cortex-plugin", "my-webapp"). | default |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description fully discloses mutation behavior, security scan, dedup process, error handling, local-only data flow, atomic writes, and typical latency. It covers all important behavioral traits an agent needs to anticipate outcomes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is fairly long but well-structured with sections (Behaviour, Use for, Do NOT use for, Returns, Example). Every sentence adds value, though a few details could be tighter. Front-loaded with purpose, which is good for scanning.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (4 parameters, multiple behavioral aspects, return values), the description covers everything an agent needs: input constraints, processing steps, output format, error messages, and a complete example. No gaps remain.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, but description adds meaning beyond the schema: explains tags are used for filtering and recall scoring, suggests title format and character limits, describes content as atomic prose with rationale, and clarifies scope_id semantics. This exceeds the baseline of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description begins with a clear verb+resource pair: 'Save a durable lesson, decision, or convention to persistent memory.' It distinguishes from siblings like 'transcript_search' and provides concrete examples, leaving no ambiguity about the tool's purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly lists when to use (decisions, preferences, invariants, gotchas) and when NOT to use (session logs, trivial facts, speculative claims, duplicates). It also names an alternative (transcript_search) and explains what happens with duplicates, giving the agent clear selection criteria.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
transcript_searchA
Search raw Claude Code session transcripts for past conversation excerpts.
This is the DIFFERENT from memory_recall — it searches the raw session
JSONL files under ~/.claude/projects/, not the mined memory corpus.
Use it when you need to find the actual back-and-forth of a prior
conversation, not the distilled lesson from it.
Behaviour:
Read-only. Does not modify transcripts, memories, or any index.
No authentication required.
No rate limits. Latency scales with transcript corpus size; typical 100-500ms across a year of daily sessions.
Data access scope: reads ~/.claude/projects/**/*.jsonl via direct filesystem access. Does NOT read ~/obsidian-brain/ (that's what
memory_recallandmemory_listare for). Nothing is sent over the network.Idempotent and deterministic for a given filesystem state.
Failure modes: returns "No matching sessions" on empty result sets. Sessions older than Claude Code's 30-day retention window are not searchable (they've been deleted).
Use transcript_search when:
You want to recall "what did I actually say three weeks ago about X"
A mined memory references a session and you want the full context
You want to find all sessions that touched a specific file or topic
You're verifying a memory's source_session or debugging extraction
Do NOT use for:
Looking up durable knowledge (use
memory_recall— mined, ranked, faster)Listing memories (use
memory_list)Query-tailored context briefings (use
context_assemble)
Returns: Markdown-formatted session excerpts with the session id, date, and matched text, or "No matching sessions" if nothing matches. Transcripts older than Claude Code's 30-day retention are not searchable.
Example: transcript_search(query="redis connection pool size", limit=3)
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of session excerpts to return. Each excerpt is a short quote with the session id and date. | |
| query | Yes | Search phrase or keyword list. Matches against the raw Claude Code session JSONL transcripts (not the mined memory corpus). Use this for session-level context recovery, not durable knowledge lookup. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, so description fully covers behavior: read-only, no auth required, no rate limits, data access scope (reads ~/.claude/projects/**), idempotent, failure modes, retention window. Highly transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well-structured with clear sections, but somewhat lengthy. Could be slightly more concise without losing clarity, but overall effective.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity and lack of annotations, description covers all necessary aspects: purpose, usage, behavior, parameters, return format, and failure modes. Output schema exists but description still adequately describes return format.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3. Description adds useful context beyond schema: explains that query matches against raw session files, and limit controls number of excerpts returned. Provides example usage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states it searches raw Claude Code session transcripts for conversation excerpts. Distinguishes from sibling tool memory_recall by specifying it searches raw JSONL files, not the mined memory corpus.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly lists when to use (e.g., recalling past conversations, verifying memory sources) and when not to use (e.g., durable knowledge lookup, listing memories), with specific alternative tool names.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
6 tool updates
v0.1.2- Changed
context_assemble4 fields changed- added
Input schema / properties / project / descriptionAdded value: +"Project scope for the assembly. \"default\" pulls from cross-project memories. A specific project name pulls that project's playbook + memories + session history." - added
Input schema / properties / query / descriptionAdded value: +"The question or task you want context for. Typically this is the user's first message in a new session — the assembler will pull relevant memories and synthesise a focused briefing tailored to what they asked." - added
Input schema / properties / query / maxLengthAdded value: +2000 - added
Input schema / properties / query / minLengthAdded value: +1
- Changed
memory_import3 fields changed- added
Input schema / properties / scope_id / descriptionAdded value: +"Project scope to tag imported memories with. Defaults to \"default\" (cross-project). Use a specific project name to scope the import." - added
Input schema / properties / source_path / descriptionAdded value: +"Absolute path to a file or directory containing content to import as memories. Supported formats: plain text files (.txt, .md) treated as single memories, directories recursively scanned, and structured exports (JSONL chat logs). Must be under $HOME for path-traversal safety." - added
Input schema / properties / source_path / minLengthAdded value: +1
- Changed
memory_list1 field changed- added
Input schema / properties / scope_id / descriptionAdded value: +"Project scope to list. \"default\" lists every memory across all projects. A specific project name lists only memories tagged with that project."
- Changed
memory_recall7 fields changed- added
Input schema / properties / limit / descriptionAdded value: +"Maximum number of top-ranked memories to return. Results are ranked by 50% FTS relevance + 15% recency + 15% access history + 20% importance. Linked memories may be expanded beyond this limit." - added
Input schema / properties / limit / maximumAdded value: +50 - added
Input schema / properties / limit / minimumAdded value: +1 - added
Input schema / properties / query / descriptionAdded value: +"Natural-language query describing what you want to find. Can be a question (\"how did we handle auth?\"), a topic (\"jwt rotation\"), or a fragment of a decision you vaguely remember. Keyword match is fuzzy (FTS5 + synonym expansion) so approximate terms work." - added
Input schema / properties / query / maxLengthAdded value: +500 - added
Input schema / properties / query / minLengthAdded value: +1 - added
Input schema / properties / scope_id / descriptionAdded value: +"Project scope to search within. Defaults to \"default\" which searches across all projects. Pass a specific project name to narrow the search to that project's memories only."
- Changed
memory_save7 fields changed- added
Input schema / properties / content / descriptionAdded value: +"The full memory body, written as natural-language prose. Should be atomic (one idea per save): capture ONE decision, convention, lesson, or preference with enough context to be useful in a future session. Include the rationale, not just the outcome. Max ~2000 characters recommended." - added
Input schema / properties / content / maxLengthAdded value: +5000 - added
Input schema / properties / content / minLengthAdded value: +10 - added
Input schema / properties / scope_id / descriptionAdded value: +"Project scope for the memory. \"default\" for cross-project global memories (user preferences, general lessons). A specific project name for project-scoped memories (e.g. \"cortex-plugin\", \"my-webapp\")." - added
Input schema / properties / tags / descriptionAdded value: +"Comma-separated tag list (e.g. \"auth,jwt,security\"). Tags are used for filtering in `memory_list` and contribute to recall scoring. Keep them lowercase and topical." - added
Input schema / properties / title / descriptionAdded value: +"Short descriptive title, <120 characters. If empty, the first 60 chars of content are used. A good title is a declarative summary (\"Use RS256 JWTs in prod\"), not a question." - added
Input schema / properties / title / maxLengthAdded value: +120
- Changed
transcript_search6 fields changed- added
Input schema / properties / limit / descriptionAdded value: +"Maximum number of session excerpts to return. Each excerpt is a short quote with the session id and date." - added
Input schema / properties / limit / maximumAdded value: +20 - added
Input schema / properties / limit / minimumAdded value: +1 - added
Input schema / properties / query / descriptionAdded value: +"Search phrase or keyword list. Matches against the raw Claude Code session JSONL transcripts (not the mined memory corpus). Use this for session-level context recovery, not durable knowledge lookup." - added
Input schema / properties / query / maxLengthAdded value: +500 - added
Input schema / properties / query / minLengthAdded value: +1
6 tool updates
v0.1.0- First observed
context_assemble - First observed
memory_import - First observed
memory_list - First observed
memory_recall - First observed
memory_save - First observed
transcript_search
TDQS
Each tool has a distinct purpose: context_assemble synthesizes briefings, memory_import bulk-imports, memory_list lists, memory_recall searches, memory_save saves, and transcript_search searches transcripts. They are clearly differentiated with no overlap.
All tool names follow a consistent pattern: [domain]_[action] using snake_case (e.g., memory_save, memory_recall, context_assemble). The naming is predictable and uniform.
With 6 tools, the server covers core functionalities (save, search, list, import, context assembly, transcript search) without being excessive or minimal. The scope is well-balanced.
The tool set covers creation, search, listing, and import, but lacks explicit update or delete operations for memories. While this may be intentional, it's a minor gap that could cause agent limitations.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Persistent memory for Claude Code and Cursor. Stop re-explaining your project every session.
Persistent context for Claude. Your AI always knows your projects and next actions across sessions.
Persistent cross-session memory shared by Codex, Claude Code, ChatGPT, and other AI agents.
Persistent, governed institutional memory for Claude Code — specs, decisions, learnings.
Related MCP Servers
- AlicenseBqualityAmaintenanceProvides a persistent "second brain" for Claude featuring zero-latency hot caching, semantic cold storage, and automatic pattern mining from activity logs. It enables users to store, search, and automatically extract project facts and code patterns for enhanced contextual recall.567MIT
- AlicenseAqualityDmaintenanceCross-surface persistent memory for Claude. Bridges context between Claude Chat, Code, and Cowork via local SQLite with full-text search.6216MIT
- AlicenseAqualityFmaintenancePersistent memory and automatic git snapshots for Claude Code, capturing decisions, patterns, and architecture across sessions.107698MIT
- AlicenseNot gradedqualityDmaintenancePersistent memory for Claude Code — a self-evolving knowledge layer that survives across sessions, grows from every conversation, and surfaces relevant context automatically.14MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/TT-Wang/memem'
If you have feedback or need assistance with the MCP directory API, please join our Discord server