Skip to content

About

Shared long-term memory for Claude Code, backed by the Mnemopi engine that omp writes.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Claude Mnemosyne

Long-term memory for Claude Code. A fact you save in one session is there in the next one, in any repo.

How memory flows

Install

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-mnemosyne

Answer 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.

Update

claude plugin update mnemosyne@mnemosyne

Restart 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.

Updating without Claude Code

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 instead

It 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:

  1. Engine. Installed with bin/install-engine.ts: built next to the old one, checked, then swapped in. Skipped when the build is already current.
  2. Plugin copy. ~/.claude/mnemosyne/plugin (the MCP proxy and the dashboard) is staged in plugin.next and swapped the same way.
  3. Launcher. ~/.claude/mnemosyne/bin/mnemosyne-mcp.cmd (mnemosyne-mcp elsewhere) is rewritten with the current launcher, keeping your store, activity log, guard mode, vocabulary, model cache and embeddings choice; it is created if missing.
  4. Dashboard. On Windows the always-on dashboard (scheduled task "Mnemosyne dashboard") is stopped for the swap and started again, with no window.
  5. 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 600 waits up to 10 minutes for them to end before the swap;
  • --stop-engines stops 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.

Uninstall

claude plugin uninstall mnemosyne@mnemosyne

This leaves the engine and your memories in ~/.claude/mnemosyne/. To delete them too (this erases every memory, permanently):

rm -rf ~/.claude/mnemosyne

Setup

In 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.

Use

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.

What 0.8.0 adds

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).

The save guard

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:

  1. strict (the default): the save is refused, with the rules named, until the same call is sent again with confirm: true. Claude does that only when the memory is wanted as it is.
  2. 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.

Kinds

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.

Where a memory comes from

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.

How long a memory lasts

  1. Trim by scope. The engine used to delete any memory not labelled imported after 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 is session. The plugin always saves with scope global (or channel in 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.
  2. Project state expires. A context note gets an expiry 14 days after the save (expiry.contextDays). Any save can pass expires_in_days (1 to 365). Recall stops returning an expired memory; it is not deleted.
  3. Restore. The engine can bring a retired or expired memory back (mnemopi_update with restore: true, for the owner's own row). An outside agent can do it through the proxy: memory_update with restore: true is passed to the engine, and it restores a retired row that belongs to the agent's own owner (the engine answers not_found for another owner's row, and unchanged for a row that is not retired). Claude's own memory_update tool does not offer restore; since 0.9.0 the dashboard's Review tab has a Restore button (see Dashboard writes and the Review tab).
  4. Unused memories. Since 0.9.0 a sweep calls the engine's mnemopi_expire_unused once a day and expires memories of the kinds context, event, error and artifact that nobody recalled for expireUnusedDays (90 by default). See Unused memories: expireUnusedDays.

Near-duplicates and replacing a memory

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.

The audit trail

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).

Backups

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: vocabulary and namespaces

Tags now follow one format, and Claude, the tagger and outside agents all use it:

  1. A tag is value or ns:value. ns is entity, agent or asset (for example agent:codex); any other prefix is just punctuation inside the value.
  2. value is lowercase, hyphen-separated, 1 to 31 characters: Code Review becomes code-review.
  3. 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.
  4. An alias in the vocabulary folds into its tag (CR into code-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 init

It 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).

What leaves your machine

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.

Saves cannot move to another bank

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.

What 0.8.0 did not do

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:

  1. 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; send tags: [] to remove tags on purpose.
  2. Embeddings, and so G3, are tested on macOS on Apple Silicon only (see Engine logs and platforms).

What 0.9.0 adds

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.

Dashboard writes and the Review tab

The dashboard was read-only apart from Forget. It now writes, through the same guards as every other save.

  1. 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: global or a project already in the store. A context note gets its 14-day expiry unless you set another.
  2. 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 with confirm: true; a confirmed save is flagged in metadata.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).
  3. A dashboard save is recorded as source dashboard, trust tier STATED, author type human and veracity stated. Edits go through the engine as the memory's own owner and are recorded in the audit trail as dashboard. Tags that all clean away are refused rather than read as "remove the tags".
  4. 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:

  1. 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.
  2. 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.

Session summaries and pinned memories

  1. 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; false unpins), and the dashboard's Edit form has the same checkbox. Outside agents cannot pin (the proxy takes no pinned).
  2. 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.firstPromptCount is lower) and at most 2000 characters of them, the last one cut with an ellipsis and a cut mark that names memory_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.
  3. Session summaries are off by default (sessionSummary in the settings). When on, a session ending (exit, /clear or 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 the extractionModel (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 one event memory with source import:session, kept 30 days. A session of fewer than 2 turns is skipped, a reply of NONE saves 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). extractionModel set to off stops it.

Unused memories: expireUnusedDays

expireUnusedDays (90 by default) sets how long a memory can go without being recalled before the daily sweep expires it.

  1. What it expires: only memories of the kinds context (project state), event (including session summaries), error (bug notes) and artifact (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 unreadable metadata or no scope is spared. A fact, preference, instruction, decision or unknown memory is never touched, whatever the number, and no setting can widen the list.
  2. 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 expired and Restore brings it back.
  3. The number is 30 to 3650; 0 turns the sweep off; 1 to 29 (or anything that is not a whole number) is refused and no sweep runs.
  4. 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 in per-project scope, the global ones in global scope), 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 as expire-sweep, and the activity log gets one expire line with counts and ids.
  5. Preview it, changing nothing: bun bin/memories.ts --store ~/.claude/mnemosyne/store expire (add --days N or --owner <owner>). It lists what would expire and prints the exact --apply command to run it by hand.

Engine logs and platforms

  1. Engine logs. The engine writes one omp.*.log file per run into ~/.claude/mnemosyne/model-cache/logs and never trims them. Session start and /mnemosyne-setup now delete, in the background and without any message, the omp.*.log files 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.
  2. Platform notes. /mnemosyne-setup ends 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 when embeddings is on.

Importing documents and sessions: /mnemosyne-import

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.

Measuring recall: bin/eval.ts

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 from gold.draft.json by copying the right ids into expect) and the runs.

