Long-term memory for Claude Code. A fact you save in one session is there in the next one, in any repo.
You need Bun on your PATH. At the prompt of a Claude Code session:
/plugin install mnemosyne --marketplace Interstellar-code/claude-mnemosyne
Or from a shell:
claude plugin install mnemosyne --marketplace Interstellar-code/claude-mnemosyneAnswer y to add the marketplace, then pick a scope (user scope for every project). The install then asks for the settings below; leave the empty ones empty. Claude Code's install screen shows values only for the scope picker and the on/off switch, so /mnemosyne-setup saves the defaults into any empty setting, and after a restart /plugin → Mnemosyne → configure shows them, ready to edit.
Works on macOS, Linux and Windows. On Windows, ~ means %USERPROFILE%.
The marketplace install has not been tested end to end yet. Every test so far loaded the plugin from a checkout (
claude --plugin-dir <repo>). If the install fails, use that instead and open an issue.
claude plugin update mnemosyne@mnemosyneRestart Claude Code afterwards. The plugin carries its own copy of the engine. From 0.32.0, when an update brings a newer engine build, the next session start installs it in the background, with no /mnemosyne-setup: a notice says the update started, and a second one says it finished or failed. Embeddings keep their state, and the embedding model is not downloaded again; bun install may still fetch engine packages that are missing from Bun's cache, so an update made offline can fail and is tried again at the next session start. If another session is already updating, this one waits for it. A memory_remember or memory_update made during the update waits for it, for up to 60 seconds. If the update fails, the old engine stays in place, recall, forget and stats keep working, and saves are refused with a message that asks for /mnemosyne-setup, which retries the install by hand. Only an older build of the same engine is replaced this way: a newer engine (one a session on an older plugin finds) or a build that cannot be read is left alone, with a notice that asks for /mnemosyne-setup. The first install is always /mnemosyne-setup's, because it downloads the embedding model. After an update, restart any other MCP client (Cursor, Codex) that uses the memory: its proxy reads the engine build once, when it starts.
After updating to 0.8.0, run /mnemosyne-setup before you save anything: it installs engine build 18.12.3-mnemosyne.10. Until it has run, the plugin refuses memory_remember and memory_update with a message that names /mnemosyne-setup, and an outside agent's save through the MCP proxy is refused the same way. Nothing is lost by this: recall, forget and stats keep working on the older engine, and Claude can save again as soon as the setup is done. The refusal is there because the older engine's 24-hour trim deletes every memory that is not labelled imported, and 0.8.0 no longer labels saves that way (see How long a memory lasts); the same refusal applies when the installed engine's build cannot be read. After the setup, restart any other MCP client (Cursor, Codex) that uses the memory: its proxy read the old build when it started and keeps refusing saves until the client is restarted. The 0.8.0 changes that live in the engine need that build: the trim that spares durable memories by scope, the memory kind and expiry, the near-duplicate check, replaces, the audit trail and the first-writer rule. The same build carries what 0.7.0 added (the knowledge tools, local embeddings and /mnemosyne-embed); without it extraction, /mnemosyne-knowledge and the dashboard stay off and say so, and embeddings stay off (see What 0.7.0 does not do yet). Then, if you want meaning-based recall for the memories you already have, run /mnemosyne-embed apply (see Embeddings). What changes for you in 0.8.0 is in What 0.8.0 adds.
On a machine that uses Mnemosyne only through Claude Desktop (or another MCP client) there is no claude plugin update. Since 0.28.0 one command updates the engine, the proxy and the dashboard copy under ~/.claude/mnemosyne, with Bun on the PATH:
bun bin/update.ts # from any checkout of 0.28.0 or later: installs the latest release
bun bin/update.ts --from . # installs this checkout insteadIt downloads the newest vX.Y.Z release into ~/.claude/mnemosyne/update-staging (with git, or as the GitHub tarball when git is missing), runs that release's own updater and removes the folder afterwards. Then:
- Engine. Installed with
bin/install-engine.ts: built next to the old one, checked, then swapped in. Skipped when the build is already current. - Plugin copy.
~/.claude/mnemosyne/plugin(the MCP proxy and the dashboard) is staged inplugin.nextand swapped the same way. - Launcher.
~/.claude/mnemosyne/bin/mnemosyne-mcp.cmd(mnemosyne-mcpelsewhere) is rewritten with the current launcher, keeping your store, activity log, guard mode, vocabulary, model cache and embeddings choice; it is created if missing. - Dashboard. On Windows the always-on dashboard (scheduled task "Mnemosyne dashboard") is stopped for the swap and started again, with no window.
- Report. It prints the plugin version and engine build before and after.
Your store, config.json, vocabulary.json and activity log are never touched. If any step fails, the old engine and plugin copy stay in place and the message says what is installed.
Open chats (Windows). The update runs while Claude Desktop has Mnemosyne connected; open chats keep the old engine until Claude Desktop restarts. A running engine can keep its files locked: if Windows refuses the swap, the update puts the old version back, lists the processes holding it (how many, which pids) and exits 2. Quit Claude Desktop and run it again, or:
--wait 600waits up to 10 minutes for them to end before the swap;--stop-enginesstops them first (open chats lose Mnemosyne until Claude Desktop is restarted). Nothing is stopped without this flag.
Other options: --tag v0.28.0 installs that release, --force reinstalls the same version (or an older one, which is otherwise refused). Exit codes: 0 done or already up to date, 1 failed, 2 refused while engines run, 3 another install holds engine.lock.
Start Menu (Windows). Each update writes Update Mnemosyne next to Open Mnemosyne dashboard (or run bun bin/update.ts --install-shortcut). It runs ~/.claude/mnemosyne/plugin/bin/update.ts hidden and shows the result in a message box (full output in ~/.claude/mnemosyne-ui/update.log). When chats are running it lists them and asks before stopping them. The shortcut needs a plugin copy of 0.28.0 or later, so the first update runs from a checkout.
Restart Claude Desktop (and other MCP clients) afterwards to use the new version.
From the dashboard. The Health tab's System panel shows the version and, once a day, checks GitHub for the latest release: "Mnemosyne v0.28.0 · v0.29.0 available" with an Update… button, or "up to date". On a Claude Code install (from 0.33.0) the same button runs claude plugin update for you and shows its output; when it finishes, restart Claude Code to load the new version, and the next /mnemosyne-ui restarts the dashboard on it. A checkout run with --plugin-dir gets no button. The confirm lists the plugin version and engine build before and after, says that memories are not touched and that open Claude Desktop chats keep the old engine until it restarts; Cancel is the default. Update starts the same bin/update.ts, detached and windowless, and the panel shows its progress (~/.claude/mnemosyne/update-status.json, full output in update.log). When the dashboard comes back through its scheduled task it keeps its address, so the page reloads itself; otherwise the panel says to open it again from the Start Menu. If chats hold the files, the panel says so in plain words; it never stops them.
claude plugin uninstall mnemosyne@mnemosyneThis leaves the engine and your memories in ~/.claude/mnemosyne/. To delete them too (this erases every memory, permanently):
rm -rf ~/.claude/mnemosyneIn a Claude Code session, run once:
/mnemosyne-setup
This installs the Mnemosyne Memory engine into ~/.claude/mnemosyne/engine/ (about 135 MB, and about 280 MB more when embeddings are on) and creates the store at ~/.claude/mnemosyne/store/. The engine comes from the plugin's own copy (the engine/ folder of this repo: the @oh-my-pi/pi-mnemopi 18.12.3 source with a few changes, listed in engine/UPSTREAM.md); only its four @oh-my-pi dependencies are downloaded from npm. Setup builds the new engine next to the old one and swaps it in only once it answers, so a failed setup leaves the old engine working.
If you use the memory from other MCP clients (see below), restart them after setup: a client keeps the engine it started with, and an outside agent's proxy that started before the setup keeps refusing saves until its client is restarted.
| You type | What happens |
|---|---|
remember that we deploy on Fridays |
Claude saves the fact. |
what do you remember about deploys? |
Matching memories are looked up and shown to Claude. |
| the first prompt of a session, and the first after a compaction | Memories matching it are looked up automatically. |
we now deploy on Thursdays |
Claude updates the saved fact in place (memory_update). |
forget the memory about deploys |
Claude finds it and deletes it. |
Claude also saves and recalls on its own: lasting preferences, conventions, decisions. Each save names its kind and passes the save guard. To change a fact, or give it topic tags or another kind, Claude updates it in place with memory_update; it keeps its id and recall history, and no duplicate is saved. A fact that replaces an older one is saved with replaces (see Near-duplicates and replacing a memory).
Recall shows at most 500 characters of each memory. A longer one comes back cut, ending in … and marked truncated: true with its full_length, and Claude reads the whole text with memory_get (from 0.34.0): it takes the id and, for another project's memory, the owner recall gave, and returns the full text with the kind, tags, pinned flag, importance and timestamps. The first-prompt lines and the session-start block mark a memory they cut the same way ([cut: call mcp__mnemosyne__memory_get with this id and owner for the full text]), next to its id and owner.
With embeddings off, recall matches words, not meaning: "deploy Fridays" finds the fact above, "release schedule" does not. With embeddings on (see below), a memory can also be found by what it says.
0.8.0 is about what goes into the memory. Every save now passes one check, every memory says what kind it is and where it came from, project-state notes expire, and tags come from a shared vocabulary. Run /mnemosyne-setup before you save anything after updating: the changes below need engine build 18.12.3-mnemosyne.10. Until you do, saves are refused, not lost: memory_remember and memory_update answer with a message that names /mnemosyne-setup (the same when the engine's build is missing or unreadable), outside agents get a JSON-RPC error (-32000) for the same calls, and recall, forget and stats keep working. The reason is that the older engine's 24-hour trim keeps only memories labelled imported, and 0.8.0 stops labelling saves that way (see How long a memory lasts), so a save made on the older engine would be deleted. Memories saved before the update carry the imported label and are safe. After the setup, restart your other MCP clients: a proxy that started before the setup keeps refusing until its client is restarted. Session start also shows a notice asking for the setup.
A save must name its kind: memory_remember (Claude's tool, and the MCP proxy for outside agents) refuses a call with no kind instead of storing it as unknown, and the refusal does not use up a remember phrase. See Kinds.
Dashboard writes (create, edit, retire and restore on the page), the Review queue's actions, importers for documents and past sessions, session summaries and the sweep that expires unused memories are not in 0.8.0; they came in 0.9.0 (see What 0.9.0 adds and What 0.8.0 did not do).
Every save made through Claude (memory_remember, and memory_update when it changes the text) and every save from an outside agent (through the MCP proxy) is checked before it reaches the engine. Four rules:
| Rule | Looks for | What happens |
|---|---|---|
| G1 secret | an AWS key, GitHub token, sk- API key, Slack token, JWT, private key header, Bearer token, a URL with user:password@, or password, secret, api_key or token set to a value of 12 or more characters |
refused, always |
| G2 size | empty text, or under 15 characters | refused, always |
| G2 size | over 4000 characters | refused, always |
| G2 size | over 1500 characters | warning |
| G3 near-duplicate | a stored memory that says almost the same (cosine score 0.88 or more) | warning |
| G4 chatter | text that reads like dated progress (a date next to released, merged, done, next, overnight or round, or the words today, tonight, this session) |
warning |
The numbers in the table are the defaults; the size and near-duplicate limits, the chatter check and extra "never save" words can be changed in the Settings tab (see Settings). The secret rules and the empty-text block cannot.
A refusal or warning names the rule and says what to do; it never repeats the matched text. A secret refusal says to remove the credential and save the fact without it. A chatter warning suggests saving the note as kind context, which expires. Since 0.8.2 G4 is silent when the save names one of the kinds context, event, instruction, preference and decision (a text-only memory_update is checked with no kind, so pass the kind there too): a rule or decision often quotes the date it was made ("user 2026-10-13: ..."), and that is not progress chatter.
Settings → guardMode picks what a warning does:
strict(the default): the save is refused, with the rules named, until the same call is sent again withconfirm: true. Claude does that only when the memory is wanted as it is.warn: the save goes in at once, the rule ids are stored in the memory's metadata (guard), and the reply shows the warnings.
Secret and size refusals cannot be confirmed in either mode, and any other value of the setting counts as strict. A refused save does not use up a remember phrase, so the retry still counts as yours. An update that changes only tags, importance or kind is not checked, and a clean rewrite of a warned memory clears its flag.
G3 needs embeddings on (it asks the engine's local mnemopi_similar tool, which does not change any memory's recall count). With embeddings off, an engine that is not current, or any engine error, it says nothing and the save goes on. It applies to Claude's saves only: outside agents get G1, G2 and G4. Saving the same text again is the engine's own duplicate check (one row, no warning), not a near-duplicate; and a text that is already stored by a different owner counts as a near-duplicate. When Claude is told about a near-duplicate it is asked to update that memory (memory_update with the id and owner shown) or to save the new one with replaces.
The refusals show in the activity log as a failed call with the rule ids and no text.
The rules are patterns, not understanding. An unusual secret can pass, and some prose is refused: a note such as token: refreshAccessToken is the function that rotates it looks like a secret assignment to G1, and a plain sentence with today saved as a fact warns under G4. Memories saved before 0.8.0 are not checked again. To see what the guard would say about your own store, run bun bin/guard-audit.ts --store ~/.claude/mnemosyne/store from the plugin folder: it opens the database read-only and prints counts per rule (G1, G2, G4) and per verdict, never a memory's text, id or owner. It reads each memory's kind, so G4 counts match what a save of that memory would get.
A save names what kind of memory it is (kind). The rules text Claude gets asks for it, and outside agents see the same kind argument.
| Kind | Plain label |
|---|---|
fact |
fact |
preference |
preference |
instruction |
rule or runbook |
decision |
decision |
context |
project state |
error |
bug note |
artifact |
reference |
event |
session summary |
A save with no kind is refused (memory_remember and an outside agent's save alike; the reply names the eight kinds and nothing is stored), as is a kind that is not in the list; memory_update needs no kind. Memories saved before 0.8.0 stay unknown. memory_update can change a kind. The kinds fact, preference, instruction and decision are protected: saving the same text again never relabels them or gives them an expiry.
Since 0.8.0 a save says honestly who wrote it and through what. The engine's source, trust_tier and veracity fields are filled per write path:
| Path | source | trust tier | veracity |
|---|---|---|---|
| Claude saves on its own | claude-code |
DERIVED |
unknown |
| You, with a remember phrase | user |
STATED |
stated |
| An outside agent through the proxy | mcp:<client name> |
EXTERNAL_WRITE |
unknown |
Memories saved before 0.8.0 keep source imported (that was the only label the old trim allowed); they are not relabelled, and a later retag changes tags and, where the kind is unknown, the kind, nothing else. An outside agent cannot set its own trust tier, veracity or a valid_until date (the proxy drops them); it can ask for an expiry with expires_in_days (1 to 365) on a save. Since 0.9.0 the dashboard's saves and the importers (import:doc, import:session) record their own source too.
If the same text is saved again, the first writer's source and trust tier stay (and the first author and veracity, as before); the scope only widens, and a protected kind keeps its kind. So a fact you stated and an agent later repeats still reads as yours. The reverse also holds: if Claude saved a sentence first and you later say it with a remember phrase, the row keeps claude-code and DERIVED and only its veracity moves to stated.
- Trim by scope. The engine used to delete any memory not labelled
importedafter 24 hours (or past 1000 per owner). That is why every save used to carry that label. Now the trim only touches memories whose scope issession. The plugin always saves with scopeglobal(orchannelin per-project mode), so none of its saves is trimmed, and each can carry its true source. A memory with no scope is also spared. - Project state expires. A
contextnote gets an expiry 14 days after the save (expiry.contextDays). Any save can passexpires_in_days(1 to 365). Recall stops returning an expired memory; it is not deleted. - Restore. The engine can bring a retired or expired memory back (
mnemopi_updatewithrestore: true, for the owner's own row). An outside agent can do it through the proxy:memory_updatewithrestore: trueis passed to the engine, and it restores a retired row that belongs to the agent's own owner (the engine answersnot_foundfor another owner's row, andunchangedfor a row that is not retired). Claude's ownmemory_updatetool does not offerrestore; since 0.9.0 the dashboard's Review tab has a Restore button (see Dashboard writes and the Review tab). - Unused memories. Since 0.9.0 a sweep calls the engine's
mnemopi_expire_unusedonce a day and expires memories of the kindscontext,event,errorandartifactthat nobody recalled forexpireUnusedDays(90 by default). See Unused memories: expireUnusedDays.
When a close memory exists, Claude is told to update it. When the new text supersedes an old one, Claude saves it with replaces (the old memory's id, and its owner from recall if it belongs to another project). After the new memory is saved, the old one is retired: recall stops returning it, nothing is deleted, and it can be restored through the engine. If the save is refused, or the old id cannot be found, nothing is retired. replaces is a Claude tool only; outside agents cannot retire a memory.
Each retire, restore, text update, attest and expiry writes one row to the engine's memory_validations table: which memory, who did it, what was done and when. The row never holds memory text. The "who" is claude-code for Claude's own text updates and for retires made through replaces, mcp:<client name> for an outside agent's text update, dashboard for a retire, restore or edit made on the dashboard, expire-sweep for the daily sweep's expiries, and unknown otherwise (a restore made through the proxy is recorded as unknown). The engine keeps only the last 3 rows per memory, so it is the last three actions, not a full history. Forgetting a memory deletes its rows. Since 0.9.0 the Review tab's "Not live" list shows each row's last audit rows (action, who, when).
bun bin/memories.ts --store <store folder> backup (run from the plugin folder) copies the database to ~/.claude/mnemosyne/backups/mnemopi-<time>.db with VACUUM INTO, owner-only, checks the copy, and keeps the newest 10 (storage.backupsKeep; the Settings tab also has a Back up now button). It stops without copying when the disk has less free space than twice the store. You rarely need to run it: every command that changes the store in bulk backs it up first and stops if the backup fails, saying that no memory was changed. These are /mnemosyne-tags apply (a tag run and a retag), /mnemosyne-knowledge apply and /mnemosyne-embed apply or rebuild. Try each on a copy of the store first, as before.
Tags now follow one format, and Claude, the tagger and outside agents all use it:
- A tag is
valueorns:value.nsisentity,agentorasset(for exampleagent:codex); any other prefix is just punctuation inside the value. valueis lowercase, hyphen-separated, 1 to 31 characters:Code Reviewbecomescode-review.- A tag that is only the project's own name is dropped, a memory keeps at most 5 tags (
tags.maxPerMemory), and duplicates are removed. - An alias in the vocabulary folds into its tag (
CRintocode-review).
Tags already stored are not rewritten when read; they change only when you retag or update a memory.
The shared vocabulary is ~/.claude/mnemosyne/vocabulary.json (owner-only). Create it once from the tags your memories already use:
bun bin/vocab.ts --store ~/.claude/mnemosyne/store initIt starts with the agents agent:claude-code, agent:codex, agent:opencode and agent:agy, plus every tag that is on at least 2 memories once names are folded (never a project name). init will not overwrite an existing file unless you pass --force, which keeps the tags and aliases you added by hand. Then list prints it, admit <tag> adds a tag and alias <from> <to> adds an alias. Nothing reads the memories' text to build it. /mnemosyne-tags prints the command with the plugin's path when the file is missing; fill in your store folder.
The tagger is shown up to 150 admitted tags by namespace, asked for 2 to 5 tags and one kind, and may propose at most one new tag per memory. A proposed new tag is counted, not written; once /mnemosyne-tags apply has written it for 3 memories it is admitted. A vocabulary file with a bad entry is ignored whole without a message about the file: alias folding is off, and the tag dry runs then say there is no vocabulary (although a file exists) until it is fixed.
/mnemosyne-tags retag re-proposes tags and a kind for every memory, not only the untagged ones (see Topic tags).
Nothing new leaves it in 0.8.0. The guard, the near-duplicate check, the vocabulary, the backup and the audit trail all run on your machine. Memory text goes to one place, as before: the extractionModel (Haiku by default), through Claude Code's own model channel, for the tag dry runs, the retag dry run and knowledge extraction. A retag dry run sends the text of every memory (a plain tag run sends only the untagged ones), so it costs one model call per memory. extractionModel set to off stops all of it.
0.9.0 adds two more things that go to the same model (Haiku by default) and nothing else: /mnemosyne-import session sends the redacted conversation of the session you name, and, only when sessionSummary is on, the background summary of an ended session sends that session's redacted conversation. Both are described in Importing documents and sessions and Session summaries and pinned memories. Everything else 0.9.0 adds (dashboard writes, the Review tab, the vocabulary tab, /mnemosyne-import doc, the expiry sweep and log pruning) runs on your machine and sends nothing.
0.28.0: when the Health tab opens, the dashboard asks GitHub (api.github.com and raw.githubusercontent.com) for the latest release tag and its engine build, at most once a day, with no memory data. bin/update.ts downloads releases from github.com. Nothing else changes.
A .env file in the folder where an agent runs used to be able to point the engine at a different bank, and so a different database folder. The engine environment, both mnemosyne-mcp wrappers and the installer now pin the bank to default, so a hostile or stray .env cannot divert saves.
0.8.0 left five things for 0.9.0, all now done (see What 0.9.0 adds): dashboard writes and restore, a tag vocabulary page, the importers, session summaries, and the expiry sweep, plus the Health tab's "unused by design" labels for the engine's seven tables that stay empty on purpose (episodic_memory, consolidation_log, gists, facts, graph_edges, triples, scratchpad and their search indexes; see DESIGN.md). Two limits are still true:
- A tag update whose tags all clean away (only punctuation or blanks such as
["!!!"], or only the project's own name) is refused since 0.8.2, from Claude, from an outside agent and from the dashboard alike, and the stored tags stay; sendtags: []to remove tags on purpose. - Embeddings, and so G3, are tested on macOS on Apple Silicon only (see Engine logs and platforms).
0.9.0 is about working with what is in the memory: you can write and review memories on the dashboard, import documents and past sessions, carry the last session's summary and your pinned memories into the next session, and let unused project notes expire. It needs no new engine build beyond 0.8.0's. The importers are described in Importing documents and sessions below; the rest follows here.
The dashboard was read-only apart from Forget. It now writes, through the same guards as every other save.
- New memory and Edit. The Memories tab has a New memory button, and a memory's detail sheet has Edit. The form takes the text, the kind (required), tags (picked from the vocabulary), importance, an expiry in days (1 to 365; Edit can remove an expiry), Pinned (Edit only) and, for a new memory, where it belongs:
globalor a project already in the store. Acontextnote gets its 14-day expiry unless you set another. - The save guard runs first, with the settings in
config.json. A blocked text (a secret, or empty, too short or too long text) is refused with the rule names and cannot be saved. A warning (chatter, or a near-duplicate found by meaning when embeddings are on) is shown and offers "Save anyway", which sends the same text again withconfirm: true; a confirmed save is flagged inmetadata.guard. The page shows a save as done only after the server said so. A body over 24 KB is refused (the other write routes take 4096 bytes). - A dashboard save is recorded as source
dashboard, trust tier STATED, author type human and veracitystated. Edits go through the engine as the memory's own owner and are recorded in the audit trail asdashboard. Tags that all clean away are refused rather than read as "remove the tags". - Forget stays in the memory detail sheet only (a first button, then a confirm that cannot be undone). The Review tab never offers it.
The Review tab keeps its four lists of the newest memories (never recalled, no tags, not recalled in 30 days, importance under 0.4) and adds two:
- Retire candidates: live memories the server flags, each with its reasons: marked superseded (the capital words SUPERSEDED, HISTORICAL or DEPRECATED, or "possibly stale"), a near-duplicate of another live memory (by meaning, so only with embeddings), a dated status note, a guard flag, or never recalled and older than 30 days. A pinned memory is never listed. The most reasons come first, then the oldest; at most 200 are shown, with the total.
- Not live: the memories that are no longer recalled, each with its state, the date it stopped, its last audit rows (what was done, by whom, when; never text) and a Restore button.
Retire works on one memory at a time, behind a confirm that names the memory and offers an optional "replaced by" id. A retired memory is not deleted: it stops being recalled and Restore brings it back. Nothing is retired in bulk.
Every memory has one of four states, shown in the Memories tab and the Review tab (the map draws them too): live; retired (retired by hand or by replaces); expired (ended by the daily sweep); lapsed (its own expiry date passed, such as a context note after 14 days). The states retired and expired come from the audit trail, which keeps only the last 3 actions per memory, and the engine writes a trail row only when the caller names itself; a memory retired with no replacement and no such name, or whose trail rows were pushed out, reads as lapsed. In the Memories tab only live memories show unless you tick Show retired (off by default; the choice is kept in your browser only), and the others are dimmed with their state.
The Vocabulary tab lists the shared tags by namespace with their use counts, and the candidates the tagger proposed, each with Admit and "Alias to". It writes through the same code as bun bin/vocab.ts admit and alias, and says so when there is no vocabulary yet (run /mnemosyne-tags once to make one). The Health tab now labels the engine's tables that stay empty on purpose as "unused by design", with the reason, instead of "empty".
Security is as before: every write is a POST behind the token, the Host and Origin checks, a JSON body and a size cap, and the server writes through the engine, not the database file. The activity log gets a remember, update, retire or restore line from the dashboard (origin ui) with ids and rule names, never memory text.
- Pinned memories. A memory marked pinned is never a retirement candidate, never expires on its own and never goes in the sweep. Claude can pin with
memory_update(pinned: true;falseunpins), and the dashboard's Edit form has the same checkbox. Outside agents cannot pin (the proxy takes nopinned). - The first-prompt block. On a session's first prompt, before the usual related-memories recall, Claude is given "what we were doing last time, and pinned memories": the newest session summary of this owner, then up to 5 pinned memories (fewer if
recall.firstPromptCountis lower) and at most 2000 characters of them, the last one cut with an ellipsis and a cut mark that namesmemory_get, so Claude can read the rest by its id. A memory shown there is left out of the related-memories list so it appears once. The block is read from the store: no model call, nothing leaves your machine. - Session summaries are off by default (
sessionSummaryin the settings). When on, a session ending (exit,/clearor resume) leaves a small marker in~/.claude/mnemosyne/session-pending/: the session id, its owner and its transcript path, no text. On a later session's first prompt, after Claude's reply and in the background, the marker is claimed and the session's conversation (your messages and Claude's text replies only, key and token shapes replaced by[redacted], the newest 30,000 characters) goes to theextractionModel(Haiku by default) through Claude Code's own model channel. Haiku's note, at most 600 characters, is checked by the save guard and saved as oneeventmemory with sourceimport:session, kept 30 days. A session of fewer than 2 turns is skipped, a reply ofNONEsaves nothing, at most 3 summaries are made per run, and a session is summarised once (a summary that was retired or expired is not made again).extractionModelset tooffstops it.
expireUnusedDays (90 by default) sets how long a memory can go without being recalled before the daily sweep expires it.
- What it expires: only memories of the kinds
context(project state),event(including session summaries),error(bug notes) andartifact(references), that have no end date yet, are not pinned and were neither saved nor recalled for that many days (a memory's last use is the later of the two). A memory with an unreadablemetadataor no scope is spared. Afact,preference,instruction,decisionorunknownmemory is never touched, whatever the number, and no setting can widen the list. - It never deletes. Expiring gives the memory an end date, so it stops being recalled; the Review tab's Not live list shows it as
expiredand Restore brings it back. - The number is 30 to 3650;
0turns the sweep off; 1 to 29 (or anything that is not a whole number) is refused and no sweep runs. - When: at most once a day (a marker file,
expire-sweep.json, in the Mnemosyne root), started in the background by a session's first prompt, for the current owner's own memories only (this project's inper-projectscope, the global ones inglobalscope), and at most 50 memories per run, longest unused first; the rest wait for the next day. The store is backed up first, and if the backup fails nothing is expired. Each expiry is recorded in the audit trail asexpire-sweep, and the activity log gets oneexpireline with counts and ids. - Preview it, changing nothing:
bun bin/memories.ts --store ~/.claude/mnemosyne/store expire(add--days Nor--owner <owner>). It lists what would expire and prints the exact--applycommand to run it by hand.
- Engine logs. The engine writes one
omp.*.logfile per run into~/.claude/mnemosyne/model-cache/logsand never trims them. Session start and/mnemosyne-setupnow delete, in the background and without any message, theomp.*.logfiles in that folder that are older than 7 days, always keeping the newest 20. Only regular files with that name directly in that folder are touched; a linked folder or file is left alone, and a run removes at most 1000. - Platform notes.
/mnemosyne-setupends its reply by saying that embeddings are tested on macOS Apple Silicon only and that Linux and Windows are untested. On an Intel Mac (darwin x64) it also says embeddings are not supported there, and leaves them off even whenembeddingsison.
Two importers. Each starts with a dry run that changes no memory and shows counts first; apply then backs the store up (the same backup as /mnemosyne-tags apply, and it stops if the backup fails), checks every candidate again with the save guard and the near-duplicate check, and saves what passes. A candidate the guard blocks (a secret, text that is too short or too long) is never saved, and its text is never logged or written to a file. In strict guard mode a candidate with a warning is saved only by /mnemosyne-import apply confirm; in warn mode it is saved and flagged in metadata.guard. The proposals file is ~/.claude/mnemosyne/import-proposed.json (mode 0600): paths, headings, hashes, rule ids and verdicts, never the text.
/mnemosyne-import doc <path> takes a .md or .txt file under 1 MB. It is split at headings into chunks of at most 1500 characters, and a file that splits into more than 200 chunks is refused with its count, so one document cannot flood the store (split the file, or keep the part worth remembering). Each chunk is saved as kind artifact with source import:doc, trust tier IMPORTED and veracity imported, and metadata.origin holding the path, the chunk's sha256 and its heading. Importing an unchanged file again adds nothing. When a chunk has changed, the new text is saved and the old row is retired, not deleted, so it can be restored. Text the store already holds exactly is skipped. With scope per-project, an import sees and replaces only the current project's earlier rows: another project's copy of the same file is never touched, and each project gets its own.
/mnemosyne-import session <id | last> reads one finished session of this project from ~/.claude/projects (last is the newest earlier one, never the running session, and is refused when the running session's id is unknown; an id is looked up in this project's folder only, never in another project's) and asks the model for at most 5 durable memories: preferences, decisions, rules, runbooks and lasting facts, never task progress. Each one is checked like any save, shown in the dry run, and saved by apply with source import:session, trust tier IMPORTED and veracity imported. The memories themselves stay in the Claude Code process between the dry run and the apply (their text is never written to a file), so an apply after a restart asks for a new dry run.
What leaves your machine: for a session import, the conversation text (your messages and Claude's text replies only; no tool calls, tool results or thinking), with key and token shapes replaced by [redacted] and cut to the newest 30,000 characters, goes to the extractionModel (Haiku by default) through Claude Code's own model channel, as the tag dry runs already do. extractionModel set to off stops it, and /mnemosyne-import session then says so. A document import sends nothing anywhere. Neither importer extracts knowledge from what it saves (no model call per chunk); run /mnemosyne-knowledge when you want that.
Claude's memory_update also takes pinned: true pins a memory so it is never a retirement candidate and never expires on its own, false unpins it. Outside agents cannot pin.
A ranking change is only an improvement if it finds the right memories more often. The evaluation harness replays your own real recall queries and measures that, with nothing in the store changing. From the plugin folder:
bun bin/eval.ts capture # the real recall queries from your activity log
bun bin/eval.ts run --mode hybrid --name base # replay them all (keyword or hybrid, --weights v,k,i to try others)
bun bin/eval.ts gold-draft # the most-asked queries with today's top hits, to label
bun bin/eval.ts compare <run a> <run b> # how much changed, and recall@k / MRR on your labelled set
How it stays safe and private:
- A replay runs on a snapshot copy of the store and never counts a recall.
- Everything it writes is under
~/.claude/mnemosyne/eval/, private to you: the queries, your labels (gold.json, made fromgold.draft.jsonby copying the right ids intoexpect) and the runs.
Since 0.7.0 the engine can also compare memories by meaning, using a small local model, BAAI/bge-small-en-v1.5 (384 dimensions, about 63 MB). It runs on your machine. The engine's route to a remote embedding API is closed: it needs an engine-only opt-in (MNEMOPI_EMBEDDINGS_API_OPT_IN) that the plugin pins to 0, so memory text is never sent to an embedding API. The API URL and key, OPENROUTER_BASE_URL, OPENROUTER_API_KEY and OPENAI_API_KEY are also emptied, and the model name, the download host and the cache folder are pinned to fixed values. A .env file or a shell variable cannot send text to an embedding API, change the model or move the model cache.
- Where it comes from.
/mnemosyne-setupdownloads the model once fromhuggingface.co(theQdrant/bge-small-en-v1.5-onnx-Qrepository; setup prints the host and size first). The model is cached under~/.claude/mnemosyne/model-cache/, not the shared~/.ompcache, and is only ever downloaded during setup. After updating to this version, run/mnemosyne-setuponce to install the engine build18.12.3-mnemosyne.11and fetch the model; until then embeddings stay off and recall is keyword-only. A later update that brings a new engine build also needs a fresh/mnemosyne-setup. - The option.
embeddingsin the plugin settings isautoby default: on only on macOS on Apple Silicon (the tested platform), off elsewhere unless you set it toon.offkeeps recall keyword-only. Changing it needs/mnemosyne-setup(to download the model) and a dashboard restart. Windows and Linux are not verified. - The install check. With embeddings on, setup proves the model works before it swaps the engine in: it saves and recalls a test memory in a throwaway store and requires a stored vector for it. If the model cannot produce a vector (for example the download failed), setup refuses to swap the engine, keeps the old one, writes no setup marker, and says embeddings are off. Then set
embeddingstooffand run/mnemosyne-setupagain, or run/mnemosyne-setupagain when you are online. - The cost. Embedding a memory adds about 0.3 s per engine call that embeds, and each engine process that embeds uses about 0.4 GB of memory (peak, from the EMB research). The engine install grows by about 280 MB (fastembed and its ONNX runtime, which overrides onnxruntime-node to 1.26.0 so the process no longer aborts on exit under Bun). On Linux x64, setup also skips the GPU (CUDA) download from
api.nuget.orgthat onnxruntime-node would otherwise try on every install: only those CUDA/TensorRT files are skipped, the CPU runtime ships inside the package. - The backfill cost. Filling the vectors of an existing store costs far more than one save or recall: on a copy of a real store,
/mnemosyne-embed applytook 27 s and about 3 GB of memory (peak) for 139 memories, and 198 s and about 4.2 GB for 695 memories — about 0.2 to 0.3 s per memory. It runs as one engine process with a 10-minute (600 s) limit, so a store of roughly 2000 memories or more may stop at the limit. Run/mnemosyne-embed applyagain to continue: progress is saved per batch. Close memory-heavy apps before running it on a big store. - How recall changes. A memory's keyword score is blended with its meaning score: for the working-memory tiers the score becomes 0.8 × the keyword score plus 0.2 × the similarity, and a candidate with too little word overlap still surfaces when its similarity is at least 0.65. Recall still answers within its timeout.
- Older memories. Memories saved before embeddings were on have no vector until you fill them:
/mnemosyne-embed
That is a dry run: it counts the memories with no vector. Then /mnemosyne-embed apply fills them, in one engine process, and changes no memory text (only its vector). Running it again adds nothing; it counts again afterwards and reports an error if any memory is still missing a vector. If you changed the model the vectors are refused unless you pass /mnemosyne-embed rebuild, which first checks that the model loads, then wipes and rebuilds every vector.
/mnemosyne-ui
Starts a small local web page for your memories and opens it in your default browser. The reply shows the full link, with its key after #, and the link is on the clipboard too, so you can open it in another browser.
The header shows how many memories are in how many projects (global is counted apart: "in 13 projects + global"), and a connection pill: teal "Local · live" while the server answers, amber "Reconnecting…" when it has stopped (an amber strip under the header counts down to the next check, offers Try now, and the page's data is dimmed as out of date), and "Link expired" when the server was restarted (a card then says how to get a fresh link with /mnemosyne-ui, with a Copy button; nothing is lost). The plugin and engine versions are behind the ⓘ button. The page has nine tabs and opens on Map:
-
Map: the solar-system Galaxy view: the sun and writer nodules, planets and crafts with their styles, land, moons, asteroids, belts, black holes, satellites, comets and beams, clouds and ice cap (see The galaxy). Arrange by, Find, the Bodies strip, the Details panel, and the live events; Timeline and Meaning unchanged. Two new files in the Mnemosyne root:
activity-daily.json(counts only) andprojects.json(project names and the remote's host only); a project is recorded the next time a Claude session starts in it. -
Memories: every memory, one line each:
- Each row shows the kind as a glyph, the text with its lead in bold and up to three tags under it, the project as a chip (global in gold), the reach, importance as five pips, a recall bar, and the saved time.
- Reach follows the map's colours: all (a gold dot: the global owner, every project reads it), shared (a dashed blue ring: a project's memory every project can recall) and project.
- One filter bar: text, tag or id (
/goes there), the project, the sort (newest, most recalled, never recalled), and Live / Not live / All with counts. Retired, expired and lapsed memories show in ash with their state as a pill (see Dashboard writes and the Review tab). - Counted chips below it filter by reach and kind. A chip with nothing to show is disabled.
- Grouping: rows are grouped by the day they were saved. The title counts live, not live, global and shared memories and the projects.
- With nothing to show, the tab names the filters that are on and offers to clear them.
- Keys: J and K move, Enter opens, Esc closes, N starts a new memory.
A memory opens beside the list on a wide screen (1200 px and up), else in a panel from the bottom. The panel holds the kind, owner and reach, the text, four numbers (importance, recalls, saved, state), the reach in words, Edit, and a Forget button that asks before it deletes. Knowledge and Search open memories in the same panel.
GET /api/memoriestakesreach=all|shared|projectandkind=, and returnsfacets(the counts by reach and by kind under the other filters) and each row'smemory_type. -
Activity: the activity log, live, one plain sentence per call, with the app and the session that made it; click a line for its details (see Activity log).
- Tiles: recalls today (split into Claude, other apps and the dashboard), saves, updates, retires and failed calls, with a link to the newest failure.
- Today by hour: a small chart stacked by action; click an hour to jump to it.
- Colours follow the map's live trails: recall gold, save blue, update green, forget red, retire an ash ring.
- Filters: action chips with counts, From (Claude Code, other apps, dashboard), one app, and a project.
- Grouping: lines sit under day headings, the newest day open. Runs of the same call (three or more within a minute of each other) fold into one row, with the queries and projects. A script-paced burst can hide its app. Turn this off with Group bursts.
- A failed line shows its error, with Try again (for a recall) and Copy error.
-
Search: the same recall Claude uses, top 20, in rank order. Searching here does not change any memory's recall count.
- As seen from a project shows what Claude would receive there: its own memories plus the shared and global ones. Every project searches each project as itself.
- Match by Keyword, or Keyword + meaning when embeddings are on. Your last five queries show as chips.
- Each hit shows its rank and score ring, the kind, project chip and reach, whether it matched on words, meaning or both, the date and recalls, the text with the query words marked, and Open and Map. A dashed line marks where scores drop under 0.50.
- Sort by rank, newest or most recalled, and filter by project, kind or match type from the side panel, without searching again.
- Keys:
/on most tabs opens Search; J and K move, Enter opens, M shows the hit on the map.
-
Knowledge: the facts, preferences, instructions, dated events and relations that extraction pulled out of your memories (see The Knowledge and Health tabs).
- Tiles count the items (and the memories they came from), those recalled this week, those gone quiet (30+ days) and those never used.
- Kinds are tabs with the memory-kind shapes; keys 1 to 5 switch.
- Rows are grouped by project, then topic (or Flat). A project row folds; it shows the reach, the count, the recall range and how recently it was used. Top topics filter with one click, and All / Gone quiet / Never used narrow the list.
- A row opens its source: the memory it was mined from, the other items from that memory, Open memory and Show on map.
- Source recalls is how often the memory an item came from was recalled. A relation reads as subject, link and object with five confidence pips; an instruction shows Active or Retired.
-
Review: memories to look at again: never recalled, no tags, not recalled in 30 days, or low importance, then Retire candidates with their reasons and a Not live list with Restore. Each row has Open and a one-at-a-time Retire; Forget is not here.
- Queues are split into "Needs a look" and "Clear". A banner says why the memories are listed, and chips under each row give its reasons.
- New memories (saved in the last 7 days) are marked new; turn off Show new ones to hide them from Never recalled until their grace period ends.
- Keep hides a memory you checked from the queues for 30 days (in this browser). This pass counts what you kept and retired, with Undo and Restore.
- Grouping: rows are grouped by project when a queue spans several.
- Keys: J, K, O, Space (keep), R (retire, then Enter), U (undo).
-
Health: how much of your memory is used, how much has never been recalled, and which features are switched on.
- What needs action first: a verdict, counts by state, and up to three findings with one button each (for example, no agent has recalled anything over the days the log covers: check that the memory tools are on). Recalls from other MCP agents such as Codex count as use, so working in another agent is not flagged.
- System: the version, store, server, activity log and mode, beside the board.
- Tiles and feature rows show their state by a mark whose shape carries the meaning; provenance is marked "by design".
- The recalls chart stacks Claude Code, other apps and the dashboard per day.
- Projects show as bars (global in gold), and storage as chips. Never recalled and recalled at least once both count live memories, and global is never counted as a project (the header reads "in 13 projects + global").
-
Vocabulary: the shared tags by namespace with counts, and the candidates to Admit or Alias.
- Groups: Topics, Projects (tags named like a project on the map), Entities, Assets and Agents, each tag with a usage bar. An unused tag is shown in ash.
- Find and sort: a find box (
/) and Most used / A–Z. Tiles count admitted, waiting and unused tags and tag uses, and a ribbon shows where the uses fall. Hover a tag to open its memories. - Candidates sit in a panel with ticks toward the auto-admit count. When one looks like a spelling of an admitted tag, "Count as " is offered first.
-
Settings: every setting of the memory system with its value and default, where you change, reset and back up (see Settings). It writes to
~/.claude/mnemosyne/config.jsonand, through theclaudecommand, to the plugin options.- Rows: each row reads across: what it is, the control, and when and where it applies.
- Changing a value: a change is checked as you type (the warn length must stay below the max length), Save turns on only for a valid change, and Cancel puts it back. A row changed from its default is marked, and numbers show where the default sits.
- Finding a setting: a group rail with counts, and quick filters: Changed, Next session, Plugin options and Read-only.
- Plugin options this page cannot write show
/configwith Copy, and read-only facts fold into one line. - Reset all sits in a danger zone and lists what it would change.
Every tab but the Map shares one content width.
The Theme button in the header switches the whole dashboard between Auto (follows your system), Light and Dark, and remembers your choice. The header shows the plugin version the dashboard runs (for example v0.8.0) beside the engine's. The block at the bottom of the Health tab shows the store, the server process and whether the activity log is on.
One dashboard runs at a time: running /mnemosyne-ui again, in this session or another, hands over the same link. It keeps running until every session that opened or reused it has ended (/clear counts as an end), so a session ending does not take it away from another one; a session that crashed does not hold it up. Run /mnemosyne-ui again for a fresh one.
Security. The server binds 127.0.0.1 only, so nothing outside your machine can reach it. It makes a new random token each time it starts; the link carries it after #, which a browser never sends, and the page sends it only in a request header. Without it, every data route answers 401. The page loads no outside files, and a strict content security policy blocks inline script and style. The server reads the database read-only: a write (create, edit, retire, restore, forget) or a search goes through the engine, as Claude's would. Anyone who can run programs as your user can read the token from ~/.claude/mnemosyne/ui.json, as they could read the database itself.
The dashboard and the activity log are tested on macOS and Linux, not on Windows.
Working on the dashboard. From a checkout of this repo, bun run dev:ui starts a second dashboard straight from the checkout on 127.0.0.1:47175 (another port with MNEMOSYNE_UI_PORT) and opens it. It reads the same store as the installed one, which keeps running, and keeps its own ui.json under ~/.claude/mnemosyne/dev-ui. The page reloads itself within a second when a file in ui/ changes, and an edit to ui/server.ts restarts the server on the same port and token, so there is no plugin update between edits. The footer shows "Mode: dev". Ctrl-C stops it.
The Map tab draws the solar system of your memories: the sun is the store, writer nodules orbit it, each project is a planet or a craft, and each memory is an asteroid in a swarm around its project's kind moon. The Timeline layout is unchanged; meaning is the nebula under the Galaxy (0.29.0).
The mapping (how a thing on the map comes from the store):
| On the map | From the store |
|---|---|
| Sun | the memory store (all live memories) |
| Writer nodules | the writers (claude-code, dashboard, …), sized by count |
| Planets (gas 30+, ice 10–29, rocky, dwarf <3) | git repos, styled by live memory count |
Crafts (space station at 50+ memories, starship below; the Settings tab's map.stationMin) |
plain folders (no git repo) |
| Land masses / bands | a project's tags (top 12 by uses) |
| Moons | memory kinds present (one per kind; none for dwarfs and crafts, never for kind unknown; at most 8; size = count) |
| Asteroids | that kind's memories (size = importance, brightness = recalls, colour = writer or recalls or age) |
| Planet asteroid belt | memories of kind unknown (old untyped rows; a dwarf's memories) |
| Sun asteroid belt | the true global memories (owner global, saved for every project), gold; the card's "Global (sun belt)" |
| Gold beads (tethers) | a string of gold beads on an arc above the orbit plane, from a global memory to each project it is close to in meaning (up to 3, from the same pairs as the black holes, so map.linkScore applies; brighter = closer). Folded to a short stub at both ends until you hover or open that project; then its strings draw in full |
| Probes | a project's memory shared with every project (scope global): a small blue probe circling its planet, station or starship on a dashed blue ring just outside the moons (up to 48 per body); the memory also stays in its kind's swarm; hover names it, a click selects it; the card's "Probes (shared from projects)" |
| Outer galaxy belt | retired, expired, lapsed memories, in ash grey (faded; Restore brings them back) |
| Black-hole pair | similar memories across two projects: two black holes joined by twin violet strands with an "N pairs" label (2+ pairs, cosine ≥ 0.80 by default; the Settings tab's map.linkMinPairs and map.linkScore) |
| Satellites | the knowledge items mined from the selected memory |
| Recall comet / save comet | a live recall (planet → sun nodule, gold) / a live save (sun nodule → planet, blue) |
| Update beam / forget laser | a live update (green beam) / a live forget (red laser; the memory fades) |
| Clouds / ice cap | needs review / idle 30+ days (clouds when more than a quarter of live memories are kind unknown) |
Motion and focus. The map turns slowly, and while a tooltip is open (and for 2 seconds after) nothing turns, so a memory you hover stays under the pointer long enough to read. The project you hover (its planet, its memory dots or its probes), or the one you opened, is in focus: a white ring marks it, its gold tethers draw in full, its black-hole bonds brighten and keep their "N pairs" label, and the other projects' bonds and labels step back. A planet's tooltip also says how many of its memories are shared with every project (probes) and how many global memories are close to it, with the best score.
Arrange by re-orders the planets' orbits: activity (busier = closer, the default), size, age, or recalls. Each keeps its angle. At most 10 orbits. Find dims every body except those whose tag, project or label holds the text. The Bodies strip lists what matches. The Details panel shows a body's facts and is read-only; edit, retire and restore a memory from the Memories and Review tabs. A memory's text, tags, writer, provenance, dates, state, pin, history and similar memories all show there.
The meaning nebula (0.29.0). With embeddings on and vectors filled (see Embeddings), the Galaxy draws a nebula under the orbit plane: every live memory that has a vector is a glowing speck placed by its meaning (the engine's vectors laid out with UMAP), so memories that say similar things sit near each other, whatever project they belong to. Specks that bunch together form clouds, each in its own colour and named after the tags most typical of it ("homelab · 7of9"). Hover a memory on a planet, or a speck, and a thread joins the two while its closest memories light up; click a speck to open its memory on its planet. The view from Below looks up through the nebula at the planets; from above it dims so the planets stay readable. Each cloud is an electric storm that flickers and now and then flashes, with a bolt crackling between two of its memories. Glowing ties rise from each cloud to the projects that hold its memories, each with a crackling ball of electricity travelling up and back (brighter for a bigger share): hover a planet to see the topics it feeds, a speck to see the projects that share its topic; the tooltips list them too. Leave the map alone for a few seconds and specks show their memories' first lines, one cloud at a time; any mouse or wheel input clears them. The "Meaning nebula" and "Nebula captions" switches in the Show card turn them off. It needs at least 10 memories with a vector. The layout is worked out once and cached in ~/.claude/mnemosyne/meaning.json (owner-only); if it is not ready within 8 seconds the Galaxy shows without the nebula and it appears on the next load. Before 0.29.0 this was a separate Meaning layout; the Layout card now has Galaxy and Timeline, and a saved Meaning choice opens the Galaxy.
Simulate. The Demo button opens "Simulate a recall" and "Simulate a write", which play demo events on the map so you can see the live view on a quiet day. They are demo only: they make no request, never touch the store and never write a line to the activity log. Each press plays one event; nothing repeats on its own.
Knowledge lists what extraction pulled out of your memories (see Knowledge extraction), one table per kind: Facts, Preferences, Instructions, Timeline and Relations. A row shows the item, its topic (the memory's first tag), its project, the memory it came from, its status for instructions and relations (an instruction is active or retired, a relation shows its confidence), how many times that memory was recalled and when it was last used. A bar above the table filters by project, finds text, sorts, and shows only the items never used. Click a row to open its memory; Map ↗ selects the source star on the Map tab. A notice at the top says when extraction is off, or when the store holds no items yet.
Health shows four numbers (memories, never recalled, recalls in the log, store size), the share of memories that have topic tags, knowledge, a recorded author and at least one recall, recalls per day over the last 14 days, and the projects; the days before the activity log starts are greyed out. The never-recalled list moved to the Review tab. Storage folds into one line and filters its tables by All, Has data or Empty. The "Embeddings (meaning search)" bar counts the memories that have a vector; while there are none it reads 0 and shows the hint "Turn on embeddings, then run /mnemosyne-embed apply".
Claude is asked to add 2 to 5 topic tags when it saves a new memory, in the format and from the vocabulary described in Tags: vocabulary and namespaces. To tag the memories you saved before, run:
/mnemosyne-tags
This is a dry run: it sends the text of each untagged memory to the extractionModel (Haiku by default), shows the tags it proposes, and saves the proposals (ids, tags and a hash of each text, never the text) to ~/.claude/mnemosyne/tags-proposed.json. It changes no memory. Read the proposals, then write them:
/mnemosyne-tags apply
apply makes no model call. It backs the store up first (see Backups) and stops if that fails. It writes the saved tags through memory_update, and skips any memory that was deleted, already got tags, or whose text changed since the dry run.
To bring the tags you already have onto the vocabulary, create the vocabulary file once (see above) and run:
/mnemosyne-tags retag
Another dry run, now for every memory, tagged or not: it proposes tags from the vocabulary and a kind, and shows each memory's current tags beside the proposal. It sends the text of every memory to the model, a few at a time, and waits and retries when the model channel is rate-limited. /mnemosyne-tags apply then backs up, replaces the tags only on memories unchanged since the dry run, and sets the proposed kind only on a memory whose kind is unknown. It never changes text, importance, source or trust tier.
After each save, and after an update that changes a memory's text, Mnemosyne asks the extractionModel (Haiku by default) to pull out the facts, preferences, instructions, dated events and relations in that memory. The save returns at once; the call runs in the background. The engine's own prompt builds the request, and the engine checks and stores the answer. Every run is one line in the activity log, action extract.
Only the memory's text goes to the model, through Claude Code's own model channel; nothing leaves your machine for another service. One model call per save is the cost. The setting extractionModel picks the model (a short name such as haiku, the default, or a full model id). It also drives /mnemosyne-tags. Set it to off and nothing is extracted, no model call is made, and /mnemosyne-tags and /mnemosyne-knowledge say that extraction is off.
If Claude Code cancels the background call (Esc on that turn, or the session ends), a small pending file under ~/.claude/mnemosyne/knowledge-pending/ usually records which memory needs it. The file is written by two background steps that start after the save's reply, so a save made right before the session ends may leave no pending file and no extract line at all. Nothing is lost for good: /mnemosyne-knowledge also lists every memory with no knowledge, whether or not it has a pending file, and its dry run is the check. Finish the pending ones, and extract the older memories, with:
/mnemosyne-knowledge
That is a dry run: it counts, per project, the memories with no knowledge and the pending entries, and changes nothing. Then:
/mnemosyne-knowledge apply
apply finishes the pending entries first (each from its current text and hash, so a memory you edited is redone, never stored stale), then extracts the older memories with no knowledge. It makes one model call per memory (a few at a time), batches the engine work per project, 40 memories at a time, and never edits or deletes a memory. A memory whose extraction found nothing is noted in ~/.claude/mnemosyne/knowledge-empty.json and skipped while its text is unchanged. Only apply makes that note: a live extraction that finds nothing is not recorded as tried, so the first apply after many such saves may make one more model call for each of those memories. Saving text that already exists (a dedupe) starts a fresh extraction for the same memory and replaces its knowledge rows with equal ones; that costs one model call and changes nothing you see.
Outside agents' saves (Codex, Cursor and other clients, through the MCP proxy) get no automatic extraction: the proxy has no model channel. Run /mnemosyne-knowledge apply to cover them.
Try apply on a copy of the store first.
The extracted items are stored in the engine's own tables (facts, preferences, instructions, timelines, relations), replaced whenever a memory is extracted again, and deleted with the memory when you forget it. The Map and Knowledge tabs show them.
- Moons for a memory saved or extracted after the page loaded show only after you reload the dashboard page: the map reads the knowledge once, when the page loads.
- The dashboard keeps the
extractionModeland theembeddingssetting it started with. After you change either,/mnemosyne-uion the running dashboard says it must be restarted: end every session that opened or reused it (/clearcounts), then run/mnemosyne-uiagain. - Saves by outside agents (Codex, Cursor and others, through the proxy) get no automatic extraction. They need
/mnemosyne-knowledge apply. - This update needs the plugin's newer engine build (
18.12.3-mnemosyne.10, which has the knowledge tools, local embeddings andreembed): run/mnemosyne-setuponce after updating. Until then extraction stays off and says so in the activity log (engine out of date), and embeddings are off: they switch on only after setup has downloaded the model. - Embeddings are tested on macOS on Apple Silicon only. On Windows and Linux they are off under
auto, and turning them on is not verified there.
A save records who wrote it (and, since 0.8.0, through what: see Where a memory comes from). When a prompt starts with a remember term, the first save that turn is yours: it is stored with author_type human and veracity stated. Claude's own saves are agent; so are outside agents' saves, named by their MCP client. stated ranks a little higher in recall than the default unknown (1.0 against 0.8), which is why a fact you asked for is found first.
With activityLog on (the default), each memory call adds one line to ~/.claude/mnemosyne/activity.jsonl: recalls, saves, updates, forgets and stats, from Claude, from the plugin's own lookups, from the dashboard and from other MCP clients, plus one extract line for every knowledge extraction run. Since 0.6.0 a line also names the app that made the call (client, for example Claude Code, or the name an MCP client gave when it connected) and the Claude Code session (session), and a save records who wrote it (author_type and veracity, see above). Older lines have none of these.
The Activity tab reads the log and follows it live. Its list has column headers (Time, Action, Source, Project, What, Result) and a key under the list that says what each Source means (hook, tool, trigger, external, ui, backfill). The filter chips pick one action, or Outside agents; the App drop-down picks one app (Claude Code, Codex, Cursor, Dashboard, or another client by its own name); pressing the short session id on a line shows only that session, and a chip clears it. A saved memory carries a badge for who wrote it: "you said" for a save you asked for with a remember term, "Claude" for Claude's own save, the app's name for another agent's, and no badge for a save that does not say. An older memory takes the author of the first save after the update: after any re-save of the same text, a memory from 0.5.1 reads "Claude" or the app's name, even if you wrote it. Four tiles count today's recalls, saves, updates and failed calls.
Privacy: a line holds the query or the start of the memory text (at most 200 characters). Text that looks like a secret (API keys, tokens, password= values, long hex or base64 runs) is replaced by [redacted] before it is written, by pattern, so an unusual secret can slip through. The file is owner-only, capped at 1 MB (storage.activityLogMaxBytes) with one older copy kept. Set activityLog to off to write nothing. A change made in the Settings tab adds a settings line naming the setting and the kind of value before and after, never the value.
Since 0.8.5 the dashboard has a Settings tab (the last one, after Health) where you can see, change and reset every setting of the memory system, without editing JSON or remembering command-line flags. Each row shows the setting's name, a plain-words help text, its current value and its default, an editor for its type (a switch, a choice, a number with its range, text or a list), and a "Reset to default" link. A change is checked against the allowed range before it is saved, and the row says when it takes effect. A box above the list filters the rows by name.
- Mnemosyne config (
~/.claude/mnemosyne/config.json). Every number that used to be fixed in the code lives here: the save guard's limits, the tag counts, the recall counts and weights, the expiry days, the activity-log size, the backups kept and which memory tools exist. The hooks, the outside-agent wrapper and the dashboard read the file on every call, so a change applies on the next save or recall, with no restart. Only the values that differ from the default are stored, and with no file every setting is at its default and Mnemosyne behaves exactly as in 0.8.4. The file is owner-only (0600) and is written safely (a new file is moved over the old one, and the old one is kept asconfig.json.bak). You can edit it by hand: a value that is the wrong type or outside its range is ignored (the default is used), and the Settings tab shows a warning naming the setting. - Plugin options (the ten options in the second table below, also reached through
/plugin→ Mnemosyne → configure or/config). Claude Code stores these. The Settings tab reads and changes them only through the officialclaude plugin configurecommand, one option per change, and reads the value back to confirm it. A change takes effect after the plugin reloads or Claude Code restarts, and the tab says so ("Restart needed for: ..."). An install started with--plugin-dirhas no plugin id, so there the options are read-only and the tab points to/config.
Each row carries badges: where it is stored (Mnemosyne config, Plugin option, Read-only) and when a change takes effect:
| Badge | Meaning |
|---|---|
| Applies now | Read from config.json on the next save or recall. |
| Applies next session | The six memory tools are registered when a session starts, so turning one on or off shows from the next session. |
| Applies after restart | A plugin option: after the plugin reloads or Claude Code restarts. |
Defaults and ranges are the ones in the schema (hooks/settings.ts); a spec (tests/bun/readme-settings.spec.ts) fails when this table and the schema disagree. A key is written group.name, which is where it sits in config.json ({ "guard": { "maxLength": 6000 } }).
| Setting | Default | Allowed | Applies | What it does |
|---|---|---|---|---|
guard.warnLength |
1500 | 200 to 7999 | now | Text longer than this is saved with a warning to split it into one fact per call. |
guard.maxLength |
4000 | 201 to 8000 | now | Text longer than this is refused. The ceiling is 8000 because the embedding model reads at most 8192 characters. |
guard.minLength |
15 | 5 to 100 | now | Text shorter than this is refused. Empty text is always refused. |
guard.nearDuplicate |
on | on, off | now | Look for a stored memory that says almost the same before a save. Needs embeddings. |
guard.nearDuplicateScore |
0.88 | 0.8 to 0.99 | now | How close two memories must be to count as near-duplicates. Higher flags fewer. |
guard.chatter |
warn | strict, warn, off | now | The dated-progress check. warn: flag it, and guardMode decides whether a flag needs a confirm. strict: always ask for a confirm. off: no check. |
guard.neverSave |
none | up to 100 entries of 3 to 80 characters | now | Extra words or phrases that block a save, like a secret. Plain text, not patterns. It can only add to the built-in rules. |
tags.maxPerMemory |
5 | 1 to 10 | now | A memory keeps at most this many tags. |
tags.autoAdmitCount |
3 | 1 to 10 | now | A new tag proposed for this many memories joins the vocabulary on its own. |
recall.defaultLimit |
5 | 1 to 20 | now | How many memories memory_recall returns when the call gives no limit. |
recall.maxLimit |
50 | 20 to 100 | now | The most one memory_recall may return. |
recall.firstPrompt |
on | on, off | now | Look up related memories on the first prompt of a session. Off means no automatic recall; the remember and recall terms still work. |
recall.firstPromptCount |
5 | 1 to 10 | now | How many memories the first-prompt recall adds. |
recall.firstPromptSentences |
4 | 1 to 6 | now | How many sentences of the first prompt are searched. |
recall.vectorWeight |
0.5 | 0 to 1 | now | Advanced. How much closeness in meaning counts in a ranking. |
recall.keywordWeight |
0.3 | 0 to 1 | now | Advanced. How much matching words count in a ranking. |
recall.importanceWeight |
0.2 | 0 to 1 | now | Advanced. How much the saved importance counts in a ranking. |
expiry.contextDays |
14 | 1 to 365 | now | A project-state (context) note stops being recalled this many days after it is saved, unless the save names another number. It is not deleted. |
tools.memory_remember |
on | on, off | next session | Saves a memory. Off also removes the remember trigger and the rules block from the session, so Claude is never told to call a missing tool. |
tools.memory_recall |
on | on, off | next session | Looks memories up. |
tools.memory_update |
on | on, off | next session | Changes a saved memory. |
tools.memory_forget |
on | on, off | next session | Deletes a memory for good. |
tools.memory_stats |
on | on, off | next session | Shows counts of what is stored. |
tools.memory_get |
on | on, off | next session | Reads one memory in full, by id (recall cuts long ones). |
tools.externalAllow |
all 6 tools | any of the 6 tool names | now | Which of the six tools the outside-agent wrapper serves (see Use the memory from other tools). Needs externalMcp on. |
storage.activityLogMaxBytes |
1048576 | 262144 to 16777216 | now | The activity log rolls over to its one backup file past this many bytes (1 MiB by default; 256 KiB to 16 MiB). |
storage.backupsKeep |
10 | 3 to 50 | now | A backup keeps this many of the newest files and removes older ones. |
map.stationMin |
50 | 2 to 1000 | now | On the map, a plain folder with at least this many live memories is a space station; with fewer, a starship. Git repos are always planets. |
map.linkScore |
0.8 | 0.65 to 0.95 | now | How close in meaning a memory of one project must be to a memory of another for the pair to count toward a black hole between them. Lower draws more links. Needs the embeddings. |
map.linkMinPairs |
2 | 1 to 50 | now | How many such pairs two projects need before the map draws a black hole between them. A small store with few close pairs may show none until this is 1. |
Rules between settings are checked together: guard.minLength must stay below guard.warnLength, and that below guard.maxLength; recall.defaultLimit cannot be above recall.maxLimit; the three ranking weights cannot all be 0, and they are scaled to add up to 1. A change that would break a rule is refused and the message names the setting.
The ranking weights are sent with every recall and also pinned in the engine's environment, so a shell variable can no longer change a ranking without you knowing.
| Option | Default | Allowed | What it does |
|---|---|---|---|
scope |
global | global, per-project | global: one memory for every repo. per-project: each repo keeps its own; ask for "global" to share a fact. |
showActivity |
on | on, status, off | on: a dim line in the conversation when Mnemosyne recalls, plus a status line under the prompt. status: the status line only. off: silent. |
activityLog |
on | on, off | on: each memory call adds one line to ~/.claude/mnemosyne/activity.jsonl (see Activity log). off: nothing is written. |
guardMode |
strict | strict, warn | strict: a save the guard warns about is refused until it is sent again with confirm: true. warn: it is saved at once, flagged, and the reply shows the warnings (see The save guard). Secrets and empty, very short or over-length text are refused in both. |
memoryDir |
~/.claude/mnemosyne/store | text | Where memories are stored: an absolute path or one starting with ~/. |
rememberTerms |
remember:, remember that, note that, save to memory | list of terms | A prompt that starts with one of these is saved. none turns it off. |
recallTerms |
recall:, what do you remember about, check memory | list of terms | A prompt that starts with one of these looks up memories. none turns it off. |
externalMcp |
off | on, off | Lets other MCP clients use the same memory (see Use the memory from other tools). |
extractionModel |
haiku | text | The model that pulls facts, preferences, instructions, dated events and relations out of each saved memory in the background (an alias or a full model id). It also drives /mnemosyne-tags. off turns extraction off: no model call, and /mnemosyne-tags and /mnemosyne-knowledge say so. |
embeddings |
auto | auto, on, off | auto: meaning-based recall on only on macOS on Apple Silicon, off elsewhere. on: always on. off: keyword-only recall. Local only; /mnemosyne-setup downloads the model once (see Embeddings). |
sessionSummary |
off | off, on | off: nothing. on: when a session ends (exit, /clear or resume) a small marker is left (the session id, its owner and its transcript path; no text). On a later session's first prompt, after the reply and in the background, Haiku (the extractionModel) summarises that session's redacted conversation (at most 30,000 characters leave this machine) into one session-summary memory of at most 600 characters, kept 30 days. The next session's first prompt shows the newest summary and the pinned memories. |
expireUnusedDays |
90 | text | Once a day, in the background, a memory of the kinds project state, session summary, bug note or reference that nobody has recalled for this many days gets an end date and stops being recalled. Nothing is deleted and it can be restored in the dashboard's Review tab. Pinned memories, facts, preferences, rules and decisions are never touched, and the store is backed up first. 0 turns the sweep off; 1 to 29 are refused. Preview it: bun bin/memories.ts --store <store> expire. |
The tab draws the plugin options from the schema the claude command reports, so an option a later version adds appears there by itself, in an "Other plugin options" group.
Changing memoryDir or scope changes which memories you see, and the memories already saved are not moved to the new place. So the tab asks you to type the new value again before it saves, and warns you.
With scope: per-project, a repo keeps one memory wherever it is opened: a second clone, a worktree, the repo moved to another folder, or a fresh checkout in a cloud session all share it.
- A folder already in use keeps its owner (
project-<folder>-<hash of its path>), so nothing saved before moves or is hidden. - A new folder whose git remote is already known joins that project's owner. The remote is matched in every form:
git@github.com:Org/Repo.git,https://github.com/org/repoandssh://git@github.com:22/org/repoare one repo (case is ignored on GitHub, GitLab and Bitbucket). - A repo seen nowhere before gets
project-<repo>-<hash of its remote>, the same id on every machine. A repo without a remote, and a plain folder, keep the path id as before.
projects.json records a hash of each remote, never the remote, its owner or org. Known limits: two copies that each have their own memories from before this change stay apart (neither is merged into the other); a project learns its remote's hash the first time a session starts in it after the update, so open the original folder once before a new clone or worktree, or the clone starts its own memory; a repo without a remote is still tied to its path.
- "Reset to default" on a row sends that one setting back to its default; for a
config.jsonsetting this removes its key from the file. - "Reset all" (at the top, after a confirm) puts every
config.jsonsetting back to its default and keeps the old file asconfig.json.bak. It does not touch the plugin options; reset those one at a time. - "Back up now" copies the database to
~/.claude/mnemosyne/backups/(see Backups) and lists the backups; it keeps the neweststorage.backupsKeep. - The Setup card shows whether the engine is installed and current, whether the embedding model is downloaded, whether the outside-agent wrapper exists and which Bun runs, with a button that copies each setup command. Running setup and rebuilding vectors stay commands (
/mnemosyne-setup,/mnemosyne-embed); the page never runs them. - Theme and the search mode are per browser and live in that browser only.
Every change from the tab adds one settings line to the activity log, naming the setting and only the kind of value before and after (for example guard.maxLength number>default), never a value or a list item.
- The built-in secret rules (an AWS key, a GitHub token, an
sk-API key, a Slack token, a JWT, a private key header, aBearertoken, a URL with a password, andpassword,secret,api_keyortokenset to a value). The tab lists them read-only. No setting can switch one off or weaken it, and nothing can override a secret refusal; theguard.neverSavelist can only add words. - The block on empty text. Empty text is refused whatever
guard.minLengthsays. - The engine's relevance floors and its 0.8/0.2 blend of keyword and meaning scores. They are engine code, and the plugin does not change the engine. The three ranking weights above are the part that is yours to set.
- The embedding model (BAAI/bge-small-en-v1.5, running on your machine). It is pinned so that memory text can never be sent to an embedding service (see Embeddings).
- The tag format (length, the namespaces
entity,agentandasset) and the tag vocabulary's contents. The tab shows their counts; tag aliases are edited withbun bin/vocab.ts(see Tags: vocabulary and namespaces). - The engine's own 27 tools. They are hidden from Claude and from outside agents; the tab lists them read-only.
Turn on externalMcp, start a new session, and the plugin writes ~/.claude/mnemosyne/bin/mnemosyne-mcp: the engine as a stdio MCP server, with the right store and settings baked in. Add it to any MCP client:
claude mcp add mnemosyne-memory -- ~/.claude/mnemosyne/bin/mnemosyne-mcpOn Windows the file is %USERPROFILE%\.claude\mnemosyne\bin\mnemosyne-mcp.cmd.
For Cursor, Codex and others, use the same path as the server command. It offers the same six tools as the plugin (memory_remember, memory_recall, memory_update, memory_forget, memory_stats, memory_get) and nothing else of the engine's. The setting tools.externalAllow picks which of the six it serves (see Settings); any other tool answers "Tool not available". Remember and update take topic tags (in the vocabulary format), a kind and confirm; remember also takes expires_in_days, and update also takes restore: true (see How long a memory lasts). Every save passes the save guard (G1, G2 and G4; not the near-duplicate check) and is recorded as written by mcp:<your client's name>. A refused save is a JSON-RPC error -32602 whose data holds rules (the rule ids, such as ["G1"]) and verdict: block (cannot be saved) or warn (send it again with confirm: true). A client cannot pick an engine bank, set its own trust tier or write other metadata, and it has no replaces. memory_remember and memory_update need the engine build this plugin ships and are refused with a JSON-RPC error until /mnemosyne-setup has run, so after a plugin update run /mnemosyne-setup once, then restart your other MCP clients: a server that is already running keeps the engine it started with. It reads and writes global memories by default; set MNEMOSYNE_OWNER to a project-… owner to use a project's. It recalls every memory saved with scope global, whichever project holds it, but it can only change its own owner's: memory_update and memory_forget of another owner's memory answer not_found, and the reply then adds owner (whose memory it is) and a hint that says where to change it (the dashboard, a Claude Code session in that project, or a server started with that MNEMOSYNE_OWNER). memory_get reads only what recall would show this server: its own owner's memories and live global ones (a retired or expired global memory of another project is hidden, as recall hides it). Any other id, or an owner that is not the memory's own, answers {"status":"not_found"}, the same as an id that does not exist, so it says nothing about other projects' memories. A recall row cut to its 500-character preview carries truncated: true and a note naming the memory_get call (no note when tools.externalAllow leaves memory_get out). A tools.externalAllow list saved before 0.34.0 is kept as it was, so it does not gain memory_get: add it on the Settings tab. Turning the option off deletes the file.
Security. There is no token because there is no network port: the client starts the server itself over stdin/stdout, and nothing listens on the network. Access is whoever can run programs as your user, the same people who could read the database file directly. To keep other accounts on the machine out, the plugin sets ~/.claude/mnemosyne and the store to owner-only (700) on macOS and Linux; on Windows your user profile's permissions already do. Don't wrap the server in an HTTP bridge without adding authentication there.
Since 0.10.0 the engine and the proxy can be installed without Claude Code, everything under one folder you name (Switch keeps its copy in ~/.switch/memory). From a checkout of a release tag, with Bun on the PATH:
bun bin/install-engine.ts --root ~/.switch/memory # embeddings off
bun bin/install-engine.ts --root ~/.switch/memory --embeddings # also downloads the local model once--root must be an absolute path to a new or empty folder, or to one an earlier install made (it holds .mnemosyne-root); any other non-empty folder is refused. --store <abs dir> puts the store somewhere else (default <root>/store), but not inside <root>/engine or <root>/plugin, which each install replaces. It prints one JSON line (ok, root, engine, build, cli, store, launcher, proxy) and creates what is missing:
| Path | What | Mode |
|---|---|---|
engine/ |
the engine, staged and swapped in | 700 |
store/ |
the memory store | 700 |
model-cache/ |
the local model (PI_CONFIG_DIR) |
700 |
plugin/bin, plugin/hooks |
the proxy and the files it imports | folders 700, files 644 |
bin/mnemosyne-mcp |
the launcher | 700 |
config.json, vocabulary.json |
settings and tags | 600 |
activity.jsonl |
the activity log | 600 |
activity-daily.json |
per-day counts for the dashboard, written beside the log | 600 |
.mnemosyne-root |
marks the folder as a root | 600 |
Point an MCP client at <root>/bin/mnemosyne-mcp, as in Use the memory from other tools. The checkout is not needed after the install. The engine, the launcher and the proxy files are replaced at each run (a run with the same tag is a full reinstall); an existing store, config.json, vocabulary.json and activity.jsonl are kept as they are. The new engine is swapped in only after it answers initialize and tools/list (and, with --embeddings, a remember and a recall), so a failed install leaves the previous engine working and exits 1.
What stays out of $HOME, and what does not. The installer and the launcher do not read or write ~/.claude or ~/.omp, as far as the tests show: they install and run remember and recall with HOME pointing at a fake home (unchanged afterwards) and at a folder that does not exist (still missing afterwards). Two writers outside the root remain:
- Bun itself keeps a package cache (
~/.bun/install/cache) and a transpiler cache (~/Library/Caches/bunon macOS) under$HOME. SetBUN_INSTALL_CACHE_DIRandBUN_RUNTIME_TRANSPILER_CACHE_PATH=0(for both the install and the launcher) to keep them out; the tests above pass only with both set. - The engine's logger (pi-utils) builds its folder as
path.join($HOME, PI_CONFIG_DIR), so with the root's absolute cache folder its logs would go to$HOME/<root path>/model-cache/logs, or to$XDG_STATE_HOME/omp/logswhen that folder exists. It writes only when the engine logs something: a corrupt model being replaced, an embedding failure, a model change that rebuilds the vectors, a failed consolidation. A normal install, remember and recall wrote nothing there in the tests, with embeddings on or off. pi-utils has no setting that moves this folder, and the engine is not changed in this release; this is a known limit.
Two installs at once: each holds <root>/engine.lock while it runs. A second install waits up to 10 minutes and then exits 3 with another install holds <path>; MNEMOSYNE_LOCK_WAIT_MS (milliseconds) changes the wait. A lock older than 30 minutes is taken over, a running install keeps its lock fresh, and an install stopped by SIGTERM or SIGINT removes its lock (one killed outright leaves it for the 30 minutes). /mnemosyne-setup takes the same lock beside its own engine folder and waits only 30 seconds for it, so it ends inside its own time limit.
The path id of a folder, the owner it had before issue #3 (the plugin now looks the owner up, see Which project a memory belongs to, and still uses this id for every folder already in use):
bun bin/owner.ts ~/dev/switch-agent # project-switch-agent-…
bun bin/owner.ts --global # globalInside a git repo the id is that of the main worktree (the parent of git rev-parse --path-format=absolute --git-common-dir, as git reports it, symlinks resolved), so a linked worktree shares its repo's owner; any other folder is hashed as given. A git failure other than "not a git repository" (a broken .git file, an unsafe-directory refusal) exits 1 with git's message. Known limit: inside a submodule, or a repo made with git init --separate-git-dir, the parent of the common git dir is not the work tree, so the id is built from the wrong folder; the rule is kept as is because other implementations copy it. tests/fixtures/owner-golden.json lists paths and their ids for another implementation to check against.
Two notes. On Windows the launcher is bin\mnemosyne-mcp.cmd and a root holding ", % or ! is refused; this path is untested on Windows. When the installed engine is not the build the proxy expects, a save is refused with a message that says to run /mnemosyne-setup in Claude Code; outside Claude Code, run the install above again instead (the text is unchanged so the plugin's message stays right).
Saving text this owner already holds. Since 0.11.0 the memory_remember reply carries duplicate: false for a new memory, true when the same text was already stored. status stays stored and memory_id is the existing memory's id, so a caller that ignores the field sees what it saw before. true means a row with exactly that text exists for the owner, also a retired or expired one, which a re-save does not bring back; and a save of the same text by another process between the check and the save can report false for an existing id. It needs engine build 18.12.3-mnemosyne.11. In Claude Code, run /mnemosyne-setup once after updating: until then saves and updates are refused, because the plugin wants this exact build. Calling an older engine directly leaves the field out.
The plugin hooks into Claude Code at four points:
- Session start: adds the memory tools and the
/mnemosyne-setup,/mnemosyne-ui,/mnemosyne-tags,/mnemosyne-knowledgeand/mnemosyne-embedcommands, and says so if the installed engine is older than the plugin's. - System prompt: adds a short, fixed set of rules telling Claude when to save and recall.
- Your prompt: on the first prompt (again after a compaction), or one starting with a term above, looks up memories and attaches them to that prompt.
- Tool calls: when Claude calls a memory tool, checks a save with the guard, runs the engine and returns the answer.
Mnemosyne shows a dim line in the conversation when it recalls and a status line under the prompt; showActivity quiets them.
Where an organization manages Claude Code, the system-prompt rules are skipped unless the organization lists this plugin in its managed settings; the tools and trigger terms still work.
Memories live in a local SQLite file. Mnemosyne runs on its own private copy of the Mnemosyne Memory engine, built from the plugin's engine/ folder. Design notes: DESIGN.md.
MIT
