claude-mem-lite
claude-mem-lite is a persistent memory (also called long-term memory or cross-session context) system for Claude Code — Anthropic's CLI coding agent. It runs as an MCP server plus a set of Claude Code hooks, automatically capturing coding observations, decisions, and bug fixes during sessions, then providing full-text search with query expansion to recall them later.
Compared to general-purpose LLM memory frameworks like mem0 or the MCP reference memory server, claude-mem-lite is purpose-built for Claude Code's hook lifecycle: episode batching cuts LLM calls 7–10× vs the original claude-mem (an estimated ~600× lower total cost — see the cost model below; this is an architecture estimate, not a measured benchmark), while the FTS5 retriever benchmarks at 0.90 Recall@10 / 0.85 Precision@10
(see Search Quality for the reproduction command).
中文简介:claude-mem-lite 是 Claude Code 的轻量级持久化记忆 / 长期记忆 / 跨会话上下文插件,基于 MCP 协议 + 钩子机制,自动捕获编码会话中的决策、修复和上下文,并通过 FTS5 全文检索召回。详见 中文 README。
Zero external services. Single SQLite database. Minimal overhead.
Why claude-mem-lite?
A ground-up redesign of claude-mem, replacing its heavyweight architecture with a smarter, leaner approach.
Architecture comparison
Token & cost efficiency
For a typical 50-tool-call session (illustrative cost model — the ratios below are architecture estimates derived from batch size, token counts, and model pricing, not a measured end-to-end benchmark):
Quality comparison
Design philosophy
The original sends everything to the LLM and hopes it filters well. claude-mem-lite filters first with code, then sends only what matters to a smaller model. This is not a downgrade; it's a smarter architecture that produces equivalent search quality at a fraction of the cost.
Comparison: memory systems for AI coding agents
How claude-mem-lite differs from the major neighbors in the LLM-memory space (verified May 2026):
When to pick which: pick mem0 if you need a memory layer for a non-Claude-Code app (your own agent, multiple LLM providers). Pick the MCP reference memory server if you specifically want a knowledge-graph data model and don't mind invoking memory tools by hand. Pick claude-mem-lite if you want zero-touch automatic capture purpose-built for Claude Code's hook lifecycle, with code-domain retrieval and no external services.
Features
- Automatic capture -- Hooks into the Claude Code lifecycle (SessionStart, PreCompact, PreToolUse, PostToolUse, PostToolUseFailure, Stop, UserPromptSubmit — the seven events in
hooks/hooks.json) to record observations without manual effort - Lexical search with query expansion -- FTS5 BM25 scoring, an AND->OR rescue pass, pseudo-relevance feedback and concept co-occurrence. A TF-IDF vector arm shipped alongside it until it was measured net-negative and removed;
--deepstill fuses multiple LLM-rewritten queries with Reciprocal Rank Fusion - Timeline browsing -- Navigate observations chronologically with anchor-based context windows
- Episode batching -- Groups related file operations into coherent episodes before LLM encoding
- Error-triggered recall -- Automatically searches memory when Bash errors occur, surfacing relevant past fixes
- Proactive file history -- When editing a file, automatically shows relevant past observations for that file
- Session summaries -- written at every Stop (the assistant's final report when it has Done / Not done sections, else — when the final reply has no report section at all — its first 120 characters, else the first prompt and recent observation titles), then upgraded by a background model summary when the session has observations
- Project-scoped context -- Injects recent memory into
CLAUDE.mdand session startup for immediate context - Observation types -- Categorized as
decision,bugfix,feature,refactor,discovery, orchange - Importance grading -- LLM assigns 1-3 importance levels (routine / notable / critical) to each observation
- Observation relations -- Bidirectional links between related observations based on file overlap
- User prompt capture -- Records user prompts via UserPromptSubmit hook for intent tracking
- Read file tracking -- Tracks files read during sessions for richer episode context
- Degraded fallback when the LLM fails -- An episode that fixed an error is saved with inferred metadata (as an activity event) instead of being discarded, when a command that exited 0 printed the error (
npm test 2>&1 | tail): a command Claude Code reports as failed does not enter the episode, so a fix that follows one counts as edit-only. An edit-only episode, config and schema changes included, is summarised from raw tool output, which the noise rules drop in most cases (Modified a.js, b.jswith no lesson; only a short single-file summary survives) — as they do when the model answers without a lesson — so a failing provider loses those episodes unlessCLAUDE_MEM_KEEP_LOW_SIGNAL=1is set;claude-mem-lite doctorwarns when theclaudeCLI it would call does not resolve - Two-tier dedup -- Jaccard similarity (5-minute window) + MinHash signatures (7-day cross-session window) prevent duplicates
- Synonym expansion -- Abbreviations like
K8s,DB,authautomatically expand to full forms in FTS5 search (100+ pairs including CJK↔EN cross-language mappings) - CJK synonym extraction -- Unsegmented Chinese text is scanned for known vocabulary words (数据库→database, 搜索→search, etc.) enabling cross-language memory recall
- Stop-word filtering -- English stop words filtered from FTS queries, preventing false negatives from noise terms like "how", "the", "does"
- Pseudo-relevance feedback (PRF) -- Top results seed expansion queries for broader recall
- Concept co-occurrence -- Shared concepts across observations expand search to related topics
- Context-aware re-ranking -- Active file overlap boosts relevance (exact match + directory-level half-weight)
- Superseded detection -- Marks older observations as outdated when newer ones cover the same files with higher importance
- Adaptive time windows -- Session startup recall uses velocity-based time windows (high/medium/low activity tiers)
- Token-budgeted context -- Greedy knapsack algorithm selects session-start context within a 2,000-token budget, prioritizing by recency and importance
- Observation compression -- Old low-value observations can be compressed into weekly summaries to reduce noise
- Secret scrubbing -- Automatic redaction of API keys, tokens, PEM blocks, connection strings, and 15+ credential patterns
- Atomic writes -- All file writes (episodes, CLAUDE.md) use write-to-tmp + rename to prevent corruption on crash
- Robust locking -- PID-aware lock files with automatic stale/orphan cleanup (>30s timeout or dead PID)
- Stale session cleanup -- Sessions active for >24h are automatically marked as abandoned on next start
- Domain synonym expansion -- Search queries expand to domain synonyms (e.g., "fix" → debug, bugfix, troubleshoot, diagnose, repair)
- Multi-provider LLM mode -- Provider priority
ANTHROPIC_API_KEY(direct Anthropic API) →OPENROUTER_API_KEY(OpenRouter, OpenAI-compatible — point it at any model viaOPENROUTER_MODEL) →claude -pCLI fallback when no key is set - Lesson-learned indexing --
lesson_learnedfield indexed in FTS5 with weight 8, making past debugging insights directly searchable - Cross-source normalization --
mem_searchnormalizes scores across observations, sessions, and prompts before merging, preventing any source from dominating results - Exponential recency decay -- Type-differentiated half-lives (decisions: 90d, discoveries: 60d, bugfixes: 14d, changes: 7d) consistently applied in all ranking paths
- Prompt-time memory injection -- UserPromptSubmit hook automatically searches and injects relevant past observations with recency and importance weighting
- Dual injection dedup --
user-prompt-search.jsandhandleUserPromptcoordinate via temp file to prevent duplicate memory injection - Plugin cache hook self-heal -- Claude Code runtime reads plugin hooks from
~/.claude/plugins/cache/<mp>/<plugin>/<ver>/hooks/hooks.json, not from the marketplace source. Wheninstall.mjs-managedsettings.jsonhooks coexist with a stale cachehooks.json(e.g. from a previous marketplace install or a plugin auto-update), the runtime registers hooks twice → every session start / user prompt fires twice.install.mjsandhook-update.mjsnow clear cachehooks.jsonin every version dir, andhook.mjs session-startself-heals on every session (gated byhasInstallManagedHooksso plugin-only users are not affected).install.mjs statusreports cache pollution state (since v2.31.1/2.31.2). - Result-dedup cooldown -- User-prompt memory injection uses result-overlap detection (>80% ID overlap → skip) instead of time-based cooldown, allowing topic switches within seconds while preventing redundant injections
- OR query fallback -- When AND-joined FTS5 queries return zero results, automatically relaxes to OR-joined queries for broader recall (applied in both user-prompt-search and hook-memory paths)
- Configurable LLM model -- Switch between Haiku (fast/cheap) and Sonnet (deeper analysis) via
CLAUDE_MEM_MODELenv var - DB auto-recovery -- Detects and cleans corrupted WAL/SHM files on startup; periodic WAL checkpoints prevent unbounded growth
- Schema auto-migration -- Idempotent
ALTER TABLEmigrations run on every startup, safely adding new columns and indexes without data loss - LLM concurrency control -- File-based semaphore limits background workers to 2 concurrent LLM calls, preventing resource contention
- stdin overflow protection -- Hook input truncated at 256KB with regex-based action salvage for oversized tool outputs
- Cross-session handoff -- Captures session state on
/exitand/clear, then injects context when the next session detects continuation intent Changed in v6.10.0: the injected block went from two sections to six. Its observation queries were keyed on the hook-minted session id while everymem_savewrites amanual-<project>id, socompletedandkey_decisionscould not reach a saved lesson at all — 17 of 17 stored rows held 0 bytes in both. The block now also carries## Tree state(branch, short sha, uncommitted count) and## Next steps, read from the project's newesttasks/<slug>-paused.mdwhen it is under a week old. Four additive nullable columns land onsession_handoffs; the schema version deliberately does not move, so an older build still opens the database (measured: the v6.9.1 tree read and wrote a database this release had migrated). Revert by pinning[email protected]— no data-directory work. Original behaviour and the measurements behind it via explicit keywords or FTS5 term overlap. The/clearand/compactarm fires since v5.4.0 (R10-P1-1); before that it had never once written a row —session_handoffson the maintainer's install held 4exitrows and 0clearrows. Two host facts settled it, both measured rather than assumed. (1)Stopruns at the end of every assistant turn, not once per session, and it deleted the session file that SessionStart reads to learn which session just ended — so the branch was unreachable, and mem sessions were minted per turn (58 prompts over 16 host sessions produced 56 mem sessions and 56 summary rows, 2026-09-07). (2) Claude Code rotates its session id across/clear: of 21 real transcripts, 12 carry a/clearcommand record, and in 12/12 that record's timestamp precedes its own file's first record by ~0.1s — the command is issued in the old session and replayed into a new file under a new id. SoStopno longer deletes the file, SessionStart asks the host'ssource(startup/clear/compact/resume) instead of guessing from the file, and the handoff's prompt lookup falls back to the unscoped set when the new session's id matches none. Revert path:CLAUDE_MEM_LEGACY_STOP_UNLINK=1 - Git-SHA continuation anchor (v2.31.0) -- Handoff rows include
git_sha_at_handoff; any handoff matching the currentHEADcounts as continuation regardless of TTL. Code state is a stronger continuation signal than wall-clock time - Startup dashboard (v2.31.0) -- SessionStart hook aggregates
git status+~/.claude/tasks/*.json+~/.claude/plans/*.md+ most-recent exit handoff + recent event count into a single structured block injected viahookSpecificOutput.additionalContext - Activity namespace (v2.31.0) -- Dedicated
eventstable + FTS5 for non-memdir types (bugfix,lesson,bug,discovery,refactor,feature,observation,decision) that don't compete withWHAT_NOT_TO_SAVEsemantics on the observations table. CLI:claude-mem-lite activity save|search|recent|show.hook-llmroutes non-memdir summary types throughpersistHaikuSummaryso upgrades from observations→events are atomic. (v3.39: the/lessonand/claude-mem-lite:bugslash commands were redirected from this events table to searchable observations —mem_searchnever read the events table, so explicit saves were unfindable; the events table remains the auto-capture activity log.) - In-place observation updates --
mem_updatetool modifies existing observations atomically (field update + FTS text rebuild in one transaction), preserving original IDs and references - Bulk export --
mem_exporttool exports observations as JSON or JSONL, with project/type/date filtering and 1000-row pagination cap with batch guidance - FTS integrity management --
mem_fts_checktool verifies FTS5 index health or rebuilds indexes on demand, useful after database recovery or when search results seem wrong - Atomic multi-table writes --
saveObservationwraps the observations + observation_files INSERTs in a singledb.transaction(), preventing orphaned rows on crash - Modular NLP pipeline -- Synonym maps, stop words, scoring constants, and query building extracted into focused modules (
synonyms.mjs,stop-words.mjs,scoring-sql.mjs,nlp.mjs) for independent testing and maintenance - Surface-form PRF expansion --
observations_ftsis built on FTS5's defaultunicode61tokenizer, so the index is not stemmed: a query term matches the word forms actually stored, andcrashdoes not match a row that only containscrashes. Pseudo-relevance feedback therefore uses the Porter stemmer only to bucket morphological variants when judging which candidate terms are discriminative, and emits the most frequent surface form of each — emitting a bare stem (cach) would match nothing and kill expansion recall
Platform Support
From v5.1.0 through v6.1.0, package.json declared os: ["darwin", "linux"]. That is an npm install
gate, not a runtime check: on Windows it made npm install exit EBADPLATFORM, which the
plugin launcher runs on the first MCP start after every plugin update — so the server never
came up and /mcp reported CONNECTION_CLOSED. win32 is now in the list. A platform that
is still outside it gets a message naming both sides of the mismatch instead of a guess.
Requirements
- Node.js >= 22
- Claude Code CLI installed and configured (
claudecommand available) - SQLite3 support (provided by
better-sqlite313, which ships prebuilt binaries for 8 platforms — no compiler needed on any of them; a platform it has no prebuild for falls back to building from source) - Platform: Linux or macOS; Windows installs and runs but is not CI-covered and needs Git Bash or WSL for four hooks (see Platform Support)
Installation
Method 1: Plugin Marketplace (recommended)
/plugin marketplace add sdsrss/claude-mem-lite
/plugin install claude-mem-lite
Plugin mode manages its own hooks/runtime. On session start it only checks and reports new claude-mem-lite versions; it does not self-overwrite plugin files in place. Update plugin-mode installs through Claude's plugin workflow.
The plugin install is complete on its own — hooks, MCP tools, and the bundled slash commands (
/mem,/lesson,/claude-mem-lite:bug,/adopt) all run from the plugin with no second step. The slash commands invoke the bundled CLI by an absolute path resolved from the plugin directory (node "${CLAUDE_PLUGIN_ROOT}/cli.mjs" <cmd>), so they work without anything on yourPATH. A globalclaude-mem-liteshell command (for running queries yourself in a terminal) is optional —npm i -g claude-mem-lite— and is a separate npm install: the plugin's auto-update does not refresh it, so re-runnpm i -g claude-mem-lite@latestif you want that shell command kept in sync. You do not need it for the plugin to be fully functional.
Auto-adopt no longer adds its block to your project's
CLAUDE.md(next release after 6.19.4). On every SessionStart the plugin delivers its steering text — the trigger table that boosts Claude's proactive use ofmem_recall/mem_save. In a git repository that does not already carry the block it keeps a managed block inCLAUDE.local.mdat the repository root (Claude Code loads that file likeCLAUDE.md, also in subagents that load project instructions; the built-in Explore and Plan agents load none) and adds the file to the repository's.git/info/excludeunless your ignore rules already cover it, so git does not list or commit it; you get a one-time notice the first time it is written. Claude Code reads instruction files before the plugin's startup hook runs, so the session that creates the file gets the text added to its context instead, once (its subagents do not see it). Where Claude Code reads anAGENTS.mdas the project's instructions, it stops reading it once aCLAUDE.mdorCLAUDE.local.mdexists in the session's directory or above, so there nothing is written and the text is injected (subagents do not see it), with a one-time notice that names thatAGENTS.md. WithCLAUDE_MEM_RULES_STEERING=1the block goes to.claude/rules/claude-mem-lite.mdat the repository root instead — excluded from git the same way, loaded by Claude Code as project instructions, also in subagents that load project instructions, and it leavesAGENTS.mdloading — with its own one-time notice; it is not the default because in a pre-registered A/B it did not beat injection by the bar set in advance (docs/audits/20261006-d212-ab.md). Outside git, in a repository rooted at$HOME, whereCLAUDE.local.mdis tracked or a symbolic link, or where the repository root is an npm package thatnpm publishwould ship it with (no"private": true, nofileslist leaving it out, and without one no.npmignorenaming it), nothing is written and the text is injected into each session's context, with a one-time notice suggesting/adopt(not at$HOME). With the variable set, the same holds where the rules file cannot be written (a symbolic link on its path, a tracked or foreign file of that name, an npm package that would ship it). The notice beside anAGENTS.mdnames it, why there is no file, and the ways to a file that keep it loading, each only where it works: Project instructions =claude-md-and-agents-mdin/config, after whichCLAUDE.local.mdis written (not whereCLAUDE.local.mdis refused too: a package root, or a file you removed), and/adopt, whoseCLAUDE.mdimports theAGENTS.mdbeside it (not offered when anAGENTS.mdabove that directory or in a subdirectory would stop loading). The detail file the block points at lives in the plugin's data directory, named with a~/path..git/info/excludeis read by git only: docker build contexts, archives and other packagers can pickCLAUDE.local.mdor the rules file up (a publishable npm package root does not keepCLAUDE.local.md: a block written before is taken out at the next session start; in a project whoseAGENTS.mdit would switch off, the block comes out and is injected, or, withCLAUDE_MEM_RULES_STEERING=1, moves to the rules file where that can be written). Earlier versions added the block to your project's own<cwd>/CLAUDE.md(a file that is normally committed) plus a<cwd>/.claude/plugin_claude_mem_lite.mddetail file, unasked, in every project you opened; those projects keep the block, and the first session of this version refreshes it because the text changed — to move one to the local file, runclaude-mem-lite unadoptthere and commit the removal. Why not inject everywhere: in our sandbox evaluation (one project, Claude Opus 5.5) the agent made 1.5 proactive memory saves per 8-session run with injected steering against 5.25 with the block inCLAUDE.local.mdorCLAUDE.md, and subagents never saw injected text. It applies regardless of install path (npm, npx,/plugin, manual); silence these notices withMEM_NO_ADOPT_HINT=1.Want the block in
CLAUDE.mdinstead (for example to share it with your team)? Runclaude-mem-lite adopt: it writes the slug-scoped managed block into<cwd>/CLAUDE.mdplus the detail file, preserving everything outside the block, and removes the auto-written local copy (CLAUDE.local.mdor the rules file). Claude Code stops readingAGENTS.mdonce aCLAUDE.mdexists, so aCLAUDE.mdthat adopt creates next to anAGENTS.md(or.claude/AGENTS.md) starts by importing it (@AGENTS.md, behind a marker comment Claude Code does not load), and that file keeps loading; anAGENTS.mdabove the directory or tracked below it cannot be imported that way and is named in adopt's output, with the setting that keeps it (Project instructions =claude-md-and-agents-md). Nothing is imported where aCLAUDE.mdof yours already exists, sinceAGENTS.mdwas not being read there.unadoptdeletes aCLAUDE.mdthat holds nothing but those lines and the block. A project that carries the block is kept in sync on every SessionStart — refreshed when the shipped template changes — and gets no local or injected copy on top of it, also in sessions started from a subdirectory. ACLAUDE.mdholding nothing but the block (what an earlier version's auto-adopt wrote) gets the sameAGENTS.mdimport at the next session started in its directory, with a one-time notice. Seen from a session started in a subdirectory, the import points outside the working directory, so Claude Code asks once to approve it. A local block (CLAUDE.local.mdor the rules file) the plugin created and you deleted (orclaude-mem-lite unadoptremoved) is not written back — the text is injected instead — untilclaude-mem-lite adopt --enable(beside anAGENTS.mdthatCLAUDE.local.mdwould switch off, a file comes back only withCLAUDE_MEM_RULES_STEERING=1too). Turn steering off per project withclaude-mem-lite adopt --disable(it also removes the local block; aCLAUDE.mdblock stays untilunadopt;--enablere-arms) or globally withexport MEM_NO_AUTO_ADOPT=1(blocks already written stay and keep loading untilunadopt); freeze the blocks against template refreshes withCLAUDE_MEM_NO_TEMPLATE_REFRESH=1.claude-mem-lite unadoptremoves theCLAUDE.mdblock, the detail file, and the local block (CLAUDE.local.mdor.claude/rules/claude-mem-lite.md) with its exclude entry.
Method 2: npx (one-liner)
npx github:sdsrss/claude-mem-lite
Source files are automatically copied to ~/.claude-mem-lite/ for persistence.
Note:
npx github:…installs from the repo's default branch (HEAD), which can be ahead of the latest published release. For the stable released version use the npm package (npx claude-mem-lite), or pin a release tag:npx github:sdsrss/claude-mem-lite#vX.Y.Z.
Method 3: git clone
git clone https://github.com/sdsrss/claude-mem-lite.git
cd claude-mem-lite
node install.mjs install
install copies the source files to ~/.claude-mem-lite/, as the npx method does. Update with node install.mjs update, which installs the latest signed release. git pull && node install.mjs install installs the checked-out branch instead: it can be ahead of the latest release, and no signature is checked.
What happens during installation
- Install dependencies --
npm install --omit=dev(compiles nativebetter-sqlite3) - Register MCP server --
mem-liteserver with 19 tool definitions (10 core + 9 hidden-but-callable;mem_search_feedbackis registered only when search telemetry is enabled). The pre-v2.78 generic server namememis renamed tomem-litefor namespace hygiene; the tool names themselves (mem_search,mem_recall, ...) are unchanged. - Configure hooks -- all seven lifecycle events:
SessionStart,PreCompact,PreToolUse,PostToolUse,PostToolUseFailure,Stop,UserPromptSubmit - Create data directory --
~/.claude-mem-lite/(hidden) for database, runtime, and managed resource files - Auto-migrate --
~/.claude-mem/claude-mem.db(original claude-mem) is moved into~/.claude-mem-lite/as a.legacy-backup-<time>file (the plugin, and install across filesystems, copy it instead) and a fresh database is created. A database left in~/claude-mem-lite/(the unhidden directory of versions before the first release, 2.0.0) is moved into the data directory at the first open, when the data directory holds no database, log, snapshot or kept copy of a claude-mem-lite store yet; nothing else in either directory is moved or removed - Initialize database -- SQLite with WAL mode, FTS5 indexes created on first server start
Restart Claude Code after installation to activate.
Migration
All installation methods auto-detect and migrate from previous versions:
From claude-mem (original ~/.claude-mem/):
claude-mem.dband its-wal/-shmare moved into~/.claude-mem-lite/asclaude-mem-lite.db.legacy-backup-<time>,claude-mem-lite.db.legacy-backup-<time>-waland-shm, when~/.claude-mem-lite/claude-mem-lite.dbdoes not exist yet. With the data directory on another filesystem they are copied instead,~/.claude-mem/stays as it is, and a backup already there is not copied again- The plugin install copies
claude-mem.dband its-walinto the data directory under those names and leaves~/.claude-mem/as it is; it does so while the data directory holds neither a database nor such a backup - A backup that
installmade before v6.30.8 names its logclaude-mem-lite.db-wal.legacy-backup-<time>, and SQLite does not read that log with the backup; rename it toclaude-mem-lite.db.legacy-backup-<time>-walbefore opening the backup - A fresh database is created: the original claude-mem schema has no upgrade path
From the unhidden directory of versions before the first release, 2.0.0 (~/claude-mem-lite/):
- Its database (
claude-mem-lite.dborclaude-mem.db, with its-waland-shm) is moved into the data directory at the first open, when the data directory holds no database, log, snapshot or kept copy of a claude-mem-lite store yet. Nothing else is moved, and nothing in either directory is removed - The move is a rename: with
CLAUDE_MEM_DIRon another filesystem it fails, and so does every open, until the database is moved there by hand together with its-waland-shm
In-place rename:
- Existing
claude-mem.dbin~/.claude-mem-lite/is automatically renamed toclaude-mem-lite.db
Remove old directories manually once nothing in them is needed. A database still in ~/claude-mem-lite/ has not moved (see above), and the command below deletes it:
rm -rf ~/.claude-mem/ # original claude-mem
rm -rf ~/claude-mem-lite/ # unhidden data dir from before the first release (not a git clone of this repository)
Directory Structure
~/.claude-mem-lite/
claude-mem-lite.db # SQLite database — memory (WAL mode)
runtime/
session-<project> # Active session state
ep-<project>.json # Episode buffer
ep-flush-*.json # Flushed episodes awaiting processing
reads-<project>.txt # Read file paths (collected on flush)
managed/
repos/ # Shallow-cloned source repos
Upgrading to 6.29.0
Chinese text in lessons, facts, concepts and aliases becomes searchable, and the database opens after the update fill that in without holding up a hook. No schema-version change: 6.28.1 still opens, searches and writes the database after this release has. Go back only after the fill finishes: rows it has not reached stay without the Latin identifiers glued to Chinese under 6.28.1.
- What it costs. Each database open spends about 250 ms filling the index terms of existing observations, newest first, each batch committed, until all are done. On a copy of the maintainer's store (516 observations, most already filled by an earlier build of this pass) the pass walked every row in one open of 48–49 ms. On a synthetic v6.27.0 store of 250,000 observations and 200,000 events (mostly CJK) the first open takes about 1 s (6.28.1: about 9 s, and a 5 s hook opening it first was killed), and the fill then takes 105 opens of about 0.5 s; an open after it takes 3 ms.
- A lesson a model writes onto one of your explicit saves starts with
🤖, andgetsays "🤖 lesson auto-written". A lesson you write is never marked. - Exit codes. A flag that takes no value (
--force,--json,--confirm,--or,--dry-run, …) no longer swallows the next word:delete --confirm 12now deletes #12, andrecent --json 5prints JSON.maintain,compress,optimize,delete,activity promote/delete,import-jsonl,defer dropandfts-check rebuild, which never read--dry-run, refuse it with exit 1 on a run that would write, instead of running;activity save,import-jsonlandrestorerefuse a mistyped--project;uninstallexits 3 when the MCP registration is left. CHANGELOG.md lists the rest; a script that relied on the old behaviour needs a look. - Also in this release: the database waits of a hook that opens through
ensureDball end by its deadline, the first open's migration, the prompt search and the Agent injection included; the unattended re-enrich raises importance to 2 at most; three more background prompts send stored rows to the model as data, and smart-compress and cluster merge act only on an explicit yes. Details in CHANGELOG.md.
Upgrading to 6.28.1
After the update, events are re-indexed so Chinese text in them is searchable (the Han ranges U+4E00–9FFF and U+3400–4DBF; kana, Hangul and the rarer Han extensions are not segmented). No schema-version change: 6.27.0 still opens, searches and writes the database after this release has, and an event it writes is filled the next time this release opens the database.
- What it costs. The first open adds a column, rebuilds the events index and adds the Latin identifiers glued to Chinese text to existing observations' index text. Existing events are then filled newest first, about 250 ms per database open (checked after each row), each batch committed, so a killed open keeps its progress; with nothing left an open takes 3–4 ms. On the maintainer's store (6963 events) the first three opens took 295–327, 80–102 and 4 ms, and the file grew from 14 to 15 MB; on a CJK-heavy synthetic store of 200,000 events the first open takes 964 ms, the fill 79 opens, and the file grows from 93 to 273 MB. Until an event is filled, a Chinese query misses it as before.
- The MCP server waits out another writer's lock at startup (up to 15 s of retries) instead of exiting with "database is locked".
- Exit codes. Some commands that exited 0 now exit 1, among them
save,update,defer addandactivity savegiven a flag they do not take,saveanddefer addgiven a mistyped project,deletegiven an id list with a malformed part (1,abc),self-updatewhen it checked nothing, andimport-jsonlon lines none of which is a transcript event (an empty file still exits 0); CHANGELOG.md lists the rest.installexits 3 when only the MCP registration failed. A script that relied on exit 0 there needs a look. - Also in this release:
installno longer empties the marketplace clone'shooks/hooks.json(it puts back, withgit checkout, one an earlier install emptied); four MCP tool texts say what the tools do; a hook under a long write lock exits instead of being killed (UserPromptSubmit and SessionStart with their context), and a lock that ends between 3.5 s and the 5 s kill, which 6.27.0 waited out, now drops the write the hook was waiting to make (UserPromptSubmit's prompt row, for one). Details in CHANGELOG.md.
Upgrading to 6.25.0
Every session gets the same first line on a file-recall block again. No schema-version change: 6.24.0 still opens the database after this release has.
- What changes. Since 6.16.0 half of sessions got a plain statement of where the notes come
from instead of "system-injected context, continue your planned action", so the two could be
compared by how often the agent cited a lesson. On the maintainer's own sessions the comparison
found no difference it could detect, and a scan of the agent's replies found neither line
surfaced to the user, so the split is closed: unset now means the older line in every session.
CLAUDE_MEM_RECALL_FRAMING=factualkeeps the plain statement;=abrestores the split. - Also in this release: when the data directory cannot be read,
doctorsays "not checked" for what it could not look at andstatusnames the directory, instead of reporting a missing or empty store.maintain execute --ops dedupnames the merge pairs it skips. Details in CHANGELOG.md.
Upgrading to 6.24.0
If you set CLAUDE_CONFIG_DIR, the installer, updates and hooks now use that directory, as
Claude Code does. No schema-version change: 6.23.3 still opens the database after this
release has. Without CLAUDE_CONFIG_DIR nothing in this item changes.
- Who is affected. Claude Code keeps
settings.json,plugins/and.claude.jsoninCLAUDE_CONFIG_DIRwhen it is set. The plugin kept using~/.claudeand~/.claude.json:installwrote its hooks where Claude Code never reads them, and with two profiles the session-start setup andself-updatepruned the other profile's plugin cache and edited its.claude.json. - What to do. Installed with npm or npx: run
claude-mem-lite installonce more with the variable set, so the hooks land in your config directory. Plugin installs need nothing. Hook entries an older version wrote into~/.claude/settings.jsonstay there; if no other profile uses~/.claude,env -u CLAUDE_CONFIG_DIR claude-mem-lite cleanup-hooksremoves them. - Also in this release:
npm install -g claude-mem-liteandnpxno longer install the development toolchain. As a dependency install it went from 298 packages / 541 MB to 96 / 57 MB; after the release,-gandnpxmeasured the same 96 packages. A confirmedmaintain execute --ops purge_stalenow deletes every row its preview counted in one run, behind one snapshot.doctorwarns about a failing update check and about anANTHROPIC_BASE_URLthat ends in/v1. Details in CHANGELOG.md.
Upgrading to 6.23.1
If you set ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY, background LLM calls now go to that
gateway. No schema-version change: 6.22.1 still opens the database after this release has.
To keep the old behaviour, stay on 6.22.1; a plugin install that already updated can follow the
rollback recipe under "Trust model per install path".
- Who is affected. The direct API leg used to post to
api.anthropic.comwith the built-in model IDs whateverANTHROPIC_BASE_URLsaid; only theclaude -pfallback followed it. Now the direct leg uses it too, together withANTHROPIC_DEFAULT_HAIKU_MODEL/ANTHROPIC_DEFAULT_SONNET_MODEL. WithoutANTHROPIC_BASE_URLnothing changes, even when the two model variables are set. - A value the plugin cannot use (not a parseable URL, not http or https, plain http to a
host that is not loopback, credentials in the URL, a query or a fragment) skips the direct
leg: every background call falls back to
claude -p, which readsANTHROPIC_BASE_URLitself, andclaude-mem-lite doctorsays why.doctornow probes the gateway's host and port instead ofapi.anthropic.com. - Also in this release: large stores no longer time out on every prompt. The database now
gets SQLite's planner statistics, which adds SQLite's own
sqlite_stat1/sqlite_stat4tables. AndCLAUDE_MEM_SEARCH_TELEMETRY=1is a new, off-by-default switch that records MCP searches for relevance feedback. Details in CHANGELOG.md.
Upgrading to 6.21.0
Projects whose names are not plain ASCII get a new id, and what they stored moves once. No
schema-version change: 6.20.0 still opens the database after this release has. Pin
[email protected] before upgrading to avoid the move; after it, 6.20.0 names these
directories by their old ids again, so their moved rows are listed only with
--project=<new id>.
- Which projects. Every character outside ASCII letters, digits and
_.-used to become-, so~/projects/博客and~/projects/商城were bothprojects----and shared one memory. Letters, marks and digits of every script are now kept (projects--博客). An id whose parent and directory names are both plain ASCII keeps its id byte for byte; a name with letters, marks or digits of another script, or with a character outside the Basic Multilingual Plane such as🚀, changes id. - At a project's first session start its data moves. When no stored file path shows another
directory using the old id, the project takes everything under it, deferred items and session
history included. Otherwise only the memories whose file paths lie inside the directory move;
the rest stay under the old id. A one-time notice says what moved and how to list what stayed
(
claude-mem-lite recent 50 --project=<old id>). - Not in this release: directories with the same parent and name in different repositories
(
~/a/packages/apiand~/b/packages/api) still share one id. - Also changed: two sessions open in one project keep separate memory sessions (handoffs,
summaries and unsaved tool activity no longer mix); a session's follow-up prompts no longer
resume another session; maintenance hides idle memories for 7 days before queuing them for
deletion, in every project; an importance you set is no longer changed by reads, the access
boost or re-enrich;
recall/mem_recallrank the current project and the exact path first (--project/projectkeep one project);<private>fails closed on an unclosed, nested or attributed tag. Full list: CHANGELOG.md.
Upgrading to 6.19.0
Search output changes; no switch. No schema change and no migration, so reverting is
pinning [email protected].
search/mem_searchmark machine-written observations with🤖, andget/mem_getname them in the header. Hook-captured, imported and compressed rows carry it; explicit saves and events do not. The result line explains the mark whenever a shown row has it, andsearch --jsonaddsautoto observation rows.mem_searchshows a snippet line only when it adds to the title.- Fixes: a lone search match in a new per-project store no longer sorts last; a Bash step's stored description keeps the end of its output; the secret scrubber catches values behind markdown labels and stays linear-time on crafted input.
Upgrading to 6.18.0
One default changes, with a switch. No schema change and no migration. Pinning
[email protected] stops new lines; a line already written stays until that session's
next observation titles replace it, and 6.17.1 keeps its unrecognised tag as text in the
row's notes.
- Last Session shows how the previous session ended even without a report. When the
final reply has no Done / Not done / Failed / Uncertain section,
Completed:is its first 120 characters (code blocks and line-leading markers removed) where it used to be observation titles, which are usually empty; the /clear handoff labels it<session-summary source="last-reply">. A report or a model summary still takes precedence. A later reply under 400 characters ("You're welcome!") does not replace an earlier one, and a reply in which the secret scrubber finds anything writes no line at all.CLAUDE_MEM_SUMMARY_TAIL=0restores the titles from the session's next turn on.
Upgrading to 6.17.0
Two defaults change; one has a switch. No schema change and no migration, so reverting
everything is pinning [email protected].
- The pre-edit lesson line asks for a lesson's
#NNonly where it changed the edit. It no longer asks for an applied / not-applicable verdict on every lesson in your next reply, which put lesson-id lists into replies. Old directive:CLAUDE_MEM_SALIENCE=verdict(since 6.17.1 it also says it overrides the managed row and detail doc, which keep the new wording). Adopted projects get the matching CLAUDE.md managed row and detail doc on the next SessionStart;CLAUDE_MEM_NO_TEMPLATE_REFRESH=1keeps the old text. - SessionStart's "Deferred Work" list ends with "+N more open" when more than its 5 rows are open. No switch; pin 6.16.0 to revert.
Upgrading to 6.16.0
Three defaults change; one has an off switch. No schema change and no migration, so
reverting everything is pinning [email protected].
- Each session gets one of two first lines on a file-recall block. Half of sessions keep
"system-injected context, continue your planned action"; the other half get a plain
statement of where the notes come from, as Claude Code's hooks guide recommends. The two
are compared by cite-rate before one becomes the default. Off (old line everywhere):
CLAUDE_MEM_RECALL_FRAMING=legacy. - Memory text longer than the host's 10,000-character hook limit is trimmed by whole lines, with a closing line naming the ids left out, instead of the host replacing it with a 2,000-character preview. No switch; pin 6.15.0 to revert.
- Lessons shown after a failed Bash command now count in citation decay, like every other surface: ones never cited are ranked down over time. No switch; pin 6.15.0 to revert.
Upgrading to 6.15.0
Two defaults change; one has an off switch. No schema change and no migration, so
reverting everything is pinning [email protected].
- An auto-captured lesson that repeats text a tool printed is kept out of automatic
injection. Whoever controls a command's output (a repository's test, a fetched page, an
MCP server) could otherwise get a sentence of their choosing stored as a lesson that later
sessions are shown. Such an event stays searchable at importance 1; a
changeobservation loses the lesson. Four shared words are enough, filler included, so an occasional lesson of your own is demoted too. Off:CLAUDE_MEM_LESSON_OUTPUT_CAP=off. - Last Session reads your own Done / Not done report when it uses markdown headings
(
## Done,**Not done**) instead of falling back to the model's summary. No switch; pin 6.14.0 to revert.
Upgrading to 6.14.0
Five defaults change; three have an off switch. No schema change and no migration: an
older build still opens the database, so reverting everything is pinning
[email protected].
- File recall now also fires before Bash commands that view or write a file (
cat,sed -n,head;sed -i,cat > f, a python patch) — on recent models most reads and edits go through Bash, and the recall face with the highest measured cite rate had almost stopped firing. A fourth hook command runs underbash,pre-tool-recall-bash.sh; on Windows it needs Git Bash or WSL like the other three. Off:CLAUDE_MEM_BASH_RECALL=off. - Error recall stays quiet on a failure you meant to cause (running a test file you just
wrote or edited) and on commands that only print data and exit 0. No switch; pin 6.13.6 to
revert.
error_recallcounts incitation-statsdrop from here on — do not compare them across this version. - Auto-captured lessons must quote what happened. The episode summarizer ignores
mutation-test probes, the agent's own failing inline scripts and subagent calls that do not
edit the project, and a lesson that quotes nothing from its window is dropped (the event is
kept, at importance 1). Off:
CLAUDE_MEM_EPISODE_INPUT_FILTER=off/CLAUDE_MEM_LESSON_GROUNDING=off. - The
[mem] episode flushed: N entriesline is no longer injected. The hints that followed it are unchanged.
Upgrading to 6.13.0
One default changes: SessionStart no longer injects ### Key Events. That section listed
the five newest importance ≥ 2 rows of the events table — activity the background summarizer
records — at the top of every session, chosen by recency rather than by what you were doing. A
check of 30 of them against git history and transcripts found 2 accurate and 16 wrong. Events are still stored, searchable with mem_search, and
injected when your prompt or the file being edited matches them. To restore the section, set
CLAUDE_MEM_SESSION_EVENTS=1. No schema change and no migration: an older build still opens
the database, so reverting is pinning [email protected].
Citation readings drop at this version, and that is a measurement change. A lesson the
agent answers with #NN n/a is no longer counted as cited, so citation-stats per-face rates
read lower from here on (--sidechain excepted: it counts an n/a as an answer). Do not compare
a reading taken before 6.13.0 with one taken after.
Upgrading to 6.11.0
One default changes: re-enrich stops leaving part of its budget idle. It reserves half of
each run's budget for two backfill passes and gives its main scope the rest. When the main
scope had fewer rows to enrich than its share, the remainder went unspent; it now goes to the
backfills. The daily unattended pass runs once per machine per day, over all projects
together, so with an empty main pool it now makes up to 6 of these LLM calls a day where it
made 3. The ceiling of 6 is unchanged — it was always the declared budget — and a separately
budgeted scope-classification pass, also unchanged, can add up to 6 more short calls. A manual
claude-mem-lite optimize --run or mem_optimize behaves the same way. No schema change and
no migration: an older build still opens the database, so reverting is pinning
[email protected].
One security fix does not reach data you already have. In some combinations of two
labelled credentials on one line — token: <v> secret: <v> is one, when the first value ends
in a letter — earlier versions redacted the first and stored the second as typed. Values on
separate lines were never affected. This release fixes the write path; nothing already in
your database is rewritten.
Upgrading to 6.8.0
Two things change on upgrade. Neither needs an action from you, and neither is a schema change — an older build can still open the database.
The first open backfills the file-lookup table, once. Observations imported from a
transcript before 6.7.2 carry their modified-files list but no row in the junction table the
file-recall paths join, so asking about a file never found them. The repair was gated on that
table being completely empty, which one ordinary mem_save falsifies forever. It now runs
once per database, keyed on its own marker, and retries on a later open if it fails. A store
that never ran import-jsonl matches no rows and pays nothing.
Those rows become reachable through recall / mem_recall and the prompt-submit path. Two
limits, both measured:
- Not through the pre-tool recall hook, which admits only
importance >= 2while every imported row carries1. - Not for
NotebookEditrows imported before 6.7.2. Those were stored with an empty modified-files list, because the importer of the day readfile_pathandNotebookEditsetsnotebook_pathinstead — so the backfill, which selects on that column being non-empty, passes over them. Re-importing the transcript is what recovers those, and 6.7.2 made that work by putting the path into the title the dedup key is built from.
doctor --json changes shape. Checks that print a repair command now carry it in a
details array — previously the human screen got the command and the JSON got only the
diagnosis. And four checks (dev drift, managed files, and both hook-script branches) now
report "level": "fail" where they used to report "warn", with a new "glyph": "warn"
recording that they still render as ⚠ rather than ✗. They always counted toward the issue
total and the exit code; the level now says so. If you filter on level === "fail" you will
see four findings you were missing. The issues count, the summary line and the exit code
are unchanged.
Upgrading to 6.1.0
One default changes, and only for the daily background pass. Until 6.1.0 the unattended
normalize task read the concept vocabulary of EVERY project at once, sent it to the model as
one list, and wrote the answer back across every project — so one project's stored content
could steer the synonym groups applied to an unrelated project's rows. It now runs one scoped
pass per project.
What you may notice: k8s in one project and kubernetes in another are no longer folded
together by the daily pass. Nothing is deleted, no row moves project, and search behaviour is
unchanged — only which terms the background pass will unify.
It is forward-only. Terms that earlier cross-project runs already unified stay unified. The replaced term is kept on the row as a search alias, so those rows are still findable under the old wording, but there is no record of which unification came from another project.
To keep the old behaviour: set CLAUDE_MEM_NORMALIZE_CROSS_PROJECT=1. It restores the
cross-project scope — and that scope is exactly the guard it gives up. The other two checks
added in this release (a shape gate on concept tokens, and a check that the model's answer only
uses terms the corpus already had) do still run on that path, but the second one is then judged
against the union of every project's vocabulary, so it no longer keeps one project's term out
of another project's rows. Set it only if you want cross-project unification and trust the
contents of every project in the store. A foreground optimize run prints a warning when the
flag is set, and claude-mem-lite doctor reports it as ⚠ — the daily pass runs in a worker
with stderr closed, so it cannot warn you itself.
Upgrading to 6.0.0 (breaking)
The default search path does not change. 6.0.0 removes the TF-IDF vector arm, which has been disabled by default since 3.17.0 — if you never set CLAUDE_MEM_VECTORS, upgrading is behaviour-identical and there is nothing to do.
Three surfaces are gone:
The migration is one-way. Once a 6.0.0 build has opened your database, older versions
refuse it — schema.mjs's forward-incompat guard throws "DB schema is v49 but this
claude-mem-lite binary supports up to v48". If you want to stay on the vector arm, pin
[email protected] before upgrading. If you have already upgraded and need to go
back, either re-upgrade, point CLAUDE_MEM_DIR at a fresh directory, or restore a
pre-upgrade backup (claude-mem-lite export / the snapshots under your data dir).
Why it was removed: measured directly against the shipped path, the arm was negative on both benchmark fixtures — including the vocabulary-mismatch suite that is the only reason a vector arm would exist (Recall@10 0.3407 → 0.3018, and roughly +88% P95 latency). No observations are lost; only the derived vector index is.
Usage
MCP Tools (used automatically by Claude)
The server defines 19 tools but registers mem_search_feedback only when
CLAUDE_MEM_SEARCH_TELEMETRY=1. The other 9 core tools appear in tools/list
by default. The 9 hidden tools remain callable at the
protocol layer (tools/call by exact name still routes normally); they're
omitted from the list response so Claude Code sessions don't load 9 extra
tool schemas at startup. (It read 20 / 11 until v5.0.0 removed the two skill-registry
tools — tool-schemas.mjs is the source of truth, and
tests/tool-count-docs.test.mjs now holds this paragraph, both README tool tables,
README.zh-CN.md, llms.txt and docs/ARCHITECTURE.md to it.) Hidden tools are the maintenance / admin / browser
surface — reach them through the CLI column in the second table.
Core (10 definitions; mem_search_feedback requires search telemetry)
Hidden-but-callable (9, CLI-routed)
Headless runs (claude -p). With no permission prompt to answer, Claude Code 2.1.292
refused every mem_* call until the server's tools were allowed, and the model did not switch
to the CLI by itself: in an 88-session headless run (2026-10-06) all 13 calls were refused and
nothing was saved. On 2.1.284 (2026-09-29) the same flags let these calls through, so this
depends on the Claude Code version. Allow the server once in the settings.json the run reads:
{ "permissions": { "allow": ["mcp__plugin_claude-mem-lite_mem-lite"] } }
For a single run, claude -p … --allowedTools "mcp__plugin_claude-mem-lite_mem-lite" does the
same (both forms checked on 2.1.292, 2026-10-07: no refusal, and mem_search answered). That is
the server's name under a plugin install. An npx or git-clone install registers it as
mem-lite, so the rule there is mcp__mem-lite. An interactive session asks instead; answering
"always allow" there records a rule of its own. The plugin does not tell the model to fall back
to the CLI when a call is refused: the refusal is Claude Code's permission check for a tool you
have not allowed, and routing the same action through Bash would sidestep it.
Skill Commands (in Claude Code chat)
/mem search <query> # Full-text search across all memories
/mem recent [n] # Show recent N observations (default 10)
/mem recall <file> # Show past observations for a file
/mem save <text> # Save a manual memory/note
/mem stats # Show memory statistics
/mem timeline <query> # Browse timeline around a match
/mem browse # Tier-grouped memory dashboard
/mem <query> # Shorthand for search
/lesson <text> # Save a non-obvious lesson as a searchable observation
/claude-mem-lite:bug <text> # Log a known bug + repro steps as a searchable observation
/claude-mem-lite:verify # Check memories against the current code; correct stale ones after you approve
Efficient Search Workflow
1. mem_search(query="auth bug") -> compact ID index
2. mem_timeline(anchor=12345) -> surrounding context
3. mem_get(ids=[12345, 12346]) -> full details
Invited Memory (v2.32+)
Opt-in mechanism that installs a slug-scoped managed block into the project's
own CLAUDE.md (plus an on-demand detail doc under .claude/) so Claude Code
loads the plugin's MCP-tool triggers as project instructions, delivered in a
message after the system prompt. Steering lives in CLAUDE.md (the canonical home
for project instructions) rather than in the memdir MEMORY.md, which is reserved
for the user's own memories. The pre-v3.13 scheme wrote into MEMORY.md; it is migrated away
automatically on the next SessionStart.
claude-mem-lite adopt # install for current project
claude-mem-lite adopt --all # legacy clean-up: strip the old memory-dir sentinel in every project
claude-mem-lite adopt --status # list adopted/disabled projects + current gating snapshot
claude-mem-lite adopt --dry-run # preview without writing
claude-mem-lite adopt --disable # opt out of auto-adopt for current project (writes .mem-no-auto-adopt sentinel)
claude-mem-lite adopt --enable # re-arm auto-adopt for current project (deletes the sentinel)
claude-mem-lite unadopt # remove the CLAUDE.md block + doc, and the local block (CLAUDE.local.md or .claude/rules/; not written again)
Slash commands /adopt and /unadopt wrap the same CLI.
What adoption changes:
- A
…managed block is added to<cwd>/CLAUDE.mdunder its own## claude-mem-lite — persistent memoryheader, containing a compact trigger table pointing atmem_recall/mem_save/mem_deferwith their key arguments. The block is slug-scoped: only this region is managed; the rest of yourCLAUDE.mdis preserved verbatim, and it coexists with other plugins' blocks (e.g.code-graph-mcp) in the same file. - A
<cwd>/.claude/plugin_claude_mem_lite.mddetail file is written (not auto-loaded; read on demand when theCLAUDE.mdblock points to it). The block auto-refreshes when the shipped content drifts (version bump or template change), unlessCLAUDE_MEM_NO_TEMPLATE_REFRESH=1. - Post-adopt the conservative hook layer auto-trims: MCP server instructions
drop the
WHEN TO USEsection, SessionStart injection drops theFile Lessons/Key Contextsections.#IDreferences and theRecenttable still fire somem_getremains reachable.
When does it take effect?
- The
CLAUDE.mdmanaged block and the hook-layer trim (File Lessons/Key Context/ lesson suffix) apply on the next SessionStart (any new Claude Code session in the adopted project). - The MCP server instructions are built once at server boot and MCP has no
"push" protocol — the
WHEN TO USE/Decision rulestrim only applies after Claude Code restarts and re-spawns the mem-lite MCP server. A single/exit+ fresh session is enough. Same caveat applies tounadopt.
Safety:
- Regenerated, not hand-edit-safe:
adoptrewrites the managed block to the shipped template, and so does every SessionStart whenever the block differs from it — hand edits included. Keep your own notes outside theclaude-mem-lite:begin…endmarkers, or setCLAUDE_MEM_NO_TEMPLATE_REFRESH=1to freeze the block. - Slug-scoped & dedup-guarded: only the
claude-mem-lite:begin…endregion is ever rewritten, and duplicate / CRLF-orphaned copies are collapsed to one. Unlike the legacyMEMORY.mdscheme there is no line-budget gate —CLAUDE.mdhas no truncation cap. - Auto-adopt runs on EVERY SessionStart, for any install path, and no longer adds
the block to
CLAUDE.md(next release after 6.19.4). A git project without the block gets it inCLAUDE.local.mdat the git root (kept out of git via.git/info/exclude), or in SessionStart context whereCLAUDE.local.mdwould stop Claude Code reading the project'sAGENTS.md(in.claude/rules/claude-mem-lite.mdwithCLAUDE_MEM_RULES_STEERING=1), outside git, and where no file can be written; a project that carries theCLAUDE.mdblock (explicitadopt, or an older version's auto-adopt) has it kept in sync — refreshed when the shipped template changes (freeze withCLAUDE_MEM_NO_TEMPLATE_REFRESH=1) — and gets no local or injected copy, also in sessions started from a subdirectory; aCLAUDE.mdholding only the block also gets adopt'sAGENTS.mdimport when anAGENTS.mdis beside it. A local block (CLAUDE.local.mdor the rules file) the plugin created and you (orunadopt) removed is not written back;adopt --enablere-arms it (beside such anAGENTS.md, together withCLAUDE_MEM_RULES_STEERING=1). Per-project opt-out:claude-mem-lite adopt --disable(writes a durable<memdir>/.mem-no-auto-adoptsentinel that survives marker deletion / plugin reinstalls; run at the repository root it also covers sessions started in its subdirectories; it removes the local block). Global opt-out:MEM_NO_AUTO_ADOPT=1(blocks already written stay untilunadopt). - The fallback hook layer is never removed from source — conditional trim is runtime-gated on steering being delivered (block present, or injection on), so projects that opted out get the full verbose output.
See the invited-memory design for the full design (including the
reusable template other plugins can follow). It is a development-time document and
is no longer in the repository at HEAD, so that link is pinned to v3.95.0, the
last release that carried it.
Database Schema
Five core tables with FTS5 virtual tables for search:
observations -- Individual coding observations (decisions, bugfixes, features, etc.)
id, memory_session_id, project, type, title, subtitle,
text, narrative, concepts, facts, files_read, files_modified,
importance, related_ids, created_at, created_at_epoch,
lesson_learned, minhash_sig, access_count, compressed_into, search_aliases,
branch, superseded_at, superseded_by, last_accessed_at
session_summaries -- per-session summaries (written at Stop, upgraded by the background model summary)
id, memory_session_id, project, request, investigated,
learned, completed, next_steps, files_read, files_edited, notes,
remaining_items, lessons, key_decisions
sdk_sessions -- Session tracking
id, content_session_id, memory_session_id, project,
started_at, completed_at, status, prompt_counter
user_prompts -- User prompts captured via UserPromptSubmit hook
id, content_session_id, prompt_text, prompt_number
session_handoffs -- Cross-session handoff snapshots (UPSERT, max 2 per project)
project, type, session_id, working_on, completed, unfinished,
key_files, key_decisions, match_keywords, created_at_epoch
observation_files -- Normalized file membership for efficient file-based recall
obs_id, filename
FTS5 indexes: observations_fts (title, subtitle, narrative, text, facts, concepts, lesson_learned), session_summaries_fts, user_prompts_fts
How It Works
Hook Pipeline
SessionStart
-> Read the host's `source` (startup | clear | compact | resume) from stdin
-> On clear/compact: read the outgoing session from the session file, save its
'clear' handoff, emit the Working State block (R10-P1-1, fixed v5.4.0)
-> Generate session ID (overwrites the session file)
-> Mark stale sessions (>24h active) as abandoned
-> Clean orphaned/stale lock files
-> Query recent observations (24h)
-> Inject context into CLAUDE.md + stdout
PostToolUse (every tool execution)
-> Bash pre-filter skips noise in ~5ms (Read paths tracked to reads file)
-> Detect Bash significance (errors, tests, builds, git, deploys)
-> Accumulate into episode buffer
-> Proactive file history: show past observations for edited files
-> Flush when: buffer full (10 entries) | 5min gap | context change
-> Collect Read file paths into episode on flush
-> Spawn LLM episode worker for significant episodes
-> Error-triggered recall: search memory for related past fixes
UserPromptSubmit (two parallel paths)
-> [user-prompt-search.js] Auto-search memory via FTS5 + active file context
-> [user-prompt-search.js] Inject relevant past observations with recency/importance weighting
-> [user-prompt-search.js] Write injected IDs to temp file for dedup
-> [hook.mjs handleUserPrompt] Capture user prompt text to user_prompts table
-> [hook.mjs handleUserPrompt] Increment session prompt counter
-> [hook.mjs handleUserPrompt] Handoff: detect continuation intent → inject previous session context
-> [hook.mjs handleUserPrompt] Semantic memory injection (hook-memory.mjs), deduped via temp file
Stop
-> Flush final episode buffer
-> Save handoff snapshot (type 'exit')
-> Mark session completed
-> Write the session summary row (sync, no model call)
-> Spawn LLM summary worker (poll-based wait)
-> Keep the session file <- Stop fires per TURN; deleting it here re-minted a mem
session every turn and left the SessionStart /clear branch unreachable (v5.4.0)
Episode Encoding
Episodes are batched related operations (edits to the same file group) that get processed by a background LLM worker:
Episode buffer -> Flush to JSON -> claude -p --model haiku -> Structured observation -> SQLite
Each observation includes type, title, narrative, concepts, facts, importance (1-3), and is automatically deduplicated via two tiers: Jaccard similarity (>70% within 5 minutes) and MinHash signatures (>80% within 7 days across sessions). If the LLM call fails, an episode that fixed an error a successful command printed is saved with inferred metadata (a command reported as failed does not enter the episode); an edit-only episode (config and schema changes included) is usually dropped as noise. Related observations are linked via related_ids based on FTS5 title similarity and file overlap.
Management Commands
# Plugin install:
/plugin install claude-mem-lite # Install / update
/plugin uninstall claude-mem-lite # Uninstall
# git clone install:
node install.mjs install # Install and configure
node install.mjs uninstall # Remove (keep data)
node install.mjs uninstall --purge # Remove and delete all data
node install.mjs status # Show current status
node cli.mjs doctor # Diagnose issues (cli.mjs, not install.mjs — see note)
node cli.mjs repair # Recover a broken install from the latest signed release
node install.mjs cleanup-hooks # Remove only stale claude-mem-lite hooks from settings.json
node install.mjs update # Force-check for updates and install them (direct install / npx mode)
# npx install:
npx claude-mem-lite # Install / reinstall
npx claude-mem-lite uninstall # Remove (keep data)
npx claude-mem-lite doctor # Diagnose issues
doctorandrepairare spelledcli.mjs, notinstall.mjs, on purpose. Those two are the commands you reach for when the install is already broken, andinstall.mjsresolves around a dozen static imports before its first line runs — one missing file and it exits with a Node stack instead of telling you which file.cli.mjshas no static local imports and catches that, naming the file and a repair command. Everything else in the list is unaffected either way, exceptupdate: throughcli.mjs(and theclaude-mem-litecommand)update <id>edits a memory, and the updater isself-update.
Notes:
- Plugin mode only reports available updates; it does not self-update plugin files.
To upgrade an installed plugin to the latest published version, run inside Claude Code:
(The first command refreshes the local marketplace clone; the second reinstalls from it. Without the first command,/plugin marketplace update sdsrss /plugin install claude-mem-lite@sdsrss/plugin installreuses the stale local clone and you stay on whichever version you originally pulled.) - Direct install / npx mode checks GitHub Releases once a day (every 6 hours while GitHub is rate-limiting), from a session start, and shows a notice about a newer version at the next session start; the check does not install it.
npx claude-mem-lite self-update(from a git clone,node install.mjs update) installs it, with staged replacement and rollback on install failure. Two things install without a command: when a hook finds a module or dependency missing, a session start runsrepair(at most once per 6 hours), which installs the latest release if its signature verifies; and when the plugin is installed too, a session start copies a newer plugin version into the direct install (that copy is not signature-checked). CLAUDE_MEM_SKIP_UPDATEturns off the check, the notice, andself-update/install.mjs updateas well, so unset it before updating. It stops neither of the two installs above.- If you disabled the plugin but still have old mem hooks in
~/.claude/settings.json, runnode install.mjs cleanup-hooks.
Trust model per install path
The three install paths do not carry the same supply-chain guarantees — pick the one that matches your threat model:
Rollback recipe (plugin path). If an update misbehaves, pin the marketplace clone to the previous release tag and reinstall from it:
# 1. Find the local marketplace clone
ls ~/.claude/plugins/marketplaces/ # e.g. sdsrss
# 2. Pin it to the previous good tag (tags mirror npm versions, e.g. v3.62.0)
cd ~/.claude/plugins/marketplaces/sdsrss
git fetch --tags && git checkout v3.62.0
# 3. Reinstall from the pinned clone — inside Claude Code:
# /plugin install claude-mem-lite@sdsrss
# 4. To leave the pin later: git checkout main, then the normal update flow.
Your data directory (~/.claude-mem-lite/) is untouched by install/rollback; schema migrations are forward-only, so after rolling back more than one minor version check node cli.mjs doctor before trusting search results.
doctor
Checks Node.js version, dependencies, server/hook files, database integrity, FTS5 indexes, stale processes, MCP registration, and whether the marketplace clone can still be updated.
It runs claude mcp list to answer the registration question, and that health-checks every
MCP server you have configured — i.e. briefly launches each one, including remote endpoints.
status deliberately does not: on a plugin install it answers from the manifest instead.
status
Shows MCP registration, hook configuration, plugin disabled state, and database stats (observation/session counts).
Recovery (stuck install / hook errors)
If you see ERR_MODULE_NOT_FOUND on PreToolUse:Read/Edit hooks, or claude-mem-lite commands crash with import errors, you're likely hit by a partial update — an updater copied new scripts but missed a sibling lib/* file, breaking the hook chain.
v2.84.0+ ships a repair subcommand that re-syncs from the latest GitHub release:
claude-mem-lite repair
If repair itself fails (the bin is older than v2.84.0, or the bin is also broken), run this one-liner — it pulls a fresh tarball into a temp dir and runs that tarball's install.mjs, bypassing every file on your disk:
T=$(mktemp -d) && U=$(curl -sL https://api.github.com/repos/sdsrss/claude-mem-lite/releases/latest | grep -o '"tarball_url"[^,]*' | cut -d'"' -f4) && curl -sL "$U" | tar xz -C "$T" --strip-components=1 && node "$T/install.mjs" install
It resolves the latest release tag first. A shell one-liner cannot verify the release signature the way repair does, so running it is a trust decision you are making explicitly — that is why it is the last resort and not the first suggestion.
After it finishes, ~/.claude-mem-lite/ is back in sync with the latest release and claude-mem-lite repair is available for n