Embeddings (meaning-based recall)

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-setup downloads the model once from huggingface.co (the Qdrant/bge-small-en-v1.5-onnx-Q repository; setup prints the host and size first). The model is cached under ~/.claude/mnemosyne/model-cache/, not the shared ~/.omp cache, and is only ever downloaded during setup. After updating to this version, run /mnemosyne-setup once to install the engine build 18.12.3-mnemosyne.11 and 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. embeddings in the plugin settings is auto by default: on only on macOS on Apple Silicon (the tested platform), off elsewhere unless you set it to on. off keeps 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 embeddings to off and run /mnemosyne-setup again, or run /mnemosyne-setup again 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.org that 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 apply took 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 apply again 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.

Dashboard

/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) and projects.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/memories takes reach=all|shared|project and kind=, and returns facets (the counts by reach and by kind under the other filters) and each row's memory_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.json and, through the claude command, 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 /config with 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 galaxy

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.

The Knowledge and Health tabs

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".

Topic tags: /mnemosyne-tags

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.

Knowledge extraction: what Claude learned

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.

What 0.7.0 does not do yet

  1. 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.
  2. The dashboard keeps the extractionModel and the embeddings setting it started with. After you change either, /mnemosyne-ui on the running dashboard says it must be restarted: end every session that opened or reused it (/clear counts), then run /mnemosyne-ui again.
  3. Saves by outside agents (Codex, Cursor and others, through the proxy) get no automatic extraction. They need /mnemosyne-knowledge apply.
  4. This update needs the plugin's newer engine build (18.12.3-mnemosyne.10, which has the knowledge tools, local embeddings and reembed): run /mnemosyne-setup once 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.
  5. 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.

Who wrote a memory

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.

Activity log

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.

Settings

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.

Two homes for settings

  1. 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 as config.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.
  2. 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 official claude plugin configure command, 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-dir has 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.

The settings in config.json

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.

The plugin options

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.

Which project a memory belongs to (per-project)

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.

  1. A folder already in use keeps its owner (project-<folder>-<hash of its path>), so nothing saved before moves or is hidden.
  2. 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/repo and ssh://git@github.com:22/org/repo are one repo (case is ignored on GitHub, GitLab and Bitbucket).
  3. 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, back up and the other rows

  1. "Reset to default" on a row sends that one setting back to its default; for a config.json setting this removes its key from the file.
  2. "Reset all" (at the top, after a confirm) puts every config.json setting back to its default and keeps the old file as config.json.bak. It does not touch the plugin options; reset those one at a time.
  3. "Back up now" copies the database to ~/.claude/mnemosyne/backups/ (see Backups) and lists the backups; it keeps the newest storage.backupsKeep.
  4. 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.
  5. 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.

What cannot be changed, and why

  1. The built-in secret rules (an AWS key, a GitHub token, an sk- API key, a Slack token, a JWT, a private key header, a Bearer token, a URL with a password, and password, secret, api_key or token set 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; the guard.neverSave list can only add words.
  2. The block on empty text. Empty text is refused whatever guard.minLength says.
  3. 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.
  4. 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).
  5. The tag format (length, the namespaces entity, agent and asset) and the tag vocabulary's contents. The tab shows their counts; tag aliases are edited with bun bin/vocab.ts (see Tags: vocabulary and namespaces).
  6. The engine's own 27 tools. They are hidden from Claude and from outside agents; the tab lists them read-only.

Use the memory from other tools

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-mcp

On 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.

Install outside Claude Code

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:

  1. Bun itself keeps a package cache (~/.bun/install/cache) and a transpiler cache (~/Library/Caches/bun on macOS) under $HOME. Set BUN_INSTALL_CACHE_DIR and BUN_RUNTIME_TRANSPILER_CACHE_PATH=0 (for both the install and the launcher) to keep them out; the tests above pass only with both set.
  2. 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/logs when 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              # global

Inside 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.

How it works

The plugin hooks into Claude Code at four points:

  • Session start: adds the memory tools and the /mnemosyne-setup, /mnemosyne-ui, /mnemosyne-tags, /mnemosyne-knowledge and /mnemosyne-embed commands, 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.

License

MIT

About

Shared long-term memory for Claude Code, backed by the Mnemopi engine that omp writes.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages