memcue
juev/memcue
memcue long-term memory for DeepSeek Harness (dsh): the model searches and writes its facts itself
Installation
dsh plugin --profile web add github:juev/memcueREADME
memcue
Long-term memory for Claude Code. memcue keeps a base of short, self-contained facts in SQLite, with a text history git can sync, and works through hooks:
- At session start, and again after a compaction or
/clearit tells the agent to search memcue before it answers a question that may depend on earlier sessions, and sends a card: global rules and the rules and decisions of the current repository, the ones you stated first, within about 1.2 thousand tokens. The card is how a rule the agent would not think to search for still reaches it. - When the agent needs something from earlier sessions it runs
memcue searchitself, with its own words: synonyms, the system's name, both languages. Nothing is injected with a prompt (#361): the agent's own queries answered more questions right than facts matched to the prompt's words (see Results). A search returns at most five facts, and only those that fit: a fact whose BM25 is below 0.6 of the best one's is left out, so a query one fact answers gets that fact alone (#407, search-floor). - When the agent learns something that will matter later (a rule or a correction you gave, a decision the code
does not show, a pitfall, a host or a service outside the repository) it stores it at once with
memcue add, through the bundled skill. Nothing is extracted from the transcript afterwards (#494): the agent decides what is worth keeping, the way Claude Code's built-in memory does. - When you ask ("remember…", "that is outdated") the agent adds, corrects or retires a fact the same way.
Search is lexical (SQLite FTS5 with Snowball stemming for English and Russian), runs offline in one binary and
takes about 10 ms per query. A fact keeps where and when it was learnt and where it holds (scope), and facts from
every repository are searched: what you learnt in one project is found in another. On equal scores a fact of the
current host comes first, then one learnt in the session's project, then a global one; no fact is excluded, because
working on a server from a laptop is normal. An uppercase
ticket key such as PROJ-1234, #123 or !45 is kept as one exact term, so a query naming it finds that ticket
and not another number; bare numbers, ports and versions are still left out of the index. A versioned name such as
PostgreSQL-16 is not a ticket: it keeps PostgreSQL and drops 16.
The base can live in a git repository and be synced between machines; memcue ships a merge driver for it. The same base works for Hermes Agent and for DeepSeek Harness.
memcue is young: the 0.4 line has been measured on one developer's memory and sessions (see Results) and not yet run for long in daily work.
Requirements
- macOS or Linux.
- Claude Code. Only
memcue migrateandmemcue dream -runcall a model, through theclaudecommand onPATHor an OpenAI-compatible endpoint (see Configuration). - Go 1.27 or later, to build from source.
Compatibility
This release is 0.22.1; the fact-base schema is 6 (see Upgrade). A version-6 base does not open in 0.20.0
or older, so stop every older memcue and back the base up before the first open. A text history (0.22.0) is not read
by 0.21.2 or older, so upgrade every machine that syncs the base before the move. The three plugin manifests carry the
same version, and a release bumps all of them together.
| memcue | schema | Claude Code plugin | DSH plugin | Hermes plugin |
|---|---|---|---|---|
| 0.22.1 | 6 | 0.22.1 | 0.22.1 (dsh >=0.2.0-rc.2 <0.3.0) | 0.22.1 (hermes-agent 0.15.2) |
| 0.22.0 | 6 | 0.22.0 | 0.22.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.22.0 (hermes-agent 0.15.2) |
| 0.21.2 | 6 | 0.21.2 | 0.21.2 (dsh >=0.2.0-rc.2 <0.3.0) | 0.21.2 (hermes-agent 0.15.2) |
| 0.21.1 | 6 | 0.21.1 | 0.21.1 (dsh >=0.2.0-rc.2 <0.3.0) | 0.21.1 (hermes-agent 0.15.2) |
| 0.21.0 | 6 | 0.21.0 | 0.21.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.21.0 (hermes-agent 0.15.2) |
| 0.20.0 | 5 | 0.20.0 | 0.20.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.20.0 (hermes-agent 0.15.2) |
| 0.19.0 | 5 | 0.19.0 | 0.19.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.19.0 (hermes-agent 0.15.2) |
| 0.18.1 | 5 | 0.18.1 | 0.18.1 (dsh >=0.2.0-rc.2 <0.3.0) | 0.18.1 (hermes-agent 0.15.2) |
| 0.18.0 | 5 | 0.18.0 | 0.18.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.18.0 (hermes-agent 0.15.2) |
| 0.17.0 | 5 | 0.17.0 | 0.17.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.17.0 (hermes-agent 0.15.2) |
| 0.16.0 | 5 | 0.16.0 | 0.16.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.16.0 (hermes-agent 0.15.2) |
| 0.15.0 | 5 | 0.15.0 | 0.15.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.15.0 (hermes-agent 0.15.2) |
| 0.14.0 | 5 | 0.14.0 | 0.14.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.14.0 (hermes-agent 0.15.2) |
| 0.13.0 | 5 | 0.13.0 | 0.13.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.13.0 (hermes-agent 0.15.2) |
| 0.12.0 | 5 | 0.12.0 | 0.12.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.12.0 (hermes-agent 0.15.2) |
| 0.11.1 | 5 | 0.11.1 | 0.11.1 (dsh >=0.2.0-rc.2 <0.3.0) | 0.11.1 (hermes-agent 0.15.2) |
| 0.11.0 | 5 | 0.11.0 | 0.11.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.11.0 (hermes-agent 0.15.2) |
| 0.10.0 | 5 | 0.10.0 | 0.10.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.10.0 (hermes-agent 0.15.2) |
| 0.9.0 | 5 | 0.9.0 | 0.9.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.9.0 (hermes-agent 0.15.2) |
| 0.8.0 | 5 | 0.8.0 | 0.8.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.8.0 (hermes-agent 0.15.2) |
| 0.7.0 | 5 | 0.7.0 | 0.7.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.7.0 (hermes-agent 0.15.2) |
| 0.6.0 | 5 | 0.6.0 | 0.6.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.6.0 (hermes-agent 0.15.2) |
| 0.5.3 | 5 | 0.5.3 | 0.5.3 (dsh >=0.2.0-rc.2 <0.3.0) | 0.5.3 (hermes-agent 0.15.2) |
| 0.5.2 | 5 | 0.5.2 | 0.5.2 (dsh >=0.2.0-rc.2 <0.3.0) | 0.5.2 (hermes-agent 0.15.2) |
| 0.5.1 | 5 | 0.5.1 | 0.5.1 (dsh >=0.2.0-rc.2 <0.3.0) | 0.5.1 (hermes-agent 0.15.2) |
| 0.5.0 | 5 | 0.5.0 | 0.5.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.5.0 (hermes-agent 0.15.2) |
| 0.4.0 | 4 | 0.4.0 | 0.4.0 (dsh >=0.2.0-rc.2 <0.3.0) | 0.4.0 (hermes-agent 0.15.2) |
The DSH peer range is enforced by the harness compatibility check; the Hermes row names the version the provider
tests ran against. The binary and the adapters check each other's release: the DSH and Hermes plugins ask
memcue version -json at startup and turn the session card off with one warning on a binary older
than 0.4.0 (search keeps working), and the Claude Code SessionStart hook checks memcue version -minimum 0.4.0 and
prints the same hint. A build from source reports a suffixed version and is treated as the current tree. Install the
plugin and the binary from the same release.
Install
-
Optional: install the binary yourself and put
memcueonPATH. Download it for your system from Releases, or build it:go install github.com/juev/memcue/cmd/memcue@latestWithout this step the plugin installs the binary of its own release (see Binary from the plugin).
-
Install the Claude Code plugin (hooks and skill) from this repository:
/plugin install memcue --marketplace juev/memcue -
Turn off Claude Code's built-in memory, so the agent does not keep a second memory in files. Add to
~/.claude/settings.json:{ "autoMemoryEnabled": false }or set
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1. With the plugin on, each session starts with a short note that tells the agent its memory is memcue and to write it. memcue does not change the setting itself: while neither is set, the session start says that built-in memory is still on (#494). -
Optional: bring over what the built-in memory already holds. Each note goes through the model once; a note already in the base is skipped, so the command can be repeated.
memcue migrate -list -all # what would be migrated memcue migrate -all # migrate; -j sets how many notes run at once (default 4) -
Optional: if the base is in a git repository (for example
~/.claudeis one), set up the merge driver on every machine before the base is first committed:memcue installIt registers the driver in the repository's git config and routes the base to it: a text history gets
facts/*/*.jsonl merge=memcueinmemory/.gitattributes, next to the text, and a base of the old format getsmemory/facts.db merge=memcue diff=memcuein the root.gitattributes. The driver is a small script in.gitthat finds the binary throughMEMCUE_BIN, thenPATH, then the path it had whenmemcue installran, so an upgrade that moves the binary does not break the next merge. Without a repository memcue works the same; the base is just not synced. A merge carries no vectors. A machine whose base is in git without the driver hears it on the session card, Hermes and DSH included, untilmemcue installruns there. When two machines change a fact apart, the merge keeps every version and the latest by creation time is current, with one exception: a correction of the text wins over a retirement made without knowing it, whichever is later (#544). A retirement made after the correction was merged, or one against a change of keywords or labels only, still retires the fact.
The note file and the base
A built-in memory note file (MEMORY.md of Claude Code, MEMORY.md and USER.md of Hermes) is injected whole at
every turn, while a fact costs nothing until a search returns it. So the note file carries only what must hold before
any search can run; everything else is a fact. The Hermes mirror copies every note into the base on its own, which
means most entries end up in the file and in the base at once. memcue notes shows which:
memcue notes # one line per entry: ok ID kind/scope, or new with its size, then a summary
memcue notes -dir ~/.hermes/memories -json
It reads every *.md directly inside -dir (default MEMCUE_MEMORY_DIR, else ~/.claude/memory), splits the files
into entries at a blank line or a §, and compares each entry with the live facts by a word rule: at least five
shared words, making up at least 0.30 of the words of both or 0.40 of the shorter one; -min-share replaces both
shares with one, which makes the rule stricter or looser. It writes
nothing and calls no model. An entry marked ok can leave the file; a new one moves with
memcue add -kind K -scope S -- "TEXT" first.
Do not empty the note file before the session card has a budget of its own, MEMCUE_CARD_BUDGET over the stated
rules too (#487): without it the card grows by exactly the characters the file lost, because the card lines left out
as copies of the notes come back.
Binary from the plugin
All three plugins (Claude Code, DSH, Hermes) find the binary in this order: the one configured explicitly
(MEMCUE_BIN, or binary in the DSH and Hermes config); memcue on PATH when it is the plugin's release or newer;
the copy the plugin installed; an older memcue on PATH, so search keeps working. When none of them is the plugin's
release, the plugin downloads that release from GitHub, checks the archive against the release's checksums.txt,
and installs it:
| Host | Where | When |
|---|---|---|
| Claude Code | ${XDG_DATA_HOME:-~/.local/share}/memcue/<version>/memcue | in SessionStart, before the first answer of the session; the directory goes on the session's PATH, so the skill finds memcue too |
| DSH | the same directory, shared with Claude Code | in the background at plugin start |
| Hermes | $HERMES_HOME/memcue/bin/<version>/memcue, which outlives the container | in the background at the first initialize |
A configured binary is never replaced. Set MEMCUE_AUTO_INSTALL=0 to install nothing; the plugin then says that the
binary is missing, as before. The checksum guards against a broken download, not against a replaced release:
checksums.txt comes from the same release. Claude Code logs failed installs to
${XDG_STATE_HOME:-~/.local/state}/memcue/install.log and tries again after an hour, since SessionStart waits for
the download; DSH and Hermes download in the background and log failures as warnings. The Claude Code script
needs curl, tar and sha256sum or shasum. A binary installed this way is not on your shell's PATH: to run
memcue install or other commands by hand, call it by its path or add the directory to PATH.
Projects
A fact's origin is <project key> <date>, and a fact that holds only in one project has the scope of that key. The key
comes from where the session works, read from the files and never by running git (#343):
- A git repository is named by its remote, normalized to
host/path:git@example.com:owner/name.git,ssh://git@example.com:443/owner/name.gitandhttps://example.com/owner/nameare allexample.com/owner/name. The remote namedoriginwrites the key, or the only remote when there is noorigin. A session holds the keys of all its remotes, so a fact learnt in a fork'supstreamor on a mirror's other host holds in it too. Remotes are read from the repository's config (the main repository's for a linked worktree, plusconfig.worktreeunderextensions.worktreeConfig), with theurl.<base>.insteadOfrewrites of that config and of the global one applied;[include] pathis followed,includeIfis not. The directory's own name plays no part. - A repository without a remote is
dir:<path of its main worktree>. - Outside git the project is the
moduleof the nearestgo.mod, or elsedir:<working directory>. - No working directory is no project: a host passes an empty
cwdfor such a session (-cwd ""toadd). The Hermes plugin does so for a session outside git (#347); a session Hermes gives nocwdtakes the directory Hermes falls back to (#354). - The Claude config directory is the global tier; a temporary directory names nothing.
The agent chooses only whether a fact holds in the repository (-scope repo), everywhere or on a host; memcue writes
the key. A fact written with no scope gets the project its origin names, and global when the origin names none
(#442): an empty scope is in no place a search asks for, so only -in all found such a fact.
The project narrows and orders memcue search. In a session that works in a repository, search looks at that
repository's facts (those learnt in it or scoped to it, repo:<key>), this host's and the global ones by default
(#418). Outside a repository, and in the Hermes tool, the default is the whole base. -in all searches every
repository, -in project searches this repository only, and -in host, -in global or a scope such as
-in repo:KEY name another (#409); a key may be the end of the stored one, -in repo:memcue, or the tier of an origin,
-in repo:_global (#459). The default was
measured, not assumed: the project's facts alone answered a question about the host with 1 fact where the whole base
gave 5, three of them the host facts that answered it, and 255 of the 391 live facts of that base carry the host scope
(scope-418-2026-10) — so the host and the global facts are in the default, and a
fact of another repository still needs -in all. What the wider default costs in precision on a query corpus is not
measured; the 35 queries of #409, where the project alone left 22 useful facts against 32 for the whole base, are the
reason it is wider. An agent that finds nothing here searches again with -in all. Of facts with equal scores, one
learnt in the session's project comes before a global one and the rest.
search -cwd DIR names the session's directory; the default is the working directory.
The shelf holds what a session read from the outside world: another system's limits, versions and terms, wanted
only when the same subject comes up again (#433). Such a fact is stored in the same base under the scope
shelf:<slug>, and in practice there is one shelf, shelf:research. The agent puts a fact there itself:
memcue add -shelf research (the shelf parameter of memcue_add in Hermes and DSH) is -scope shelf:research;
a slug is [a-z0-9][a-z0-9-]{0,63}. Until #494 the extraction shelved a fact by the tool its quote came from.
A shelf is read only when a search names it: -in shelf:research, or -in shelf for every shelf. Every other place
leaves the shelves out, -in all included: a page read once is noise in a search about anything else, and an all
that still read the shelf would hide nothing. Places are a union, so -in all,shelf:research reads every fact off
the shelves and that one shelf, in either order; a shelf the search does not name stays out even when a named place
such as project would reach its fact through the origin (#532). A search that finds nothing, while its words fit a
shelved fact, says where to look: 1 fact on the shelf matches: -in shelf:research. search -json carries each
fact's scope, so a fact from the shelf can be told from the rest, and prints one object,
{"facts": [...], "hints": [...]}, whose hints are the lines the plain answer prints after the facts, this one
included (#453); the Hermes and DSH plugins read both it and the bare array of an older binary.
Upgrade
0.22.1: a search on a text history no longer writes the base (#557)
- An open that finds nothing new writes nothing (#557). Every open of a text history, a search too, saved two stamps of the cache, each a commit with its own fsync. A stamp is now written only when it changes. On the loffe disk a search on a copy of the base takes 16.7 ms instead of 20.2 ms (a file base: 15.9 ms); see text-open-cost-557.
- No schema, flag or format changed: the schema stays 6. From 0.22.0, replace the binary and the plugins. From 0.21.x or older, follow 0.22.0.
0.22.0: the text history, a correction beats a parallel retirement (#530, #552, #544, #549)
- The base is kept in git as text (#530, #552). See the move below. Until
migrate -textruns, memcue keeps working withfacts.dbas before. A cache built from the text is written in one transaction: on a copy of the Mac base a build from scratch takes 1.6 s. - A correction wins over a retirement made without knowing it (#544, #549). When two machines merge, a fact corrected on one and retired on the other stays active with the corrected text, whatever the clocks say. The rule judges by the versions each side had seen, so it holds when a side merges the result of an earlier merge. A change of keywords alone does not bring a retired fact back.
- No schema changed: the schema stays 6. New command:
memcue migrate -text. From 0.21.x, replace the binary and the plugins on every machine, then move. From 0.20.0 or older, first follow 0.21.0.
Moving to the text history
A base of the old format is one SQLite file, and git stores the whole file again at every commit: on one machine
that came to 354 MiB of loose objects in ten days (git-growth-530). The
text history keeps one JSON-lines file per fact, memory/facts/<last character of the ID>/<ID>.jsonl, a line per
version, so a commit stores only the facts that changed and git log -p shows the history of a fact. Search still
runs on SQLite: the base becomes a cache, facts.db in the state directory, outside git. Every write goes to the
cache and to the file of the fact. An open reads the directories git changed and takes their versions into the
cache; a missing cache is built from the text. A fact changed on two machines is merged by the same driver and the
same rules as before.
The session card of a synced base of the old format suggests the move. Do it in this order:
- Install this memcue on every machine that syncs the base, and run
memcue installthere. - Stop the sessions, dreams and gateways that write to the base, and sync every machine.
- On one machine, run
memcue migrate -text. It writes the text next to the file (facts.dbbecomesfacts/), copies the base into the cache, removes the file and routes the text to the driver. - Commit
memory/(the text,memory/.gitattributesand the removal offacts.db) and push. - Pull on the other machines. A pull removes their
facts.db, and the next open builds the cache from the text; no command is needed.
MEMCUE_FACTS and the facts option of the plugins may keep naming facts.db: once the facts directory exists
next to it, memcue opens the text. A base outside the default keeps its dream schedule and logs: its state directory
moves to the key of the new path. A second migrate -text is safe; if a facts.db is left next to the text, it is
merged into the text and removed. To go back, check out the commit before the move on every machine and install the
older memcue.
0.21.2: a shelf named beside all, correct ID -- TEXT, no doubled versions after a merge (#532, #533, #540, #536)
-inplaces are a union with the shelves too (#532).-in all,shelfand-in all,shelf:research, in either order, now read the named shelf; before,allended the list and the shelf was silently dropped. A named shelf no longer lets the other shelves in:-in project,shelf:researchused to return facts of every shelf learnt in the project through their origin. A place afterallis checked, so-in all,bogusis an error.-in allalone and the default still leave every shelf out.memcue correct ID -- TEXTstores TEXT (#533). A lone--right after the ID is a separator, as inadd; before, it was stored as the first word of the fact. A text that starts with the word--is writtenID -- -- TEXT, andID --with no text is a usage error. The plugins pass the text as one argument and are not affected.- A repeated merge no longer doubles a version (#540). A machine that had not pulled an earlier merge and wrote again got a copy of the other machine's correction as a new version at its next merge. Copies that earlier merges already wrote stay in the history; the current state of a fact was right all along.
- The Hermes dashboard shows the provider as ready (#536). The plugin manifest no longer declares
external_dependencies: the dashboard ran itsmemcue versioncheck with its ownPATH, so a binary set inmemory.memcue.binarymade a working provider look unavailable, and its install button could only fail. Readiness now comes from the provider. - No schema or flag changed: the schema stays 6. From 0.21.0 or 0.21.1, replace the binary and the plugins. From 0.20.0 or older, follow 0.21.0.
0.21.1: the Hermes provider tests run in a standalone install (#526)
- Only tests and this README changed.
tests/test_provider.pyof the Hermes plugin no longer reads the DSH and Claude Code plugin sources, so it runs in an installedplugins/hermes/memcueon its own;plugins/test_cross_harness.pykeeps that check in the full repository. The binary and the plugins behave as in 0.21.0. - The backup and the rollback of 0.21.0 are a checklist now (#527). The 0.21.0 text copied the base with
cpand restored that copy, which is not safe; see 0.21.0. A copy made that way may lack committed writes or hold half of one: check its integrity and schema before relying on it. - No schema or flag changed, and no new migration comes: the schema stays 6. From 0.20.0 or older, stop every older memcue, back the base up and upgrade as 0.21.0 says; its rollback applies too. From 0.21.0, replace the binary and the plugins.
0.21.0: no fact waits for approval; schema 6 (#514, #456, #521, #522)
-
Breaking: no fact waits for approval.
memcue add -candidate,memcue candidatesandmemcue approveare removed:addrejects the flag, and the two commands exit 1 and say why. A fact written withadd,memcue_addor a Hermes memory mirror, and every factimportstores, goes into the search index in the same transaction. A search returns it only when it fits the search: see Use for the places a search reads, the shelves, expiry and the-nlimit. -
Breaking:
importignores"review". A record with"review": true, or an older file whose facts carry a kind, no longer makes candidates: the facts are stored live with their kind, scope, source, evidence and prompt version.migrate -dry-runstill writes"review": false, so an older memcue imports its output as live facts too. -
Candidates and provisional facts already in a base are live. A fact whose latest version is active is indexed, listed by
memcue factsand read by the dream, whatever its review state. Its rows are not rewritten: it keeps its versions, origin, kind, scope, source, evidence and expiry, andmemcue dumpstill showsreview=candidateorreview=provisional. The session card takes it on the same terms as any fact (see Use). The next version memcue writes for it (correct,reopen, a dream, a split) drops the state and keeps the expiry;correct -no-expiresclears the expiry as for any fact. Recall still ranks a row that is provisional after the approved ones. -
The upgrade brings back nothing retired or expired. It writes no fact row, so a fact whose latest version is retired stays retired until
memcue reopen ID. A candidate or provisional fact past its expiry is handled as any expired fact, whatever its review state and before or after acorrect: search, recall and the card leave it out;search -expiredfinds it within the scope the search reads (-in, see Use);memcue factslists it. The dream reads it as it reads an expired approved fact: the packet may ask to split it, expand its queries or translate it, but it never joins a group as another fact's neighbour, because the dream finds neighbours by recall. -
The merge is unchanged: the latest version wins. When two machines change a fact apart, the merge keeps the versions of both and the one created last is current, whatever the review state. So a retire on one machine and a correction on another resolve by their times: a correction created after the retire brings the fact back, active and searchable, with the correction's text; one created before it leaves the fact retired. A correction written by 0.20.0 or older may keep
review=candidateorreview=provisional; the new memcue indexes that active fact all the same, andmemcue dumpshows the state. Both stay in the history, andmemcue retire IDormemcue reopen IDsettles it again. A retire carries the text its machine saw, so a reopen after such a merge brings that text back, not the correction. This is how a merge resolved any two concurrent changes before #514 too; a merged-in correction of a fact retired elsewhere is the one way a retired fact comes back without a reopen. -
Breaking: the schema is 6. Opening a version-5 base rebuilds the search index once and writes no fact row. memcue 0.20.0 and older refuse a version-6 base with
fact base has schema 6, this memcue knows 5: update memcue, for every command that opens the base,memcue dump(the git textconv) and the merge driver included. -
Upgrade with no older memcue running. A memcue 0.20.0 process that opened the base before the upgrade keeps working on it: its searches still leave the candidates out, and its index rebuild takes them out of the shared index. The next open by the new memcue notices this and rebuilds the index, but a process of the new memcue that is already open searches the damaged index until then. This README gives a checklist, not commands: no backup or restore procedure is shipped or tested, so adapt each item to your environment and tools. The migration needs a global barrier: do not let any memcue open any copy of the base until steps 1 and 2 are done on every machine that shares it.
- On every machine, stop every client of the base (the Hermes gateway, cron jobs and running dreams, agent sessions) and pause its synchronization (a git cron job, Syncthing and the like).
- On every machine, determine the exact database file from the configuration the services use, and the target if
it is a symlink. The defaults differ: the CLI opens
MEMCUE_FACTS, else$CLAUDE_CONFIG_DIR/memory/facts.db(~/.claude/memory/facts.db), and the DSH plugin falls back to it after itsfactssetting; the Hermes plugin opens itsfactssetting, elseMEMCUE_FACTS, else$HERMES_HOME/memcue/facts.db. Check that no process has the file open. - Back it up with a trusted SQLite-aware tool, one that takes in committed WAL content and recovers an interrupted
write left in a hot journal. Run
PRAGMA integrity_checkon the snapshot and read its schema withPRAGMA user_version. Expect 5 only from 0.20.0 back to 0.5.0; an older release may have an earlier schema (see Compatibility). Record the schema and the release that wrote the base: a rollback needs both. - Store the backup apart from the synchronization: outside the git repository of the base and every synced directory.
- Only when every machine has passed steps 1 to 4, install the new binary and the plugins of the same release, then resume the synchronization and start the services again.
Copying
facts.dbalone is not a backup: with a-walor-journalfile next to it, the copy may lack committed writes or hold half of one. -
Rollback is the backup. No in-place downgrade is provided or tested, and 0.20.0 or older cannot open a schema-6 base. Every fact written or changed after the backup is absent once you roll back, and candidates are hidden again. The rollback needs the same global barrier: every client and the synchronization stopped on every machine before any copy is touched.
- On every machine, keep a separate backup of the current schema-6 base, made and verified as above but with schema 6.
- If you want the newer facts, export them with the 0.21 binary before the older one is installed:
MEMCUE_FACTS=<path> memcue facts, ormemcue dump <path>, where<path>is the exact file from step 2 above. - Move the base and its
-journal,-waland-shmfiles aside; do not delete them. SQLite applies a sidecar left in place to the restored file. - Install the release that wrote the backup, the one whose schema matches it, and restore that verified backup to the exact path the services open. Before restarting the services, check that path and that no memcue process runs.
- Only when every machine has restored the same backup, resume the synchronization and start the services again.
Restoring a copied
facts.db, deleting the sidecars or just restarting on the old binary is not a safe rollback. -
The Hermes card keeps lines that repeat the notes (#456). The provider no longer drops a card line that shares enough words with
MEMORY.mdorUSER.md: it puts the card in as the binary renders it. No flag or schema change comes with this. -
The Hermes note covers searching during work (#521). The note and the
memcue_searchdescription let the model search, when it decides it may help, on starting with a specific host, service or repository and before it diagnoses an unexpected error, with a short query naming the system and the problem, checking the result against the current state. Text only: no search runs on its own, and the binary, DSH and Claude Code are unchanged. Whether the text changes what the model does has not been measured. -
The Claude Code session-start note covers searching during work (#522). A new line of the
session-startnote lets the model search, when it decides it may help, on starting with a specific host, service or repository and before it diagnoses an unexpected error or result, with a short query naming the system and the problem; a fact found this way is a lead to check against the current state. The note grows by about 350 characters and still fits both the one-block text and part 1 of the split start. Text only: no search runs on its own, andsession-card, Hermes and DSH are unchanged. Whether the text changes what the model does has not been measured.
0.20.0: add writes procedure and event facts (#505)
memcue add -kind procedurestores steps the user asked to remember;search -kind procedurefinds them. Until now such a fact had to go in asenvironment.memcue add -kind eventstores what happened that the next days need. It expires afterMEMCUE_EVENT_TTL_DAYS(default 7) unless-expiresgives a date; before,addrejected the kind and the expiry of #287 never applied.repoandotherare still refused byadd. Neither new kind reaches the session card.- The session-start note, the skill and the
kinddescription ofmemcue_add(Hermes, DSH) say when to use them. - The schema is unchanged.
0.19.0: the agent writes its memory itself; extraction and vet are removed (#494)
- Breaking: nothing is extracted from sessions. The agent stores a rule or a correction of the user, a decision
the code does not show, a pitfall or an external pointer with
memcue add(Claude Code) ormemcue_add(Hermes, DSH) when it learns it; the session-start note, the skill and the tool descriptions say so. The Claude Code plugin has no PreCompact or SessionEnd hook any more, the Hermes provider and the DSH plugin send no messages to the binary. - Breaking:
memcue vetandmemcue extractare removed, andMEMCUE_EXTRACT,MEMCUE_VET,MEMCUE_VET_POLICYandMEMCUE_VET_TTL_DAYSare no longer read.hook-extract,extract-sessionandextract-messagesstay as commands that read their input and exit 0, so an older plugin logs no failure. Candidates the extraction left wait formemcue approveormemcue retire; a provisional fact is found until its expiry. #514 later removed the approval: those candidates are live unless retired or expired. - Turn the host's built-in memory off yourself:
"autoMemoryEnabled": falsein Claude Code's settings, andmemory.memory_enabled: falseandmemory.user_profile_enabled: falsein Hermes's config. memcue does not change them; it says so while they are on, and a Hermes note file the memory no longer injects stops filtering the card (#502). #456 later removed that filter: the Hermes provider no longer drops a card line that shares enough words withMEMORY.mdorUSER.md, and puts the card in as the binary renders it. memcue_addof Hermes and DSH takes asource: a rule or decision is the user's by default, so it reaches the session card, which theagentsource these tools always wrote never did.addrefuses installation state written with-source toolas well as-source agent(#475).- Hermes: the dream starts from a line of the card in the system prompt (
session-card -dream-tools), which the model answers withmemcue_dream_prepareandmemcue_dream_apply; the provider no longer runsdream -run. Theextract_*,shelfand oldsessions/,pending/state go; the latter are removed at start. - DSH: the
extract,minNewChars,stateDirandmaxToolResultCharssettings go; a profile that sets them still loads. - The schema is unchanged.
0.18.1: the Claude Code session start carries every stated rule again (#495)
- In 0.18.0 the budget of #487 also cut the stated rules and decisions at the Claude Code session start, against #480:
on the author's base the card fell from 53 lines over two parts to 22. Now
session-start -part Nkeeps the stated facts outside the 3,500-character budget and bounds them only by the three parts; the other facts share the budget. session-card(Hermes, DSH) andsession-startwithout-partkeep the budget of #487 over the stated facts.- No schema or flag changed.
0.18.0: a card with a ceiling, in three parts, and a report of the notes already in the base (#475, #476, #477, #480, #481, #487, #488, #489)
memcue notesreports which entries of the built-in memory note files are already facts, with their ids, and which are not (#489). It reads the notes, calls no model and writes nothing; an entry markedokcan leave the file, anewone moves withmemcue addfirst.- The session card's budget bounds the stated rules too (#487).
MEMCUE_CARD_BUDGETsets it, 3500 characters by default. Before, a fact the user stated was placed outside that room, and the card could grow without a ceiling; now the oldest stated fact goes first, and a card the budget cuts ends with a line that counts what it left out.card.logrecordsbudget,usedandomitted. - The Claude Code plugin reads the session-start text in three parts,
session-start -part 1to-part 3, oneSessionStarthook each, at most 9,500 characters a part (#480). Claude Code keeps a hook's text of 10,000 characters or more out of the context and shows a 2 KB preview, so in the busiest repositories the card never reached the model.session-startwithout-partprints the whole text, as before. - The Hermes provider option
session_card_charspasses that budget through asMEMCUE_CARD_BUDGET, and its memory mirror labels a note with its kind and scope instead ofglobalwith no kind (#488). memcue_searchrefuses a limit of 0 instead of serving the default (#477); a flag after the query text steers the search (#481).memcue vetretires the state moments of live facts only (#475).- Hermes cron runs feed the shelf through
extract-messages -shelf-only(#476): what a research job read stays on the shelf, and nothing else from the run reaches the base.
0.17.0: every fact in a place, -json carries the hints, vet clears what code can tell is gone (#442, #453, #454, #456, #457, #458, #459, #467)
- Breaking:
search -jsonprints one object,{"facts": [...], "hints": [...]}, instead of a bare array (#453).hintsare the lines the plain answer prints after the facts: the shelf hint, the CJK hint, the-in allline of an empty answer in a repository. The Hermes and DSH plugins of this release read both shapes and hand the hints to the model; any other consumer of-jsonhas to readfacts. - A fact written without a scope gets one (#442): the project its origin names, or
globalwhen the origin names none. An empty scope was in no place a search asks for, so only-in allfound such a fact.memcue vetgives the same scope to the facts stored without one before. - vet retires by rule what code can tell is gone (#457): a live fact past its expiry, and installation state of an
older memcue (a fact of no kind or
environmentthat says which version is installed or where a binary lies, every version it names older than the running release). A fact that names an old version as a change of behaviour stays. - Mirrors of the memory notes queued before 0.16.0 are decided (#458): the review after an extraction takes them
along, a mirror of a rule the base already holds word for word is dropped, an approved one becomes
global. The Hermes plugin names the notes directory for the extraction (MEMCUE_MEMORY_DIR), so the mirror rules run there. - The extraction reads the memory notes (#456) and leaves out what they already say, so a new fact does not restate a note in other words; the facts stored before stay until the dream merges them.
-in repo:KEYtakes a key the base knows only as an origin tier, such as_global(#459);search -nbelow 1 is an error instead of an empty answer that said the base held nothing (#454); a search no longer reads the stored repository keys unless-innames one, 12.7 → 9.9 ms end to end on a live base (#467).
0.16.0: a shelf for what a session read outside, a card that says the scope once, a mirror searchable at the write (#433, #437, #440)
- Facts read from outside sources are kept on a shelf. A fact whose evidence is quoted from the result of a tool
that reads the world outside — a name starting with
web_,browser,fetch,http,curl,arxivorpdf— is stored under the scopeshelf:<slug>and read only by a search that names it:-in shelffor every shelf,-in shelf:researchfor one. Every other place leaves the shelves out,-in allincluded, because anallthat still read the shelf would hide nothing. A search that finds nothing while its words fit a shelved fact says where to look (1 fact on the shelf matches: -in shelf:research), andsearch -jsonnow carries each fact'sscope. The decision is provenance, not a model:extract-messages -shelf researchshelves a fact only when its quote is in an outside tool's result and in no other tool's result; a fact in the user's words is never shelved; without the flag (the Claude Code path) nothing changes. The Hermes plugin passes-shelf researchby default (shelfin its config, empty turns it off) andmemcue_addtakesshelf. By hand,add -shelf researchis-scope shelf:research. - The session card says the scope and the dates once per group, not on every line: a card of twelve lines gave 324 of its 2 338 characters to the same suffix. The facts are unchanged; only the heading carries what the lines shared.
- A memory note is searchable when it is written. Every write of the built-in memory tool was mirrored as a candidate, and a candidate has no index row: between the write and the next vet run the user's latest memory was invisible, and an older approved fact answered in its place. A mirror is now approved at the write, and a stale one is retired by rule, without a model call.
0.15.0: search listens to a message, the card stops repeating the memory, and installation state stops being a fact (#417, #418, #421, #423, #424, #425, #426)
- A thin answer says what it left out. When one fact is all the score floor allows, the Hermes
memcue_searchadds a hint naming the lever (min_score 0) and how many facts hold the same words below the floor; the tool description now says that fewer facts thanlimitmeans the floor or the scope, not that the words are wrong. The hint costs one more search on those answers only (p50 8.9 ms, ~150 characters). Measured on 9 thin queries with one model: 7 of 9 reached an answer inside three turns against 3 of 9 without it, and tokens per answered question fell 44 % (thin-answer-hint-2026-10). The DSH tool is unchanged. - A message is searched as a message (#417). A query of more than eight words is a user's message, not a search: its floor is 0.45 of the best fact instead of 0.6, and a fact has to hold two of the message's terms, because one shared word with a message of dozens usually means something else. On the same twenty user prompts the answer keeps 17 useful facts instead of 11 and loses 2 instead of 8; the eight agent queries return exactly what they did before. The unrelated facts of a message rise, 23 to 27 of the delivered set — the BM25 tail of a message is flat, and the write-up says why a floor cannot separate it (message-floor-417-2026-10).
-in repo:KEYaccepts the end of the stored key, and the default of a repository takes the host and the global facts (#418).-in repo:memcuefindsrepo:github.com/…/memcue; a key that matches nothing fails with the stored keys named instead of answering an empty result. Inside a repository the default is now that repository, this host and the global facts: on the measured base the project alone was 1 of 391 facts while the host scope held 255, and a question about the host went answered by the project or not at all (scope-418-2026-10).- The thin-answer hint counts the tail (#423): its wide call asks for
min(4 × limit, 20)facts, so the number it prints is the size of the tail and no longerlimit − served— 19 more on the query that used to say 4 (thin-hint-tail-423-2026-10). - The session card leaves out what the memory block already says (#424): a mirror of a built-in memory note, a
line whose opening repeats one already on the card, and a line the memory notes state are dropped;
card.loggainsexcluded. On the live card 16 lines and 3 181 characters became 11 and 1 995, at every session start and after every compaction (card-memory-copies-424-2026-10). #456 later removed the third rule, the plugin's filter against the built-in memory notes; the binary's filters are unchanged. - Five facts stay the search default (#425): the higher limit adds the tail — 40 facts on twenty prompts, one of them useful — for 62 % more characters, so the schema says when to raise it instead (limit-default-425-2026-10).
- Installation state is not a fact (#426): the extraction prompt and the review prompt no longer invite the version
of something installed, the path of a binary or a wrapper, a config default or the size of a base, and
vetdrops such a candidate before the duplicate check. 13 of 386 approved facts of the live base match the rule and are retired in a separate pass (installation-state-426-2026-10). - No schema change: 5, as in 0.14.0. The scope default and the message floor are search-side; the plugins pass the same flags as before.
0.14.0: a dream over the whole base, and search hands over only what fits and where the agent asks (#405, #407, #409, #414)
memcue dream -run -allanswers packet after packet until the base has no work left, so the translations of 0.12.0 need not wait for three days of hourly dreams. It uses the endpoint whenMEMCUE_API_URLis set andclaude -ponMEMCUE_MODEL(default Opus) otherwise. It stops when the work is done, after three passes that change nothing, or after 200 passes, anddream.logrecords each pass with its cost (#405).memcue search -nis now a maximum, five by default. A fact whose BM25 score is under 0.6 of the best fact's is left out;-min-scoresets another share,-min-score 0turns the floor off. On 35 queries that agents wrote, the floor removed a third of the unrelated facts, kept a useful fact for every query that had one and cut the characters per search by 17% (search-floor-2026-10, #407). A search that finds nothing prints "No stored facts match." The session note and the search tools of the Hermes and DSH plugins say so.- The agent steers search (#409):
-inwhere to look (project,all,global,host, or a scope such asrepo:KEY),-kind,-since,-expiredand-all-words; the Hermes and DSH search tools take the same asin,kind,since,expired,wordsandmin_score. In a repository search now looks only at that repository's facts by default, and a text answer that finds nothing there says that-in allsearches every repository. Outside a repository, and in the Hermes tool, the default stays the whole base. Expired facts leave the default result. See Projects for what the narrow default costs on measured queries. - A query in Chinese, Japanese or Korean finds facts only through the Latin names in it: the facts are in Russian and
English. The session note and the search tools now say to write the query in one of them, and
searchadds that hint to the answer of a CJK query, found or not (hintin the Hermes and DSH tools). A Chinese question translated first finds its fact 54–60 times in 60, against 0–50 as it is (chinese-2026-10, #410, #414). - No schema change. The plugins pass the new parameters only to a 0.14.0 binary.
0.13.0: Hermes runs the dream itself (#401)
memcue dream -runruns a whole dream through the extraction's endpoint (MEMCUE_API_URL). It checks what the Claude Code session start checks,MEMCUE_DREAM_EVERYand fresh or untranslated facts, and makes no model call when no dream is due. The answer goes through the same checks as-apply, anddream.logrecords the tokens. WithoutMEMCUE_API_URLit refuses: on Claude Code the dream stays with the session's model.- The Hermes plugin starts
dream -runafter each extraction, so a Hermes base gets its dream, and the translations of 0.12.0, without a cron job.dream: falseinmemory.memcueturns it off. - The endpoint model reasons over the packet. Without reasoning, deepseek-flash retired a fact it should have kept, and on a large packet its answer was cut at the token limit; with reasoning a dream takes 1.5–2.5 minutes and 22,000–29,000 output tokens in the background (dream-run-2026-10).
- No schema change. Upgrade the binary and the plugin together: the plugin starts
-runonly with a 0.13.0 binary.
0.12.0: a question in either language, and a base that brings itself up to date (#390, #391, #395, #396)
- Translation. Every fact keeps its whole text in the other language, Russian or English, as one search query
that starts with
=(#395). Before, an English question found a Russian fact 22 times out of 30, and every other combination 29–30. With the translation it is 30 out of 30, 23 of them at rank one instead of 12, measured on a copy of a live base of 2,855 facts (language-2026-10). The session extraction writes the translation with each new fact, for about 4% more per session. Facts stored before get theirs from the regular dream, 40 a dream (#396). A base of 2,855 facts therefore takes about 72 dreams, three days at the hourly default. Search output and the card do not change, and the base grows by one version per fact. - Opening the base. Each index row is stamped with the version of the number terms it was built with (#390). An open restems only older rows, including rows written by an older memcue on another machine of the same base. The index check trusts its last count when nothing changed (#396). On the copy above, search took 57 ms on 0.8.0, 90 ms on 0.11.1 and 10 ms here, and the translation does not change that. The first open restamps every row once, in about 0.6 s.
- Numbers after
:and/. An image tag (image:v1.106.1) and a versioned path (gradle/9.8.1/) give a term (#391). On the copy above, 15 of 15 version-only prompts now find their fact, against 13 before. - The card names what this machine still owes. That is a missing merge driver (
memcue install) or old repository names in the facts (memcue migrate -repos). The line reaches Hermes and DSH too (#396). - No schema change and no new flag.
reindexis a diagnostic again, and nothing has to be run by hand after the upgrade.
0.11.1: the index repair asks a cheaper question
- 0.11.0 asked every live fact carrying a digit whether the lexer would give it a number term by stemming it, on
every open of the base: search went from 17.0 ms to 39.7 ms end to end.
text.HasNumberTermanswers the same question with one regexp match and the latest version per id is a grouped join, which brings it to 23.5 ms on the same prompts. The repair itself is unchanged — a base with 60 numbered rows and 2 without is repaired on the first search and answers both version-only queries. - No schema change, no new flag: only the cost of the check.
0.11.0: a partly numbered index is repaired, and reindex (#383, #384)
- The index is rebuilt on the first open when a live fact whose text carries a version, date, port or address has no
number term in its row. 0.10.0 only noticed an index with no term at all, so a base indexed by an earlier release
kept word-only rows for the facts it already had: on a live base two facts were unreachable by the version alone
and reachable as
topic + version. The check stems only the facts whose text carries a number, so opening a base still costs what it cost. memcue reindex [-dry-run] [-json]reports live facts, index rows and rows carrying a number term, then rebuilds.-dry-runreads through a read-only open, so it does not repair what it measures.- An address after an
@(dig @1.1.1.1) is one exact term, as versions and dates already were; it never matches a neighbour address. memcue searchhands over five facts by default, as both plugins do, instead of ten (#384).-n Nstill overrides.- No schema change: bases stay at schema 5, and the repair writes only
fact_index/fact_fts.
0.10.0: versions are terms by default, and the dream sweeps old facts (#376, #377)
MEMCUE_NUMBER_TERMSis on unless it is0(false,off,no): versions, dates, ports and addresses are one exact term, so a prompt naming a version finds the fact naming it. A base indexed word-only by the previous release is rebuilt once on the first search, with noreindex. On a copy of a live base the share of top-5 facts carrying the queried version went from 1 of 30 to 10 of 30; 10 of 10 ordinary questions returned the same facts (docs/research/number-terms-2026-10.md).- A dream also sweeps up to 50 facts it has already seen, after a cursor in
dream.merge, and adds at most 20 groups of its own inside the packet bound: a duplicate pair whose copies both predate the last dream is reviewed at last.dream.loglists every group by id undergrouped, with its fresh facts and its outcome, so the log says whether two facts were grouped in a run (docs/research/dream-merge-2026-10.md). - Grouping passes a candidate whose own terms the text nearly all holds, below the search's coverage gate; search itself is unchanged.
0.9.0: five facts per search by default (#372)
memcue_searchhands over five facts unless the call asks for another number. On a live base ten facts doubled the characters of a search and reached no prompt five did not, with the useful share falling from 22% to 12%.- The number is the provider option
search_facts(default 5, 1–50); an installation that wants ten sets it back in its own config, no release needed.
0.8.0: the agent searches itself (#361)
- No facts come with a prompt. The Claude Code plugin has no
UserPromptSubmithook; the Hermes and DSH plugins deliver the session card only. The session-start note, the Hermes system prompt and the DSH system prompt tell the agent to search memcue before it answers from earlier sessions. memcue hook,memcue recall(with-explainand-fact) andmemcue evalare removed, withrecall.logand the per-session.recalledfiles. An older plugin that still callshookgets "unknown command": Claude Code ignores it, Hermes and DSH log one warning per session. Install the plugins and the binary of the same release.MEMCUE_RECALL_LOG,MEMCUE_MIN_COVERAGE,MEMCUE_MIN_TERMS,MEMCUE_MIN_MATCHED,MEMCUE_MAX_FACTSandMEMCUE_FOREIGN_COVERAGEare no longer read; drop them from wrappers. The Hermes optionsmin_coverage,max_factsandsuppress_mirroredare ignored.MEMCUE_SEARCH_LOG=1logs every search tosearch.log, the record to judge the agent's searches by.- Every rule and decision you stated is on the card, past its budget (#363). Hermes carries the card in its system prompt instead of the first turn.
- A Hermes session without a working directory (a one-shot or cron run) takes the directory Hermes falls back to, so a run inside a repository names that repository (#354).
- The base is untouched.
0.7.0: recall without a judge, no review page
- Recall uses the words alone unless
MEMCUE_RERANK=judgeasks for Jev (#331): a configured Jev endpoint no longer turns the judge on. A setup that relied on that needsMEMCUE_RERANK=judge. memcue review, the local review page, is removed (#325);memcue candidates,approve,retireandvetstay. #494 later removedvet, and #514candidatesandapprove.MEMCUE_RERANK=localis gone with the tree reader (#324) and now keeps the words.- Run the project-key migration below once on every machine that writes to the base.
Hermes sessions (#347)
The Hermes plugin used to pass the constant hermes as the working directory, so its facts had the origin
hermes <date>, and on the author's base an empty one from 2026-10-23. Now a session in a git repository names its facts by
that repository, and every other session leaves the origin empty. Facts already stored keep their origin; nothing
needs to be moved.
Project keys (#343)
Facts written before #343 carry the directory's name (<name> <date>, repo:<name>), and a session no longer
matches them. Move them once on every machine that writes to the base:
memcue migrate -repos -root ~/src # report only: what maps, what does not
memcue migrate -repos -apply -root ~/src # back the base up, then write the changes
The map comes from this machine's clones: the working directories of the Claude Code transcripts and every repository
under each -root. repo:<owner>/<name> scopes map by their suffix. A name with no clone here, or with two clones
that now have different keys, stays as it is and is listed. So is every key that facts hold and no clone here has: a
repository of another machine, or one whose remote changed, with the clones' keys it may have become. A changed key
moves only by an explicit pair: memcue migrate -repos -apply OLD=NEW. A session in a repository whose old name
facts still carry gets that pair on its card (#396). -apply first copies the base to
backup/facts.db.pre-repos-<time> in the state directory, and writes each change as a new version that keeps the
fact's date, so a base merged from another machine sees it.
Schema 5
The fact base schema is 5: it adds the search queries of a fact, which the index holds next to its words. memcue
0.4.0 refuses a version-5 base with update memcue, because its merge, its writes and its index rebuild would drop the
queries: upgrade every machine that shares the base, the Hermes container included, before the first memcue 0.5.0
opens it. Opening a version-4 base adds the column and rebuilds the index once; memcue dump (the git
textconv) still reads older revisions of the file, with no queries.
Two things a base upgraded from 0.4 still needs. The facts stored before 0.5 have no queries; each dream now gives them
queries, a packet at a time, and memcue dream -prepare -all starts on the whole base. And facts that describe memcue 0.4
itself (its variables, verbs and versions) stay recallable and win lexically, because they are written in memcue's own
words: no dream or vet compares a fact with the release it is about. Find them and decide each one:
memcue facts | grep -E 'MEMCUE_EMBED|MEMCUE_(VET|RERANK|TRIAGE)_(URL|MODEL|KEY)|EMBED_MODE|memcue (embed|label|audit)|0\.4\.0'
memcue retire ID a fact that describes the removed state as current, memcue correct ID TEXT one that is partly
true, and keep one that records what was measured or decided then. Since 0.5.3 vet finds most of these itself (see
Candidates); facts about the host's own config files are still found only this way.
Schema 4 was the schema of 0.3.0 and 0.4.0; 0.2.1 and older refuse a newer base with update memcue: upgrade
every machine that shares the base before the first new memcue writes to it. If you ran memcue install before
0.4.0, run it once more after the upgrade: the merge driver is now a script in .git that survives a binary upgrade,
while an older install pinned the absolute path of the binary it ran from. A mixed-version sync is not safe: an
older binary refuses the version-4 base instead of reading it, and 0.2.1 does not know fact_vectors. Opening a version-3 base creates
fact_vectors and rebuilds the derived index once; version 4 adds the optional vectors on top of the version-3
stems, which keep the word of a versioned name such as PostgreSQL-16.
Use
Nothing to do in a session: the agent searches the base when it needs to and stores what it learns. A fact written
with memcue add or stored by memcue import is indexed at once; nothing waits for approval (see
Candidates). A search returns it when it fits: by default a search in a repository reads that
repository's, this host's and the global facts, and one outside a repository reads every fact; a shelf is read only
by a search that names it; an expired fact only with -expired; and at most -n facts (5 by default) above the
-min-score floor come back. The session card carries a fact only if it is active and unexpired, a global rule, or
a rule or decision scoped to the session's repository, and its source is not memory; it fits the card budget
(MEMCUE_CARD_BUDGET). memcue correct writes a new version and memcue retire takes a fact out. By hand (add without -origin names the project of the working directory and
today; in a temporary directory the origin stays empty, because a scratch directory says nothing about where the fact
came from). See Projects for how a project is named. A fact of kind: event gets an expiry of MEMCUE_EVENT_TTL_DAYS days (7 by default) unless -expires
says otherwise: what happened in one run is not the knowledge a later session needs:
memcue search -json postgres port # the facts that fit, best first, with their IDs (-json: {"facts", "hints"}; at most -n, default 5)
memcue search -min-score 0 postgres # every fact up to -n, the floor off
memcue search -in all postgres # every repository; the default in a repository is its own, this host's and the global facts
memcue search -in shelf:research rate limit # facts read from pages and documents, which no other place returns
memcue search -in all,shelf:research rate limit # places are a union: every fact off the shelves and that shelf
memcue search -kind rule,decision -since 2026-10-01 release # only rules and decisions learnt since that day
memcue search -all-words -expired postgres port # every word required; facts past their expiry included
memcue add -keywords "demo, postgres" -origin "demo 2026-10-19" "The demo database is PostgreSQL 18 on port 55432."
memcue add -expires 2026-10-21 "Check how the nightly dream ran on 21 October."
memcue correct -expires 2026-10-28 ID "…" # -no-expires clears it
memcue correct ID "The demo database is PostgreSQL 18 on port 55433."
memcue correct ID -- "…" # a lone -- after the ID is a separator, as in add; -- -- keeps a leading "--"
memcue retire ID # no longer recalled; history keeps it
memcue reopen ID # undo a retire: the fact is searchable again
memcue facts # every active fact, with expires= when it has one
memcue notes # which entries of the built-in memory note files are already facts
memcue reindex -dry-run # index rows against the live facts, and how many lack their number term
memcue reindex # rebuild the index from the live facts (-json for one JSON object)
memcue dream -since 2026-10-19 -list # groups of similar facts a dream would review
memcue dream -prepare # the work of a dream, for the session's model (-all: the whole base)
memcue dream -apply answer.json # check and carry out the model's answer to it
memcue add and memcue correct refuse text that looks like a token or a password; -force stores it anyway.
migrate and import skip such facts and report them without echoing the value, because the base may be synced
through git.
A fact is found by a question in either language. The dream stores, next to the search queries, the whole fact in
the other language, Russian or English, as one query that starts with = : search reads it, while the card and the
search output show the fact as it was written (#392, #395); it gives a translation to 40 facts a dream (#396). Until
then the keywords the agent writes in both languages are what a question in the other language finds.
Nothing judges a fact on its way in: the agent decided to write it. add refuses only installation state the agent
saw in a tool's output or concluded (-source tool or agent): a version, the path of a binary, a config default,
the size of a base, a restart time or PID, a line count, an mtime. A command reads each of these, and the stored copy
is stale after the next release or restart (#475); a fact in the user's words is stored as said.
What goes stale later is handled in two places. A fact a newer one replaced meets it in the dream, which groups
similar facts and lets the session's model retire or merge the old one (Dream); a fact found wrong in use is
corrected or retired in the session. A fact the world made wrong without anyone saying so is not caught (#494).
memcue vet, its rules, its review model and its check of suspect facts are removed with the extraction they served;
the vet.log, vet.checked, extract.log and extract.err of an older release can be deleted.
The SessionStart card is a view of the active facts, not a copy: a rule or decision leaves it when corrected,
retired or expired. card.log records the ids it carried, so a rule shows as used even though no search found it.
Claude Code keeps a hook's additionalContext of 10,000 characters or more out of the context: it saves the text to
a file and shows the model a 2 KB preview. The limit holds per hook, so the plugin declares three SessionStart
hooks, session-start -part 1 to -part 3, and the binary lays the note, the hints and the card out over them, at most
9,500 characters each (#480). The note and the hints open part 1. A part ends only between lines, and a part that
starts inside a group repeats its heading. A part with nothing to carry prints nothing. Only past all three parts,
about 28,000 characters, does the card leave lines out. The last part then says how many and how to search them,
and card.log counts them as left. session-start without -part prints the whole text in one block, as before.
Every rule and decision the user stated (source user or user_confirmed) is on the card, each cut to 400
characters (#363): since the agent searches itself, the card is the only memory it gets unasked. Where it holds them
depends on the host. At the Claude Code session start they sit outside the budget, bounded only by the three parts
(#480, #495). On session-card, which Hermes and DSH put in the system prompt of every turn, they share the budget
(MEMCUE_CARD_BUDGET, 3,500 by default): the oldest go first, and a cut card ends with a line that counts the rest
(#487). On the author's
archived base that is 9 global rules, about 1,300 characters. The other facts share a budget of about 3,500 characters,
a dozen facts, and a labelled base has more rules than that. They are chosen in this order:
- Newest first. The date alone says little on a migrated base, where most rules share the day of the migration, which is why the stated facts come before the budget.
- The rules and decisions of the current repository may take half of the budget before anything else is placed.
- The rest of the budget is filled in the order of step 1, global rules and the repository's facts together, so a repository whose facts come first may take more than half.
Measured on the author's base (65 global rules after the labelling of #219, 129 facts for the largest repository): the global card is unchanged; a repository with 49 facts, none of them stated, gets 6 lines instead of 4; a repository with 11 stated decisions keeps its 10 lines. Ordering by recall hits was rejected: the rules recall already served were the ones the card is not needed for.
An expiry is the last day a fact is worth recalling: after it the fact stays in the base and in facts, and leaves
the card. memcue search still finds it, and search -json shows its expires. The date is the local calendar day of the
machine that judges it. A correction keeps the expiry unless it names a new one.
Dream
A session writes a fact without reading every neighbour, so two sessions can store the same fact, or a fact and the one that replaced it. A dream tidies the facts the last dream did not see. memcue starts no model for it: the model of a session does it.
memcue dream -preparegroups each fresh fact with the stored facts close to it in words and prints one packet: the instructions, the fresh facts to split, the facts without search queries to expand, the facts without a translation to translate, and the groups to review. It keeps the packet in the state directory.- The model answers in one JSON object, with the packet's id.
memcue dream -apply FILEchecks the answer against the packet and carries out what passes. It prints and logs todream.logwhat it changed and what it refused, with the reason.
Who runs it:
- Claude Code. When a dream is due, the session start says so in one line, and the agent runs the
memcue-dreamskill when the user's task allows. It never interrupts a turn. A dream is due onceMEMCUE_DREAM_EVERY(default1h) has passed since the last one and some fact is fresh or lacks a translation. The first check on a machine only marks the start, so the facts it already had are not swept by surprise;memcue dream -prepare -allreviews them. - Hermes. When a dream is due, the card in the system prompt says so in one line (
session-card -dream-tools), and the model runsmemcue_dream_prepareandmemcue_dream_applywhen the user's task allows (#494). Hermes builds the system prompt once and again after a compression or a reset, so the line is as fresh as the last rebuild (plugins/hermes/README.md). - The whole base.
memcue dream -run -allanswers packet after packet until the base has no work left: no interval, and the merge sweep, which never runs out of groups, does not keep it going. It uses the endpoint whenMEMCUE_API_URLis set andclaude -ponMEMCUE_MODEL(default Opus, no tools, no hooks) otherwise. It stops on the first failed pass, after three passes that change nothing, or after 200 passes; a second run continues. With an endpoint set,env -u MEMCUE_API_URL -u MEMCUE_MODEL memcue dream -run -allruns it on Opus (#405). Without-all,-runchecks the interval and the facts the session start checks and makes no call when nothing is due (#401); withoutMEMCUE_API_URLit refuses, so a long run never starts onclaude -pby surprise. - Calls at once. A pass of
dream -runtakes the work of up to-jpackets (default 4) and sends it as calls at once: the fresh facts with the groups, the facts to expand and the facts to translate each go in their own calls of one packet's bounds. Their answers are applied as one, and one failed call fails the pass. Most of a call is the model writing its answer, so a pass of a base far behind does the work of four packets in about a third of the time of one; a pass with little work makes one or two calls.dream.logrecords the calls, the time of the packet, the calls and the apply, and the tokens of each pass (#471, docs/research/dream-time-2026-10.md).
A packet holds up to 40 fresh facts, 30 groups, 50 facts to expand and 40 to translate. The fresh facts it leaves out are not counted as seen, so the next packet takes them, and the session start still says facts wait.
What the answer may do:
- Split a compound fact: one part of it can go stale while another stays true. The fact is retired and its parts take its place, approved, with its origin, scope, kind, source and expiry (not its keywords, which were chosen for the whole text).
- Expand a fact without search queries: 6–10 short phrases a user would name the task with, the general name
of what the fact is about, and how the problem or question sounds. The index searches them next to the fact's words
and keywords.
addandcorrectask for them in one call whenMEMCUE_API_URLis set, so the dream fills the facts written without an endpoint, every fact the agent writes on such a machine among them, those ofimport, and the parts of a split. An older fact without queries is never fresh again, so each packet also offers the newest of them: a base upgraded from 0.4 fills over a few dreams. On the gold set of 180 prompts the queries added 7–9 prompts that got a useful fact (docs/research/one-model-2026-10.md). - Translate a fact that has no translation among its queries: the whole text in the other language, Russian or
English, kept as the query that starts with
=(#395). The dream gives it to every fact without one, the newest first, so a base is translated over a few dozen dreams without a command of its own (#396). A fact another part of the packet may change — fresh, without queries, or in a group — waits for a later packet, and a fact changed since the packet is refused, since its translation would describe the old text. - Retire a fact of a group as a duplicate or as contradicted, naming the fact that covers it (
by) and its kind,duplicateoroutdated. Of two duplicates the one with the smaller id stays, whatever the model prefers, so machines dreaming over one shared base keep the same copy and never retire both. Every group keeps at least one fact; a retired fact stays in the history. - Correct a fact of a group with text built only from the facts of the group. A correction drops the queries of
the old text. With
queriesin the action the new ones are written at once; without them the next packet asks for them.
A duplicate learnt in another project than the fact that stays makes that fact global (#346): a fact learnt twice,
independently, in two projects holds in both, so it should not count as one project's fact in recall. memcue decides
this from the two facts' scopes and origins, not the model. -apply prints global <id> and lists the fact under
promoted in dream.log. In the author's dream log, 15 of the 21 duplicates retired before this change crossed
projects.
The checks:
- An answer to another packet is refused whole: a second
-preparereplaced the packet it was for. - An answer about a fact the packet did not give for that part is refused, as is a second answer about one fact or a split into fewer than two parts.
- A group with a fact changed since the packet (a new version, or retired) is refused whole, since its actions were decided on the old text; the other groups apply. Splits apply first, so a group formed around a compound fact is refused as changed, and the next dream groups the parts with their neighbours. Queries apply after the splits; the version they write does not count as a change of the groups.
A dream keeps the facts and versions it saw in dream.seen, next to dream.at. A fact pulled from another machine
after the last dream is new to the next one even when it was written earlier, and so is a correction made since.
Without dream.seen (a machine that last dreamt with an older memcue), the next dream goes by when facts were written
and writes the file.
A pair of facts that both predate the last dream would never meet again, so each dream also sweeps up to 50 facts it
has seen before, grouping them with their neighbours the way it groups fresh facts (#377). The sweep adds at most 20
groups of its own and only as many as the packet has room for after the fresh groups. It goes through the base in
id order, from the fact after the one kept in dream.merge, and starts over after the newest. A fact is grouped
with a neighbour when it holds enough of the neighbour's terms, or when the neighbour's terms are nearly all in it:
a short note and a longer restatement of it meet whichever of the two is fresh. dream.log lists every group by id
under grouped, with its fresh facts and what came of it (acted, refused, kept or deferred), so the log
says whether two facts were grouped in a run. Measured on a copy of the live base: a pair 165 facts apart in id
order met in one group of 50 swept facts, the younger copy was retired in favour of the smaller id, and the run
record names the group (docs/research/dream-merge-2026-10.md).
The Jev triage that sorted out the groups needing no action before the model saw them is removed with the rest of the judge (#357); its measurements are in docs/research/dream-triage-2026-10.md.
Configuration
memcue calls one model, the main chat model, for migrate, for the search queries add asks for and for
dream -run; search calls none, and a dream is otherwise done by the model of a session (Dream). The judge and
embeddings roles of docs/models.md are removed (#250, #357). The table is the contract this release
uses.
| Variable | Meaning | Default |
|---|---|---|
MEMCUE_FACTS | the fact base; a leading ~/ or $HOME/ is expanded, and the DSH and Hermes adapters require the result to be absolute | $CLAUDE_CONFIG_DIR/memory/facts when that directory exists (a text history, see Moving to the text history), else $CLAUDE_CONFIG_DIR/memory/facts.db; ~/.claude without CLAUDE_CONFIG_DIR |
MEMCUE_STATE_DIR | state directory for logs, locks and the dream schedule; a non-default base gets a per-base subdirectory under $XDG_STATE_HOME/memcue | unset |
MEMCUE_MODEL | model of migrate, the queries of add and dream -run | claude-opus-5-5 |
MEMCUE_API_URL | OpenAI-compatible endpoint for the main model instead of claude -p, e.g. https://api.deepseek.com; set MEMCUE_MODEL with it | unset |
MEMCUE_API_KEY | key for that endpoint | unset |
MEMCUE_AUTO_INSTALL | 0 stops the plugins from downloading the binary of their release (see Binary from the plugin) | on |
MEMCUE_HOST | name of this machine for fact scope and the search tie-break; set it where the host name is unstable, such as a container | os.Hostname() |
MEMCUE_SEARCH_LOG | 1 logs every search and the facts it found; the log keeps the query | off |
MEMCUE_NUMBER_TERMS | versions, dates, ports and addresses are indexed as one exact term (1.27 becomes nm1x27, which never matches 1.20), so a query that names a version finds the fact naming it: 0 of 11 version-shaped prompts before, 10 of 11 after, no change on 26 ordinary questions (docs/research/number-terms-2026-10.md); 0 keeps the old word-only index, and a base indexed word-only is rebuilt on the first open without it | on |
MEMCUE_EVENT_TTL_DAYS | days a fact of kind: event stays recalled; 0 keeps event facts without an expiry | 7 |
MEMCUE_CARD_BUDGET | most characters of the session card, the facts the user stated included; when they do not fit, the oldest user-stated fact goes first, and a last line counts what is left out (memcue search -in global -kind rule finds it). The Hermes provider sets it from session_card_chars | 3500 |
MEMCUE_DREAM_EVERY | how long after a dream the session start says the next one is due (a Go duration, at least 1m) | 1h |
MEMCUE_MEMORY_DIR | directory of the built-in memory notes memcue notes reads | $CLAUDE_CONFIG_DIR/memory |
XDG_STATE_HOME | where memcue/search.log, memcue/card.log and memcue/dream.log are kept | ~/.local/state |
Search is the words alone: FTS5 over the query's terms, in BM25 order, with no threshold, because the agent reads
what it asked for. The gates that kept noise out of the facts injected with a prompt went with that injection (#361);
the BM25 order they used beat a coverage sort on 409 blind-labelled prompts (#338,
docs/research/search-chain-2026-10.md). The readers that once ordered the
candidates are removed: the vectors (#250), the tree reader (#324), the Jev judge and the listwise main-model reader
(#357). None of them served more useful facts than the words for its cost
(docs/research/one-model-2026-10.md,
docs/research/local-readers-2026-10.md,
docs/research/listwise-2026-10.md). MEMCUE_RERANK and MEMCUE_JUDGE_* are
no longer read. Opening a base deletes the vectors an older memcue stored and compacts the file once (#260); the empty
fact_vectors table stays, so the schema and older binaries are unaffected.
With MEMCUE_SEARCH_LOG=1, search.log has one JSON line per search: the time, the session (when the host passes
-session), the project, the host, the first 500 characters of the query and the ids of the facts it returned,
searches that found nothing included. It stays on your machine and is what you judge the agent's searches by; it is off
by default because it keeps the text of the queries.
cost_usd in dream.log holds only what a caller reports in dollars: claude -p. A call to MEMCUE_API_URL reports tokens, not a price, so its cost_usd is 0 and its spend is
tokens (input, of which cached came from the prompt cache, and output) times the provider's price; memcue
keeps no price table, since prices change and differ by account (#258).
The prompts of migrate and the dream (internal/extract/*.md) are written in Russian; a fact keeps the language of
its note.
Cost
Writing a fact costs no model call: the agent writes it as part of its work, and add asks for search queries in one
short call only when MEMCUE_API_URL is set. Before #494 an extraction of every session cost $0.18–1.33 per session
on Opus, about $5 per million characters of transcript, and a review followed it; both are gone.
A dream costs no call of its own: it is part of the session that runs it. One packet is about 20,000–36,000 characters on the author's two bases, instructions included; a dream with no fresh fact and nothing to expand prints one line. While facts are owed a translation, its 40 facts add about 10,000 characters (64,583 against 54,000 on a copy of the author's base with 2,831 fresh facts waiting).
Results
Measured while building memcue; details and method in docs/evaluation.md.
- The agent searching itself, end to end (#361): on 40 real prompts, each run twice and graded blind, the agent that searched memcue itself scored 1.08 of 2, against 0.89 with facts injected with the prompt and 0.76 with no memory; the edge over injection is +0.19 per prompt, 95% interval [+0.03, +0.37]. Only Opus in Claude Code was measured (docs/research/pull-2026-10.md).
- Recall on 100 unseen real prompts (the injection before #361): 33 % of injected facts useful and 38 % noise, against 4 % useful and 87 % noise for the holographic memory plugin's prefetch on a comparable set. Useful facts reached 12 of the 41 prompts that needed memory: lexical search misses prompts that share no rare words with the fact.
- End to end, 30 questions with answers in memory: 30 of 30 correct with memcue against 27 of 30 without, one turn fewer per answer; one run per question, so a direction rather than a significant result.
- Session extraction on Opus (removed in #494): no wrong facts in 102, 88 % of the durable facts of ten sessions kept.
- Migration of 312 notes: 2335 facts for $40; 2.5 % of facts carried the model's own conclusions after the prompt forbade them.
Candidates
There are none any more (#514). The model decides what is worth storing: a fact written with memcue add,
memcue_add or a Hermes memory mirror, and every fact memcue import stores, is indexed at once and found by a search
it fits (see Use). memcue retire ID takes a fact out, and memcue correct ID TEXT writes a new version. A
retired fact cannot be corrected: retirement is final, so a changed fact is added as a new fact, and memcue reopen ID is the way back.
A base written by memcue 0.20.0 or older may hold candidates, facts that waited for memcue approve, and provisional
facts the removed memcue vet left with an expiry. From schema 6 an active one is live as it is, its rows unchanged;
a retired one stays retired, and an expired one is handled as any expired fact (see Upgrade).
The local review page (memcue review) went in #325, vet in #494, candidates and approve in #514.
Harness support
The same base and binary serve all three hosts; the adapters differ at the edges:
| Capability | Claude Code | DeepSeek Harness | Hermes Agent |
|---|---|---|---|
| Search by the agent | memcue search (Bash) | memcue_search tool | memcue_search tool |
| Session card | SessionStart | session-card on first step and after compaction | session-card in the system prompt, rebuilt after compression |
| Writing facts | the agent, memcue add through the skill | the agent, memcue_add tool | the agent, memcue_add tool |
add / correct / retire | skill and CLI | four tools | four tools |
| Expiry | CLI and skill | add / correct | add / correct |
| Dream | memcue-dream skill on the session-start hint | none | dream tools on the system-prompt hint |
| Mirror of built-in memory | not applicable | no built-in memory files | MEMORY.md / USER.md mirrored as live facts (#440) |
State and logs for the default base live in $XDG_STATE_HOME/memcue; any other base gets a per-base subdirectory,
$XDG_STATE_HOME/memcue/<hash>, so two bases on one machine do not share a dream schedule. MEMCUE_STATE_DIR
overrides the whole path. Hermes keeps its mirror file under $HERMES_HOME/memcue; the binary's logs, locks and dream
schedule always follow the base. The fact base is $CLAUDE_CONFIG_DIR/memory/facts.db
(or ~/.claude/memory/facts.db) by default, $HERMES_HOME/memcue/facts.db for Hermes, and memcue's default for DSH
unless facts is configured; each becomes a facts directory after memcue migrate -text, and the cache of a text
history is facts.db in the state directory. A shared base is supported; point MEMCUE_FACTS or the plugin's facts option at the
same file.
DeepSeek Harness
memcue also works as a native DSH plugin: the session card, a search tool the model is told to use, and the fact tools it writes its memory with. See plugins/deepseek-harness.
Hermes Agent
memcue also works as a memory provider for Hermes Agent: the session card, a search tool the model is told to use, the built-in memory tool mirrored into the base, the tools the model writes and fixes facts with, and the dream tools. See plugins/hermes.
License
MIT
Ähnliche Plugins
hindsight (coding-agents)
vectorize-io/hindsight
OpenViking (dsh-memory-plugin)
volcengine/openviking
MemOS (memos-local-plugin)
memtensor/memos
ReMe (dsh)
agentscope-ai/reme