minnimemory
v1.5.0
Published
MinniMemoryMCP: an MCP server that shrinks what Claude Code and Codex load every turn. It splits your memory files (CLAUDE.md, AGENTS.md) into a small AlwaysOnMemory part and OnDemandMemory files that load only when a task needs them: init, apply, undo, d
Maintainers
Readme
MinniMemoryMCP
Curated memory for AI agents, served over MCP. Your AI coding agent re-reads its entire
project memory file on every turn, most of it irrelevant to the task in front of it, and an
edited character near the top of that file busts prompt caching for everything after it.
MinniMemoryMCP audits the file, compiles it into a small AlwaysOnMemory body plus OnDemandMemory
files loaded only when relevant, and can undo the whole thing in one step. Four verbs, the same
in Claude Code and in the terminal: init, doctor, apply, undo. Deterministic, lossless,
offline: no model, no key, no network, and the MCP server never writes your memory files (its one
write is init's record of where Claude Code loads from).
Built by Minni AI. Docs: minniai.com/minnimemorymcp/docs. Measured results: minniai.com/evidence. AI agents: start at minniai.com/agents.md. Watch: MinniMemoryMCP product demo, how it reduces your token usage.
Try it in the terminal first
Requires Node 18 or later. No install needed to try it once. init is always the first step: it
finds what Claude Code loads, saves those locations, and previews the compile. Nothing is written
to your memory until you run apply. The full loop, against examples/CLAUDE.md (the fixture
shipped in this package), real output (approx-v2 tokenizer, an offline estimate; trimmed for
length):
$ npx -y minnimemory init .
memory map for /path/to/your/repo: every file Claude Code loads, by its own rules
every session: 897 tokens
MM003 "Current status" (lines 28-33) holds a status heading and high
status language in the always-loaded prefix
-> move it to an OnDemandMemory file; editing it here invalidates
the prompt cache every time
...2 more findings...
always-loaded, files this plan rewrites: 897 -> 389 tokens (-56.6%)
best case: kept only on turns that open none of the moved files. Measured on another memory with an agent that can read and search, whole sessions cost 24.0% less and answers held (judge 76 of 81 vs 74 for the original; ledger 2026-10-06).
verdict: compile. Every turn with no routed file open saves 508 tokens; a turn with every
routed file open (613 tokens) costs 105 more than today. It pays unless routed text is open
on nearly every turn.
operations:
write .minnimemory/.gitignore +5 -0
copy 1 file(s) -> .minnimemory/original/ (CLAUDE.md -> CLAUDE.md.bak)
write .claude/OnDemandMemory/CLAUDE__history.md +28 -0
write .claude/OnDemandMemory/CLAUDE__detail.md +28 -0
write CLAUDE.md +7 -47
...
plan: 7eb0e22e815143d0
dry run, nothing written. To write these files, run:
npx -y minnimemory apply --plan 7eb0e22e815143d0 --budget 2000 .
$ npx -y minnimemory apply --plan 7eb0e22e815143d0 --budget 2000 .
7 file(s) written; originals are under .minnimemory/. Undo any time: npx -y minnimemory undo .
$ npx -y minnimemory doctor
nothing to apply: no drift since the last apply
$ npx -y minnimemory undo
restored 1 file(s) in /path/to/your/repo
deleted: 5 path(s), .minnimemory/ includeddoctor is for later, after init: it refuses until init has run here ("run init first").
With no folder, doctor, apply and undo use the folder init saved. Its exit code (0
clean, 1 findings at or above --fail-on, 2 an error) is what a CI step keys on.
Your Claude Code memory folder (~/.claude/projects/<project>/memory) is loaded with files
outside it: your ~/.claude/CLAUDE.md and anything it imports. minnimemory never searches for
those; it reads only what it is told Claude Code loads, so it cannot guess wrong. In Claude Code
the agent tells it (below). In a terminal, give it Claude Code's own load log, or the files by
hand:
$ npx -y minnimemory init ~/.claude/projects/<project>/memory --from-log
$ npx -y minnimemory init ~/.claude/projects/<project>/memory --loaded ~/.claude/CLAUDE.mdinit checks each path and saves the set for apply, doctor and undo. apply on a memory
folder refuses until something was reported, because moving a file that an unseen outside file
imports would break that import; --only-this-folder says nothing outside loads it.
apply --plan <hash> recomputes the plan from what is on disk and refuses if it no longer
matches the hash init/doctor showed you, so what you saw is exactly what gets written. Plain
apply (no --plan) works too and skips that check. Every command the tool prints is the one to
paste, with the target path when it is not the current directory. Run from an installed copy
(the npx cache, a global install, a checkout) the command calls that same copy directly, node
"<path to minnimemory>/cli.js" apply --plan <hash>, so the plan is recomputed by the build that
made it; only when the tool cannot tell where it runs from does it print npx -y minnimemory ....
Install for Claude Code
npm i -g minnimemory
minnimemory installThe first line installs the package once; the second registers the MCP server for every project on this machine (runs claude mcp add
minnimemory -s user -- node "<path to minnimemory>/cli.js" mcp and prints exactly that line: a
direct call to the installed copy, so Claude Code does not re-resolve the package through npx
on every launch), turns on two prompt hooks by default (the memory hook and the drift notice,
see below), and prints the next step. Restart Claude Code. Running it again is safe: it tells you
it is already registered. After upgrading the package, run it again: it repoints the server and
the hooks at the new copy.
The registration points at the copy install ran from, so that copy has to stay. Run as
npx -y minnimemory install, it would be the npx cache, which npm clears on its own; the server
would then show "Connection closed" and every hook would fail with no message. So from the npx
cache install writes nothing and prints the two lines above.
Or as a Claude Code plugin
/plugin install minnimemory --marketplace MinniAI-com/minnimemory-pluginThe same release, the same server and the same hooks, installed and removed through /plugin,
with nothing written to your settings.json by hand. Needs Claude Code 2.1.275 or later for the
one-line form (older: claude plugin marketplace add MinniAI-com/minnimemory-plugin, then
claude plugin install minnimemory@minniai) and Node 18.3 or later on your PATH. Pick one
route: if minnimemory install ran on this machine, run minnimemory uninstall first
(npx -y minnimemory uninstall for an older npx registration), or you get two servers and every hook twice. Updates come with
claude plugin update minnimemory@minniai, or turn on auto-update for the minniai marketplace
in /plugin; claude plugin uninstall minnimemory@minniai removes it. The plugin repo holds
generated files only, built from the release tag (docs/DESIGN.md 7.1).
Useful variants of install:
| you want | run |
|---|---|
| this repo only, not every project | minnimemory install --scope project |
| see the command without running it | minnimemory install --dry-run |
| re-register over an existing entry | minnimemory install --force |
| skip the hooks | minnimemory install --no-hook |
| remove the registration and the hooks it added | minnimemory install --remove |
The memory hook (UserPromptSubmit, minnimemory hook) runs before every prompt and places the
one short section of memory the prompt needs into the turn, only when the always-loaded memory
does not already hold a better match, once per session. The agent answers in one call instead of
opening or searching files. In a continuous 27-task session on our Control Case it cut the
session's cost by a third with answers graded as good (measured 2026-10-11; the number reaches
this README through the results ledger, like every figure here).
The drift notice hook (UserPromptSubmit) tells the agent when a memory file has changed since
the last apply, so you never have to remember to run doctor by hand. It checks the project
and the project's Claude Code memory folder, prints one short notice, only on drift, only once
per distinct change. install writes it as a direct call to the installed copy, node "<path to
minnimemory>/cli.js" drift, which costs one node start per prompt (about 0.15 s measured; a
bare node is 0.08 s). It is never written as npx -y minnimemory drift: that form re-resolves
the package on every prompt and measured 1.3 to 1.7 s. --no-hook skips it at install time;
install --remove takes out both the MCP registration and the hook entry it added, leaving
every other key and every other hook in your Claude Code settings untouched.
Install for Codex
Codex loads one chain of AGENTS.md files every turn: $CODEX_HOME/AGENTS.md, then one file
per folder from the repo root down to your working directory. The project files share
project_doc_max_bytes (32 KiB by default): the file that crosses it is cut there and nothing
after it is added. Nothing loads on demand, so a long AGENTS.md is paid in full on every turn
and whatever sits past the cap never reaches the model. The same four verbs trim it the same way.
npm i -g minnimemory
minnimemory install --host codexThis runs codex mcp add minnimemory -- node "<path to minnimemory>/cli.js" mcp --host codex
(printed, and idempotent) and adds the two UserPromptSubmit hooks to ~/.codex/hooks.json (--scope
project writes <repo>/.codex/hooks.json; Codex has no local scope). Without the codex
binary it prints the [mcp_servers.minnimemory] block to paste into ~/.codex/config.toml
and exits 2. Then open Codex and run /hooks once to trust the two minnimemory hooks. Do not
skip this: Codex skips an untrusted hook, and without the memory hook the sections moved to
OnDemandMemory/ never reach a turn. Until the hook has run, init says so in its map. Then
ask Codex to run init. In a terminal: npx -y minnimemory init . --host
codex. Codex's load set is fully determined by the files and config.toml, so --from-log and
--loaded are not needed; the host is detected when CODEX_HOME is set or your Codex config
registers minnimemory, and --host codex pins it. If you set CODEX_HOME, install passes it
on to the server (--env CODEX_HOME=...), because Codex starts MCP servers without it.
In codex exec (no one to approve a tool call), Codex refuses MCP calls that need approval.
The four verbs only read, so you can let them run without asking:
[mcp_servers.minnimemory]
default_tools_approval_mode = "approve"Codex does not show MCP prompts, so the /mcp__minnimemory__<verb> slash commands are Claude
Code only; in Codex, ask for the verb by name.
What is different on Codex: the map shows the chain (layer, file, bytes, loaded) and the cap in
force (a file cut by the cap shows PART: first N bytes); doctor adds MM022 (a file past the cap) and MM023 (AGENTS.override.md shadowing an
AGENTS.md); apply compiles the AGENTS.md Codex reads (the override file when present),
puts routed pieces in OnDemandMemory/ beside it when the repo has no .claude/ folder, and
refuses a plan whose compiled file would still be over the cap. A file that already fits under
the cap, and so loads whole, is left as it is: splitting it cost recall on Codex (--force
compiles it anyway). Started in a subfolder, one
apply writes every file the session loads: your user AGENTS.md, the git root's and the
subfolder's, each with its own backups, and one undo there puts them all back. The discovery record lives
under $CODEX_HOME/minnimemory/. Nested AGENTS.md files below the root are listed with their
size and not compiled. If your project has no AGENTS.md of its own, init works on your user
file ($CODEX_HOME/AGENTS.md, which Codex loads in every project) and says so; doctor, apply
and undo then follow it, with no folder or with the project named. In Codex, the agent tells init what Codex loaded for it, the way
Codex shows it: the folder from each "AGENTS.md instructions for" header, and user for your user
file; a guessed path is refused. Codex's own memories ($CODEX_HOME/memories/) are generated by Codex:
the map lists them with their size, and they are never compiled.
Measured on Claude Code; Codex measurement in progress (section 8 of the design).
Other MCP hosts (Cursor, Windsurf, Claude Desktop, SDK clients): install --host cursor
and --host windsurf print the mcpServers entry to paste, but the hook and the load model are
not built for them, and nothing there is exercised end to end.
Use it
Four verbs, the same four whether your agent calls them or you type them:
| verb | what it does | writes anything? |
|---|---|---|
| init | always first: takes where Claude Code loads from (your agent passes it; in a terminal --from-log or --loaded), checks and saves it, then the map of every file Claude Code loads (when, whose, tokens), the audit and the diff of every file apply would write, then the command to run | only its record of those locations, in your Claude config folder; never a memory file |
| doctor | after init, never first: the audit, drift since the last apply, the re-apply diff | no |
| apply | writes what init/doctor showed, originals backed up first; --plan <hash> checks nothing moved since you looked | terminal: yes, to disk. MCP: no. It returns the command to run and the list of operations (paths and line counts, no content), and your agent runs the command through your approval |
| undo | puts your original memory files back and removes .minnimemory/ | terminal: yes. MCP: no. It returns the command and the operations the same way |
The order is always the same: init, then apply. A week later, after you have edited a memory
file by hand, doctor, then apply again. undo at any time. doctor before init refuses and
says to run init first.
It works on two shapes, through one engine. A host file (CLAUDE.md, AGENTS.md,
GEMINI.md, .cursorrules) is trimmed in place: the file itself becomes the AlwaysOnMemory
body plus a fenced OnDemandMemory list, and routed pieces land beside it in
.claude/OnDemandMemory/. A memory folder (an index such as Claude Code's MEMORY.md plus
topic files, or the project's own Claude Code auto-memory folder) is sorted in place: every
topic file a linked host imports moves into AlwaysOnMemory/, trimmed the same way; every
other topic file moves into OnDemandMemory/, untouched; routed pieces join it there. A topic
file your index never linked gets a list line of its own, with trigger words, so it stops being
a dead end; a file whose name marks it as a copy (x (old).md, x.bak.md, x_v1.md) is left
off the list and named in the plan. The
always-loaded set of a memory folder also counts the files outside it that load it, because
Claude Code loads them on every turn too: your ~/.claude/CLAUDE.md when it imports from the
folder, directly or through a chain (CLAUDE.md -> @AGENTS.md, or a stub that imports a
dotfiles file which imports from the folder), and a CLAUDE.md or AGENTS.md that names the
folder in a list item or a link. minnimemory never searches for those files: it reads only the
files it is told Claude Code loads (the agent passes them to init from its own context; in a
terminal --from-log reads Claude Code's own load log, or --loaded <path> names each one), and
works out from their lines which of them load the folder. The report lists each linked file and
how, the reported files that load nothing in the folder, and any place the report and the lines
disagree. An @import line that changes is shown as its own block in the diff. A project root
reads only itself: no parent CLAUDE.md, no user files. With no folder named and nothing
recorded, the terminal asks and an MCP tool asks the agent to ask you.
Claude Code names its memory folder itself (~/.claude/projects/<project>/memory by default, or
the folder an autoMemoryDirectory setting points at), and the agent passes that path to init
as it sees it, so the folder is never worked out from a slug. The MCP server accepts that one
folder outside the project it runs in, because Claude Code named it.
A file with __ in its name is the tool's (<parent>__history.md, <parent>__detail.md
for a project root; the same names for a sorted folder's sources): a merge of every dated,
volatile or task-specific section that parent routed out, one piece per reason so the index
stays short. Every other file - AlwaysOnMemory/, OnDemandMemory/'s plain topic files, the
index itself - is yours, edited the way you always have.
Before, a memory folder and the user-level host that imports from it:
~/.claude/CLAUDE.md loaded every turn
@.../memory/feedback_a.md
~/.claude/projects/<project>/memory/
├── MEMORY.md index, loaded every turn
├── feedback_a.md always loaded: rules + incident history + dated notes
└── project_x.md on demand, nobody imports itAfter apply:
~/.claude/CLAUDE.md same text; only the import path changed
@.../memory/AlwaysOnMemory/feedback_a.md
~/.claude/projects/<project>/memory/
├── MEMORY.md same hook lines, link targets updated, list appended
├── AlwaysOnMemory/
│ └── feedback_a.md the rules only, same file name
├── OnDemandMemory/
│ ├── project_x.md moved, untouched
│ └── feedback_a__history.md the dated notes routed out of feedback_a
└── .minnimemory/ bookkeeping: manifest.json, original/, previous/In Claude Code
After the install, open Claude Code in your project and say what you want in plain words. The server tells the agent the loop on connect, so these four requests are all it takes:
| you say | the agent calls | what you see |
|---|---|---|
| "clean up my memory" | init, with the auto memory folder and every file Claude Code shows as loaded in its context | the files that load your memory and how, then the findings and a summary of the plan: one line per file (action, path, lines added/removed), the moves and the import rewrite in full. Those locations are saved, so nothing is asked again |
| "apply it" | apply | the command npx -y minnimemory apply --plan <hash> [path] to run and the operations it performs, one line each. Your originals are the first files written, to .minnimemory/original/ |
| "check my memory" (a week later) | doctor | the findings, what drifted since the last apply, and the re-apply plan, or "nothing to re-apply" |
| "undo the memory compile" | undo | the command that restores the originals and removes .minnimemory/, and the operations it performs |
The same four steps are slash commands. The server exposes each verb as an MCP prompt, and
Claude Code lists a server's prompts as /mcp__<server>__<prompt>, so after the install these
appear in the / menu:
/mcp__minnimemory__init
/mcp__minnimemory__doctor
/mcp__minnimemory__apply
/mcp__minnimemory__undoEach one hands the agent a one-line instruction to call the matching tool; nothing is written
by the command itself. Add a folder after it (/mcp__minnimemory__init C:\path\to\memory)
when the target is not the recorded one.
Every write goes through your own tool's approval prompt. The MCP server itself only reads, and
it never sends your files' content through the model: apply/undo return the command and the
operation list (about 240 tokens on the fixture above, about 300 on an 85-file memory folder);
content: true adds the files to write, for an agent that cannot run commands; backups are
listed as copies to make (from -> to), since their content is the file already on disk.
init/doctor return a summary diff over MCP by default (the plan table plus one line per
file); pass diff: "full" for the complete unified diff the terminal always shows.
What gets generated
A project root:
your-repo/
CLAUDE.md AlwaysOnMemory body + the fenced OnDemandMemory list
.claude/
OnDemandMemory/
<parent>__history.md dated, volatile sections, merged
<parent>__detail.md task-specific sections, merged (over about 1,500 tokens they
are split into <parent>__detail-<section>.md pieces instead)
.minnimemory/
manifest.json token counts, hashes, triggers, provenance
original/
CLAUDE.md.bak verbatim copy of what was there beforeThe OnDemandMemory list inside the host file is a routing manifest, not a summary:
<!-- minnimemory:ondemand-list -->
# OnDemandMemory
AlwaysOnMemory ends here. Read one file below only when the task matches its words.
New memory files go in .claude/OnDemandMemory/.
- `.claude/OnDemandMemory/CLAUDE__history.md`: test, src, error, deploys, status, services
<!-- /minnimemory:ondemand-list -->manifest.json records what came from where: per-file token counts, content hashes for
staleness detection, the keyword triggers, and the source line range each routed piece came
from.
Commands
The CLI and the MCP basic profile share the same four lifecycle verbs; the only difference is
who writes: the CLI writes to disk itself, the MCP tools return the one command to run and the
operations it performs (file content only on request, content: true).
minnimemory init [path]
init is always the first step. It takes where Claude Code loads from (the agent passes it
over MCP; in a terminal --from-log, --loaded or --memory-dir), checks every path, and saves
it in your Claude config folder for apply, doctor, undo and the hooks: the one thing init
writes, never a memory file. Then the map of every file Claude Code loads here: for a project, its
own CLAUDE.md / AGENTS.md, CLAUDE.local.md, .claude/rules/ (a rule with paths:
frontmatter loads only when a matching file is read), subfolder files Claude reads when it works
there and every @ import inside it; for a memory folder, its index, the files it loads and the
reported files that load it. Each file is tagged with when it loads, whose it is, and its tokens. --map prints only the map (--json for the data); init --json puts the map under
map, beside doctor and plan.
The map starts with a setup block, so you know how the project is set up before anything is
proposed: the host file Claude Code starts from, the file it points at when it is only a pointer
(CLAUDE.md holding @AGENTS.md), the one file apply will rewrite, and whether that file is
already compiled. When the project holds an AGENTS.md that Claude Code does not load, the map
ends with a not loaded by Claude Code group that names each one and why: a CLAUDE.md in the
same folder takes its place, or a CLAUDE.md higher up switches AGENTS.md off and no pointer
sits beside it. Those files count toward no total. An AGENTS.md host is compiled, audited and
undone exactly as a CLAUDE.md host is.
install also adds an InstructionsLoaded hook, which records what Claude Code actually loads
for each project in a small file under your Claude config folder (machine-local, never in a
repo). init --from-log reads it as the report of what loads your memory folder. And a
PostToolUse hook on Read logs which OnDemandMemory files your sessions open (file names and
counts only, in the same config folder); after 10 sessions doctor flags a file Claude read in
most of them (MM024), since a file nearly every session reads is cheaper always-loaded.
minnimemory stores no prompt text.
Then the first-time preview: the audit, then the plan with a unified diff of every file apply would
overwrite and the full content of every file it would create, then the command to run. It never
writes a memory file: passing --write or --update prints one line ("init never writes; use
apply") and exits.
On a memory folder the plan sorts every always-loaded topic file into AlwaysOnMemory/, trimmed
in place, and every other topic file into OnDemandMemory/, untouched; routed pieces join
OnDemandMemory/ there, one per parent per reason. The always-loaded files are the ones you
import on every turn, so that import is the placement decision: every stable section is a
candidate to stay, whatever its heading says, and "volatile" means a status heading, a
changelog-shaped body or repeated status phrases, never a date cited in a rule's rationale. With
no --budget, the budget keeps every stable section and moves only the volatile ones; when
nothing is volatile the plan says so, tells you what a lower budget would save (from a real
second plan), and leaves the choice to you. A lower --budget is shared fairly: a file that fits
an equal share stays whole, the largest files split the rest, and a heading-less file over its
share keeps its leading paragraphs and routes the rest as <name>__detail.md. A single host
file's default budget is 60% of the file, never under 2,000. On a file over about 3,300 tokens,
where 60% is above that floor, the saving lands at 40% or more with the rules still in the file;
a smaller file saves less, since the floor lets it keep more: its rule sections and what every task needs (commands, build,
test, architecture) stay, by content rather than by heading alone, and its records, references
and one-task runbooks move; a section over the room keeps its head and routes the rest as one
"(continued)" piece. The plan prints the budget it used and names every section
kept out to fit, so change --budget with the numbers in front of you.
Sections that read as hard rules (a heading or first line with HARD STOP, never, always or must)
are placed first, before size decides. To pin any other section, put
<!-- minnimemory:keep --> on the line under its heading. A pinned section that still does not
fit is named in the plan as "a rule".
.minnimemory/ (backups, manifest) is bookkeeping only, never something Claude Code auto-loads.
| flag | effect |
|---|---|
| --force | on a compiled workspace with no drift, preview the re-apply plan anyway; also previews compiling a file the plan's verdict says to leave alone |
| --loaded <path> | a file Claude Code loads this session; repeatable; the only way a file outside the folder is read (--host-file still works) |
| --memory-dir <path> | the auto memory folder Claude Code names; also the folder to use |
| --from-log | take the loaded files from Claude Code's own load log for this folder |
| --diff summary | the plan and the operation list without the file diffs |
| --budget <n> | always-loaded token budget AlwaysOnMemory must fit (default: see above) |
| --profile <name> | how much guidance to embed: auto, none, routing, output (the answer-length rules alone, about 230 tokens), full (default auto) |
| --allow-secrets | preview as if a credential-shaped source were allowed through |
| --episodic-json | plan episodic OnDemandMemory files (changelogs, logs) as JSON, not Markdown |
Profiles: auto is the same as none today, for every source: the list header already
tells the agent how to route. none embeds no rules block; routing is the rules routing
depends on, about 210 tokens; full is the complete discipline set, about 710 tokens. apply
refuses (with --force to override) a compile that would not shrink the always-loaded prefix,
naming the before/after tokens and the break-even point.
minnimemory doctor [path]
After init, never first: it refuses until init has run for this project, and with no path
it uses the folder init saved. The audit, drift since the last apply (MM010), then the re-apply
plan and "run apply", or "nothing to apply". Never modifies anything. Works on a repo, a memory folder, or a
single file. Names a workspace compiled by an older layout (manifest v4/v5) as such, with "run
undo, then apply" as the fix.
| flag | effect |
|---|---|
| --json | machine-readable output: the audit under doctor, the plan under plan |
| --force | on a compiled workspace with no drift, show the re-apply plan anyway |
| --ci | terse output, no colour |
| --fail-on <sev> | exit 1 at this severity or above: low, med, high (default high) |
| --budget <n> | always-loaded token budget for MM001 (default: the budget the workspace was compiled at, else 60% of the host file, at least 2000) |
| --policy <file> | a team's rule pack (policy.json): MM020 reports a required line missing from the always-loaded memory, MM021 banned text in any memory file; its always_on_tokens is the MM001 budget when --budget is not given. Without it, neither rule runs |
| --only <ids> | run only these rules, comma separated |
| --ignore <ids> | skip these rules, comma separated |
| --rules | list the rule set and exit |
Exit codes: 0 clean, 1 findings at or above --fail-on, 2 execution error, so a CI step
(npx -y minnimemory doctor --ci --fail-on med) can fail the build on memory bloat the same way
a lint error would.
minnimemory apply [path]
Writes what init or doctor showed. On a memory folder it refuses until init was told what
loads it (moving a file an unseen outside file imports would break that import);
--only-this-folder says nothing outside loads it. Looks at the workspace and does the right thing: an
uncompiled host file or memory folder gets a fresh compile; a compiled workspace with drift gets
re-applied, keeping every hand edit and every routed file; a compiled workspace with no drift
prints "nothing to apply" and changes nothing. Same flags, backups (.minnimemory/original/,
.minnimemory/previous/) and refusals as init/doctor.
--plan <hash> recomputes the plan from disk and refuses with "the plan on disk no longer
matches" when the hash differs from the one init/doctor printed, so what you approved is
what gets written; plain apply skips that check and always writes the current plan.
apply follows a pointer: if CLAUDE.md is only @AGENTS.md or "See AGENTS.md", it compiles
AGENTS.md and leaves the pointer untouched. A compiled host file renamed afterwards (for
example CLAUDE.md to AGENTS.md, with or without a pointer left behind) is the same host:
doctor reports the rename, and one apply carries the manifest and the backups over, so undo
still restores your original into the file that holds your memory now. A pointer rewritten by
hand so that it no longer reaches the compiled file is reported as drift; apply then writes
nothing and tells you the two ways out. Neither follows a symlink, in either direction. A
workspace compiled by an older layout (manifest v4/v5) refuses with "run undo, then apply".
minnimemory undo [path]
Restores every original from .minnimemory/original/ and removes .minnimemory/. On a memory
folder it also moves every sorted file back to the top level and removes AlwaysOnMemory//
OnDemandMemory/ once they are empty. Restores the original always: the files as they were
before the first apply, whatever re-applies happened since. A routed file you edited since the
last apply would lose those edits, so undo names it and refuses unless you pass --force.
"nothing to undo" when not compiled; a clear error when an original copy is missing (doctor
reports that as a finding before it matters). Works the same, byte for byte, on a workspace
compiled by an older layout (manifest v4 or v5) - "run undo, then apply" always has an undo to
run.
undo works only on the machine that ran apply. The backups in .minnimemory/original/
are gitignored, so they never reach your repo: a teammate's clone has the compiled files and
the manifest, but nothing to restore from. There, the way back is git history (the commit
before the compile). If .minnimemory/manifest.json is lost or unreadable (a git merge
conflict is enough), apply refuses and changes nothing; undo --force still restores every
backup in original/ and leaves .claude/OnDemandMemory/ for you to delete, since nothing then
records which files there it wrote.
minnimemory mcp [path]
Starts an MCP server, --profile basic (the same four verbs, always registered) by default.
| flag | effect |
|---|---|
| --profile <name> | basic (default): init, doctor, apply, undo, read-only. full: the six maintainer tools (recall, modules, outline, doctor, plan, always; apply with --allow-write) |
| --allow-write | adds the full profile's write tool. No effect on basic, which is read-only by construction |
init/doctor take diff: "summary" (default) or "full"; apply/undo take content: true
to include file content for a host without a shell. With format: "json", init returns three
keys, map (each load group, and each file's path, tokens and owner), doctor and plan;
doctor returns the last two. Every reply's data block carries a code drawn for that reply in
its open and close lines, so no line of memory text can end it; a memory line that looks like
the marker is also shown with "(quoted)" in the text form, and the JSON form keeps it exact. Register it with your agent rather than
running it by hand: minnimemory install above (a direct node "<path>/cli.js" mcp
registration), or by hand with claude mcp add minnimemory -- npx -y minnimemory mcp.
minnimemory install
Registers minnimemory with the current host in one command instead of the manual claude mcp
add line above, turns on the memory hook and the drift notice hook, and prints the next step.
| flag | effect |
|---|---|
| --scope <name> | user, project, or local, same meaning as claude mcp add -s (default user) |
| --write | serve the full profile with its write tools (--profile full --allow-write); the default basic profile never writes |
| --dry-run | print the command without running it, exit 0 |
| --force | remove an existing registration first, then re-add |
| --no-hook | skip the hooks (memory, drift notice, and on Claude Code the load log and the Read usage log; on by default) |
| --precise-reads | the Read hook also answers a whole-file Read of a large memory file with only the sections the current prompt needs, and names the rest by line range; off by default until a live check confirms Claude Code accepts the trimmed Read |
| --remove | remove the MCP registration and the hooks install added; the same as minnimemory uninstall |
| --host <name> | codex: register with Codex (codex mcp add) and write the two hooks to ~/.codex/hooks.json or <repo>/.codex/hooks.json, see Install for Codex. cursor, windsurf or json: print the mcpServers snippet to paste (and the config file for Cursor and Windsurf) instead of registering with Claude Code; no hook |
Idempotent: an existing registration is reported, with the command to change it, not duplicated.
Without the claude CLI on PATH it prints the manual line and a JSON snippet instead, exit 2;
if claude itself errors, its stderr is printed, exit 1.
minnimemory uninstall
Takes out what install added: the MCP registration (claude mcp remove minnimemory) and the two
hooks in your Claude settings.json. Every other hook and setting stays, including another tool's
hook that happens to look like ours. --dry-run shows the removals first; --scope matches the
one you installed with. Running it twice is harmless.
npx -y minnimemory uninstallIt does not undo your memory. A compiled folder keeps working as plain markdown without the tool,
and uninstall lists each one still compiled with its undo command, so run those first if you
want the originals back. It also leaves ~/.claude/minnimemory/ (the init records, the load log
and any undo backups); delete that yourself once you have checked undo-backups/. Restart Claude
Code afterwards.
minnimemory recall <path> <query...>
Ranks a compiled workspace's OnDemandMemory sections, or a manifest-less memory directory, against a query. Changes nothing.
| flag | effect |
|---|---|
| --max-tokens <n> | cap on returned tokens (default 1500); the top hit is always returned |
| --headings | print only "file > heading path (n tokens)" lines, not the content |
minnimemory hook
A Claude Code UserPromptSubmit hook, registered by install by default: reads the hook's JSON
off stdin, ranks the prompt against the compiled memory, and prints one fenced memory section to
stdout, or nothing. It prints nothing when the always-loaded memory holds the best match, prints
only the single best section and only under 400 tokens, only when that section shares at least
two words with the prompt and at least half of them, and never the same section twice in one
session (it reads Claude Code's session_id and keeps a small record under
<config>/minnimemory/hook-sessions/).
Measured live on 2026-10-11: a third lower session cost on a continuous 27-task session, answers
graded as good (Minni-AI run 2026-10-11-211152).
| flag | effect |
|---|---|
| --max-tokens <n> | cap on the ranked candidates (default 600); the placed section is capped at 400 regardless |
| --headings | print only heading-path lines, not the content |
| --dir <path> | an explicit memory directory, overriding the hook JSON's own cwd resolution |
minnimemory drift
A second UserPromptSubmit hook: reads the same stdin JSON, checks a compiled workspace for
drift (doctor rule MM010), and prints a one-line notice only when something changed since the
last apply, once per distinct change. minnimemory install registers it for you by
default; --no-hook skips it.
minnimemory bench [path]
What the compiled shape costs over a whole session, rather than on one turn: the tokens a compile moves out of the always-loaded prefix are not the same as tokens saved, since an OnDemandMemory file the agent opens becomes part of the conversation and is re-sent for the rest of the session.
| flag | effect |
|---|---|
| --turns <n> | session length for the exact bounds (default 50) |
| --workload <file> | one task description per line; without it, only the assumption-free bounds are reported |
| --whole-files | model an agent that reads the whole file the OnDemandMemory list named, instead of the ranked section |
| --json | machine-readable output |
Reports two kinds of claim, labelled every time: the exact bounds (best case, no OnDemandMemory
file opened; worst case, every file opened on turn 1 and carried; break-even), and, with
--workload, a session model routed the same way recall ranks.
Footprint
The MCP server's own always-loaded cost, measured (not assumed), approx-v2 tokenizer:
| | tokens |
|---|---|
| basic profile tool schemas (init, doctor, apply, undo), read-only | 1,251 |
| basic profile tool schemas, --allow-write (no effect on basic) | 1,251 |
| server instructions text | 50 |
Measured against a real registered server over an in-memory MCP transport, not assumed. Claude Code defers a tool's full schema until it decides the tool is relevant, so the real per-turn cost of a connected-but-unused server may already be lower than the schema total above; the instructions text is charged every turn regardless and is kept under about 60 tokens for that reason.
Security
init,doctor,apply,undo, andmcpmake no network calls and never run a subprocess.apply/undorefuse a symlinked source and refuse to write or delete through a symlink, so a cloned repo cannot point a memory file, or a routed piece, at a file outside the workspace.applyrefuses to write when the source holds a credential-shaped string, unless you pass--allow-secrets, and the prompt hook never places a memory section that holds one (it prints one redacted notice instead, once per session)..minnimemory/.gitignorekeeps the verbatim backups (original/,previous/) and the drift stamp local;manifest.jsonandgraph.jsonare meant to be committed, so a teammate'sdoctorsees the same drift.applyrefuses a source that is not valid UTF-8 rather than rewriting it with replacement characters, and keeps a CRLF file's line endings.undorefuses, unless you pass--force, when a compiled file was edited since the lastapplyor the workspace was re-applied since the first: it restores the originals from before the firstapply, and says what that would discard.- The MCP server validates
manifest.jsonand only serves files that resolve insideOnDemandMemory/(or.claude/OnDemandMemory/for a project root), whatever the manifest says. doctor --jsonincludes the absolute workspace path and the headings of your auto-memory files. Read it before pasting it somewhere public.
What it does not do
- Not a conversation compressor. It touches only your memory file, never the conversation.
- Not a RAG index over your code. It compiles the memory file you already wrote, nothing else.
- Not a proxy or model router. No model is called by any of the four verbs.
- Not a summariser. Every source line lands somewhere verbatim, with a stated reason.
- Not a compiler for a memory routing list.
applyrefusesMEMORY.mdas a single file, or any file that is mostly links to topic files beside it: compiling a routing list re-files its hook lines as content and orphans every topic file. Point it at the folder instead: the folder compile leaves the index's own lines alone and appends its list.
FAQ
Does it send my code or memory files anywhere? No. Every command makes no network calls at all. There is no telemetry and no API key.
Will it destroy my CLAUDE.md?
No. init/doctor are dry run by default. apply copies your original verbatim to
.minnimemory/original/ before anything is modified, never writes over that backup, and undo
restores it byte-identical on the machine that ran apply.
Does this fight with prompt caching? The opposite. Splitting volatile content out of the always-loaded prefix is precisely what keeps the cache valid across sessions.
Does it work with Cursor or Codex?
Codex: yes, since 1.4.0 (see Install for Codex): the same four verbs on the AGENTS.md chain
Codex loads, with its byte cap as a second ceiling. Cursor: the MCP server runs there
(install --host cursor prints the entry) but the hook and the load model are not built, and
nothing there is exercised end to end.
License
Proprietary, free to use. Copyright MinniAI. You may install and run the package, unmodified,
for your own use, including at work, from npm, a Claude Code plugin marketplace or Anthropic's
directory. You may not redistribute or modify it yourself, or take it apart to build a competing
product; reading its source is fine. The files it reads and writes on your machine stay yours,
and it sends nothing off your machine. The short instruction lines it writes into your memory
files are yours to keep, edit, commit and publish with your repository. The bundled open-source
components keep their own licences (THIRD_PARTY_NOTICES.md in the plugin). Full terms in the
LICENSE file shipped with the package.
