Durable memory for daily AI coding · 1-second recall · Trustworthy confidence levels · Anchor-based freshness · Optional token-goat seam
Local-first, defense-in-depth memory that remembers what your AI coding agent keeps forgetting between sessions.
You tell your AI "we use pnpm not npm" and it forgets. Every session. Then it runs npm install and corrupts the lockfile. You re-explain that you prefer 2-space indentation, and the next day it defaults to tabs. These are not oversights — the agent genuinely does not see a record of these decisions after a compaction.
Mem stores them. Locally, in your own SQLite database. Each fact carries a trust level and an anchor (a read-only predicate that tests whether the fact is still true). On recall, Mem re-validates anchors and surfaces only the facts that are fresh and trustworthy, with a confidence caveat so your agent never treats a hint as ground truth when it should not.
Works with Claude Code, Copilot CLI, Copilot in VS Code, Codex, and any agent that can run a shell command — integration guides for the first four live in docs/integrations/. There is also an optional seam -- a one-way integration point, where the other tool calls mem and mem knows nothing about it -- with token-goat, a sister CLI that gives agents narrow-slice code/doc reads to cut context burn. Token-goat reads mem epoch (a cache-invalidation key) and optionally consumes memory via the published TGMEM/2 wire format, which a future host tool can adopt.
Install:
npm install -g token-goat-mem
mem --help
Building from source, requirements, and verifying the install: Install.
Built and maintained by DFK Helper. Free under PolyForm Noncommercial. If it saves your tokens, or your sanity, drop a star at the top of this page.
Install · CLI · Walkthrough · How it works · Anchors · Token-goat integration · Disclaimer & License
Your AI coding agent accumulates durable knowledge that keeps evaporating:
- Preferences — "uses pnpm not npm", "2-space indent", "no default exports", "tabs not spaces"
- Decisions — "chose Postgres over Mongo for relational queries", "auth service owns migrations"
- Project facts — "staging DB is at prod-staging-db-1", "CI env is GitHub Actions"
- Corrections — recurring "do not do X" that you repeat every session
Today this knowledge is lost at each session boundary. The agent re-asks, re-derives, or — worst — forgets and does the wrong thing. A confident wrong memory is worse than no memory at all. If Mem surfaces a stale preference as ground truth ("you use npm" three months after you switched to pnpm), your agent acts on it and corrupts your lockfile.
The defining engineering problem is not retrieval — it is correctness and staleness. Mem solves both.
| Before | After |
|---|---|
| Agent re-reads the same preference every session | stored pref (verify): uses pnpm, not npm — mem show <id> — one-line hint with confidence |
| Agent does the wrong thing because it forgot a decision | mem remember "Postgres chosen over Mongo for relational JOIN queries" --kind decision persists it; mem recall surfaces it with provenance and age |
| Mixed signals on project setup (old README says npm, lockfile says pnpm) | Anchor predicates test the actual state (which lockfile is newer, git history); anchor-contradicted facts are excluded from recall and flagged in mem review for human resolution, never surfaced as ground truth |
| Every session starts cold | mem recall --hint-format embeds prior facts into your AI context at startup (~5-10 lines per session) |
| Session compaction forgets preferences | Facts live in SQLite, outside any context window; mem pin <id> additionally exempts a fact from time-decay. Unpinned preferences decay in confidence over time if not re-affirmed |
| Stale facts invisible until they cause damage | mem review flags anchor-contradicted facts before they become silent bugs |
- Explicit capture —
mem remember "uses pnpm not npm" --kind preferencestores a fact with a source reference (source_ref: where the fact came from, such as the command or file that produced it -- not to be confused with the separatesourcestablemem show --jsonexposes, which is designed to hold redacted content previews and is not yet written to by any capture path) and timestamp.--kindis required:preference,decision,fact, orcorrection. Saying the same thing again reaffirms the existing fact rather than writing a second row:captured_atand confidence are refreshed, and the command printsreaffirmedinstead ofremembered. Matching is deterministic and conservative -- same normalized text (case folded, whitespace collapsed, one trailing period dropped), same kind, same scope binding, same subject and value (value compared case-insensitively with internal whitespace collapsed, same as contradiction detection below, though the raw value you typed is always what gets stored and shown) -- so identical text carrying a genuinely different value stays two facts for contradiction resolution to key on. Only live facts match: apendingone is never reaffirmed (that would promote it without review), asupersededone is never resurrected, andmem suggestnever reaffirms at all, since derived text must not refresh a fact the user did not restate. - Optional anchors — Add an anchor:
mem remember "uses pnpm" --kind preference --anchor 'file-newer-than pnpm-lock.yaml package-lock.json'. The anchor is a read-only predicate; on recall, Mem tests it and returns one of three verdicts:affirmed(ground truth),unverified(hint to verify), orcontradicted(suppressed, flagged in review). - Recall with trust levels —
mem recall --kind preferencereturns active facts annotated by trust level, freshness verdict, and age. Low-trust facts are marked "verify" so your AI never mistakes a hint for ground truth. - Review and resolution —
mem reviewlists pending, contested, and anchor-contradicted facts. The two words are different mechanisms and both appear throughout: contested is fact-versus-fact (two facts disagree with each other, and neither clearly wins), while contradicted is fact-versus-world (an anchor predicate tested reality and denied the fact). A fact can be either without being the other. Contradictions (same subject + scope, different values, ambiguous winner) are never surfaced as ground truth — they appear here for you to resolve. Resolution runs both ways: once the rival is forgotten or edited into agreement, the nextmem epoch --gcreinstates the survivor (back topinnedif it was pinned) instead of leaving it withheld. - Forget and edit —
mem forget <id>soft-deletes a fact (marks it superseded, kept for audit;--reasonrecords why);mem edit <id>updates text, subject/value, anchor, or scope, andmem edit <id> --undowalks the last edit back. Both bump an internal epoch so the token-goat seam always sees the latest state.
Requirements: Node.js 22.12 or later
npm install -g token-goat-mem
Or from source:
git clone https://github.com/DFKHelper/token-goat-mem.git
cd token-goat-mem
npm install
npm run build
npm link
No daemon, no tray icon, no setup wizard. Mem is a short-lived CLI process.
A global install (and every upgrade) also wires mem's Claude Code hooks into your user-level ~/.claude/settings.json by running mem init claude-code --user, so recall works in every project without a separate step. It is idempotent -- unrelated settings are kept, and hooks an older mem wrote are refreshed -- and it never fails the install: if the mem on your PATH cannot run the hooks yet, nothing is written and mem init claude-code --user finishes the job. npm hides install-script output unless you pass --foreground-scripts; mem doctor shows hook health either way. The check of the mem on your PATH waits up to 10 seconds per probe; set TOKEN_GOAT_MEM_PROBE_TIMEOUT_MS (an integer from 100 to 600000 milliseconds) to change that for mem doctor and mem init claude-code. Set TOKEN_GOAT_MEM_SKIP_HOOKS=1 to skip it. It is skipped under sudo (anything running as someone other than your home directory's owner), so the settings file never ends up owned by root, and it gives up after two minutes rather than hang the install. Only npm runs it: after installing with pnpm, yarn or bun, or with --ignore-scripts, run mem init claude-code --user yourself. Installing from source (npm install in a clone) never touches your settings. On Windows the user-level hooks launch node on the installed bundle directly (falling back to the plain mem guard if the bundle is gone) to skip npm's slow mem shim; see docs/integrations/claude-code.md.
mem --version
mem epoch
mem --version prints the installed version; mem epoch prints a number (creating the database on first run), which confirms the CLI and the SQLite store are wired correctly.
Everything is stored in a single SQLite database at ~/.mem/mem.db. Set TOKEN_GOAT_MEM_HOME to relocate it (the test suite uses this to isolate itself from your real data). No network calls, ever.
On POSIX systems the directory is created 0700 and the database 0600, so the store is readable only by you even on a shared machine -- and an existing home created by an earlier version is tightened on the next run. Windows relies on the ACL ~/.mem inherits from your user profile, which restricts it the same way.
mem keeps snapshots of the store in ~/.mem-backups -- a sibling of the mem home, not inside it, and nowhere near ~/.claude -- so deleting either directory loses nothing. Set TOKEN_GOAT_MEM_BACKUP_DIR to keep them somewhere else (a synced folder, another disk).
- Automatic: whenever mem opens an existing store, it takes a snapshot if the newest one is at least a day old and the store has changed since. The hooks
mem initinstalls open the store every session, so that is roughly daily. The newest 14 automatic snapshots are kept. - Before a schema upgrade: a
pre-migrationsnapshot, always. - By hand:
mem backuptakes one now;mem backup --listshows them all, newest first. - Restore:
mem restore <name-or-path>checks the snapshot's integrity first, snapshots the store it replaces (pre-restore, so a restore can be undone), then moves the epoch forward. If the live store is not a readable database, it is moved aside tomem.db.unreadable-<timestamp>(the output says where) and the snapshot is restored into a fresh store.
Trust model: restore and import only files you trust. A crafted backup snapshot is equivalent to write access to mem.db, so mem restore should only be pointed at a snapshot mem or you wrote. mem import --from-json likewise trusts the provenance fields in the file (id, status, confidence, captured_at, source_type, source_ref) as written and does not re-derive them, so import only a file you exported yourself or otherwise trust.
Only automatic snapshots are pruned; manual, pre-migration and pre-restore ones stay until you delete them. A snapshot is a compacted VACUUM INTO copy with the same owner-only permissions as the store. A backup that cannot be written never blocks a command -- mem doctor's backups: line is where it shows up.
| Command | What it does |
|---|---|
mem remember <text> |
Store a new fact. --kind preference|decision|fact|correction (required). More below. |
mem suggest <text> |
Same flags as mem remember, but always stores the fact pending (captureSuggested, not captureExplicit) — never auto-promoted, confirm via mem review --promote <id>. |
mem scan-session |
Scan a session transcript for durable-statement sentences and file each as pending — the capture half of the Claude Code seam, installed by mem init as the PreCompact hook, and run by mem reflect as the Stop hook. More below. |
mem reflect |
The end-of-session worklist that turns pending suggestions into decisions, update-before-create. Lists each pending suggestion beside the live facts it most resembles (the same entity/topic lookup as mem show --related, top 3) with the resolutions in the order to try them: update the related fact (mem edit, or mem remember --subject/--value to supersede it) and reject the suggestion as merged into it; promote it as new; or reject it. More below. |
mem hook <event> |
The one command the Claude Code hooks mem init claude-code installs call; run by the host, not by hand. <event> is session-start, user-prompt-submit, stop or pre-compact, and any other value is a usage error. More below. |
mem export |
Writes stored facts to stdout. --format json|md (default json). More below. |
mem import --from-md <path> |
Advisory only. Parses a markdown file (CLAUDE.md-style) for -/* bullet lines that look like preference/decision statements and imports each as a pending, source_type: "derived" fact — the same trust path as any other suggested candidate; never auto-promoted, no bulk-promote shortcut. Confirm each import via mem review --promote <id>. More below. |
mem import --from-json <path> |
Full-fidelity. Imports a mem export file, preserving each fact's original id, status, confidence, and captured_at exactly — unlike --from-md, an imported fact keeps whatever status it was exported with (including already-active), not forced to pending. More below. |
mem recall [query] |
Retrieve facts by relevance with trust levels and freshness verdicts. Caps non-withheld results at 20 by default (a trailing showing N of M line appears when truncated) — pending/contested/contradicted facts are never subject to this cap, so a fact needing attention is never silently hidden. More below. |
mem list |
Fact IDs and one-line summaries. Caps at 20 by default (a trailing showing N of M line appears when truncated). --kind, --status (comma-separated), --subject, --scope, --limit (overrides the default 20), --json (machine-readable, unstable pre-1.0 -- shape may change; same fact shape as mem export minus embedding, plus total/truncated; use mem export for a stable machine-readable surface). |
mem show <id> |
One fact in full: text, provenance, anchor and its current freshness verdict. More below. |
mem review |
Pending, contested, anchor-contradicted, and unanchored-but-checkable facts for human resolution. More below. |
mem forget <id> [--by <winner-id>] |
Soft-delete a fact (marks superseded, kept for audit) and audit-log it. --by names the live (active/pinned) fact that replaces it (id or unique prefix; a self-reference or a non-live winner is refused, nothing written), recorded in the same audited write so mem show and mem export report superseded_by instead of an unknown edge. More below. |
mem edit <id> |
Change a fact's --text, --subject/--value (paired), --anchor, --why (an empty string clears it), --scope, or (when rebinding to --scope path) --path <file> (required when --scope path is given, rejected otherwise). Bumps epoch. A source_type=user fact -- one you stated yourself -- requires --force, and the override is recorded in the audit log; a derived fact does not, so the guard is scoped rather than a blanket block. More below. |
mem pin <id> |
Exempt a fact from time-decay (still subject to contradiction/anchor suppression), and reserve it one of 2 guaranteed --hint-format slots -- past those two, extra pins compete on relevance like any other fact. Only an active (or already-pinned) fact can be pinned: pinning is a promotion to maximal trust, so a pending, contested, or superseded fact must be resolved through mem review first rather than pinned around it. |
mem used <id...> --session-id <id> |
Record that facts recalled in a session were actually useful. Feeds recall ranking as a third rank list fused alongside BM25 (rank-based, so the signal cannot drift as the store grows). --session-id is required and names the session the recall was surfaced under -- mem is a short-lived process with no notion of a current session. Naming a fact never surfaced in that session says so and exits 0 rather than failing. More below. |
mem epoch |
Print the current write epoch (monotonic, bumped on every write). --gc runs the retention pass first: persists contradiction resolutions, prunes superseded facts/sources/audit rows, applies preference decay. More below. |
mem consolidate |
Report facts that restate each other, so a store that has accreted three phrasings of one preference can collapse to one. Deterministic and offline: clusters by Jaccard similarity over the fact_terms topic layer (mem facets), never a model call. More below. |
mem dream |
Report what a configured model thinks follows from several stored facts taken together -- the inference mem consolidate deliberately cannot do, since Jaccard over topic terms can see that two facts restate each other but never that a third thing follows from both. More below. |
mem facets |
Extract, inspect, and list the structured entity/topic terms behind mem recall --entity. Terms are written automatically on capture and re-written on mem edit, so this command is for backfill and inspection, not routine use. More below. |
mem embed |
Compute and store embedding vectors for facts, so recall can rank semantically as well as lexically. Requires TOKEN_GOAT_MEM_EMBED_URL and TOKEN_GOAT_MEM_EMBED_MODEL (see Optional: semantic recall) and errors naming them when unset. Default: embeds only facts that have no vector yet, in batches, and reports embedded / skipped / failed. --all re-embeds every fact and rewrites the recorded model -- the migration path after changing models. --limit <n> bounds the work. A failed batch costs only that batch; a run in which nothing was embedded exits non-zero. |
mem doctor |
Environment/DB health check that changes no fact or file: the store, embeddings, backups, last fact surfacing, Claude Code hook health and per-tool wiring. Each line is a structured finding (ok/warn/fail); --json prints { findings, epoch }, --probe-embeddings makes one real embed request (off by default, so plain mem doctor makes no network call), and --strict exits 1 when any is fail (default: always exit 0). Every check is listed under mem doctor details below. |
mem backup [--list] |
Snapshot the store into the backup directory now (~/.mem-backups, or TOKEN_GOAT_MEM_BACKUP_DIR), or list the snapshots there, newest first. See Backups. --list --json emits an array of snapshot objects with path, name, takenAt (ISO 8601), epoch, reason, and size fields (plain output unchanged). |
mem restore <snapshot> |
Replace the store with a snapshot (a name from mem backup --list, or a path). Validates it first and snapshots the replaced store, printing how to undo. |
mem init <tool> |
Wires mem into a coding tool's config -- claude-code, codex, copilot-cli, copilot-vscode, copilot-visual-studio, copilot-jetbrains, or opencode -- automating what docs/integrations/*.md otherwise asks you to hand-copy. More below. |
mem uninstall <tool|--all> |
Removes exactly what mem init wrote for tool -- or every tool with --all -- leaving everything else untouched. A no-op (not an error) if there's nothing mem-authored to remove. --root <path>, --user, --dry-run. |
Every <id> argument (show, forget, pin, edit, used, review --promote/--reject) accepts a git-style short prefix — at least 4 characters — instead of the full id, as long as it uniquely identifies one fact. A prefix matching more than one fact errors and lists every match.
Every command supports --help for the authoritative flag list.
mem doctor is an environment/DB health check that changes no fact or file. Each line is a structured finding (ok/warn/fail). What it reports:
- Store: db path, WAL journal mode, foreign-key setting, schema tables, current epoch, fact counts by status, source/audit-log row counts. It opens the store read-only -- it never creates, migrates, or snapshots it -- and keeps reporting when the store is broken: no file reads
store: none, an unreadable one is afailpointing atmem backup --list/mem restore, an older schema readsschema: N migrations pending, andintegrityis SQLite'squick_check. The hooks, backups, embedding and dream lines never needed the store and print either way. - Embeddings and why coverage: embedding configuration (endpoint host and model -- never the API key) and coverage, and why coverage (how many active or pinned decisions and corrections carry a
--why). - Hint budget: how many facts are recallable against the at-most-14 lines one
--hint-formatblock carries, and how many are pinned against the 2 reserved slots. - Scope placement: project/path-scoped facts whose
scope_rootno longer exists on disk -- unreachable from any session, and healthy by every other line here. - Backups: the snapshot directory, how many snapshots it holds and how old the newest is -- and a warning when a written store has none or they have stopped landing.
- Last fact surfacing: when recall last put a fact in front of a session, a lower bound since a recall that surfaced nothing leaves no record. It warns when active or pinned facts exist and none was ever surfaced, the latest surfacing is over 7 days old, or the recorded time is unreadable; the remedy for the first two is
mem init claude-code. - Claude Code hook health: whichever
membinary PATH resolves hooks to, and whether it supports each installed hook's command (mem hook <event>, or the subcommand and flags of an older flag-carrying hook), checked against.claude/settings.jsonunder the current directory, project- and user-level. Alegacy hook shapewarning names the events still on the older form, remedymem init claude-code. A probe that gets no answer within its budget is reported as "could not probe" with the remedy "re-run mem doctor once the machine is less busy", not as a binary missingmem hook <event>. A binary that cannot run at all is reported as such, with the remedy "reinstall token-goat-mem or fix PATH". A probe skipped because the binary's path carries a character cmd.exe would interpret (&,%,!,^,|,<,>, a quote or a line break, for example in a Windows user name) is reported as skipped, naming the path, not as a missingmem hook <event>. ASessionStartorUserPromptSubmithook with atimeoutbelow 20 seconds is a warning naming the event and the value, remedy atimeoutof 45, and atimeoutthat is not a number (a string such as"45") is warned about the same way; a hook with notimeout, and theStopandPreCompacthooks, are not checked. The budget for each probe isTOKEN_GOAT_MEM_PROBE_TIMEOUT_MS(an integer from 100 to 600000 milliseconds, default 10000); any other value is refused. - Wiring: every supported tool whose config exists under
--root(default: the current directory) or the home directory.wiringisokwhen the block is exactly whatmem init <tool>would write today, andwarn(remedymem init <tool>, plus--user) when it is outdated, hand-edited inside its markers or missing a hook event; a tool mem was never wired into is informational only.hook-divergencewarns when project and user Claude Code hooks run different mem invocations, since both run, and when they run the same invocation through different launchers, since Claude Code skips a duplicate only when the command text is identical.
Flags: --json prints the findings as { findings, epoch }; --probe-embeddings makes one real embed request with a tiny fixed input to the configured endpoint and reports whether it answered (off by default, so plain mem doctor makes no network call); --strict exits 1 when any finding is fail (default: always exit 0).
The rest of each command's description from the table above.
--subject <key> + --value <value> (paired, for contradiction detection), --anchor <predicate> (optional), --scope global|project|path (default global), --source-ref <ref>, --why <reason> (optional rationale, shown beside the fact on recall; decisions and corrections especially), --root <path>, --path <file> (required when --scope path, rejected otherwise — the file/directory the fact binds to, resolved against --root; without it a --scope path fact would bind to --root itself, i.e. behave exactly like --scope project).
The same scan under two triggers -- Stop never fires for a session that is killed or interrupted rather than ending a turn, and by the time it does fire on a long session everything before the last compaction boundary has been summarized away; re-scanning the overlap files nothing twice, since a candidate whose text is already stored is skipped before capture -- except when the match is still pending, where the repeat is recorded as a sighting instead of discarded: see mem review's row for what that means. Deterministic sentence matching against a fixed opener table (remember that, from now on, always/never, don't, we decided, we should/let's + always/never, decision:, rule:, correction:, that's wrong, that is wrong, that's outdated, that's no longer true), no model, so the same transcript always yields the same candidates. An opener must still be the first thing said, past a closed list of discourse fillers (please, also, so, ok, note that) that may precede it — enough to catch "Please always run the linter", not enough to let "I never got that to work" through. The stored text is the whole sentence, filler included. Nothing reaches active without mem review --promote <id>, and the dedup matches on fact text so a rejected suggestion is never re-filed by a later scan. Only the human's own text is read: tool results, <system-reminder> injections, slash-command payloads, relayed subagent reports, and compaction summaries are all stored under the user role and are all excluded. --hook-stdin (take transcript_path from a hook envelope), --transcript <path> (scan a named file instead), --root <path>, --scope global|project|path (default project), --quiet (emit nothing on success — a Stop hook's stdout would otherwise land in the session it just read). Each fact is dated from the transcript entry's own timestamp when it carries one, not from the moment of the scan -- re-scanning an archived transcript would otherwise back-date nothing and mis-age everything, skewing preference decay, the --stale cutoff, recall's recency tie-break and the pending review order by the transcript's whole age. An entry with no timestamp, or one that is malformed or future-dated, falls back to now without failing the scan. The sources row's stored_at stays the scan time: when a thing was said and when mem stored it are different columns.
Without --transcript it lists every pending suggestion bound to --root; --transcript <path> files that transcript first (the same scanner as mem scan-session) and lists only its suggestions, exiting 1 when the file cannot be read. --hook-stdin is what the Claude Code Stop hook mem init installs runs (mem hook stop): it files the envelope's transcript and answers {"decision":"block"} with the worklist as the reason, but only for suggestions this run filed -- a sentence already stored is a sighting and files nothing, so a second stop over the same transcript is silent -- and never while stop_hook_active is set, since blocking an agent already continuing because of a Stop hook would loop.
The settings file carries only the event name: each event runs the handler it names (session-start is mem recall --hint-format --hook-stdin, user-prompt-submit adds --delta, stop is mem reflect --hook-stdin, pre-compact is mem scan-session --hook-stdin --quiet), so the flags can change with the binary without leaving a stale string in settings.json. The project root is CLAUDE_PROJECT_DIR, else the cwd in the hook envelope, else the current directory. Re-running mem init claude-code rewrites an older flag-carrying hook to this form.
json is the full-fidelity envelope ({ schemaVersion, exportedAt, facts }) mem import --from-json reads back, preserving id, status, confidence, anchor, subject, and value -- pair it with that command for backup/restore or full-fidelity migration between stores, and treat it as the stable machine-readable surface; a superseded fact additionally carries superseded_by (the winning fact's id, or null when this store itself has no recorded winner), so mem import --from-json can restore the edge without re-running contradiction resolution; md is a shareable, git-reviewable rendering for humans, not a backup -- mem import --from-md reads it back, but every bullet lands pending and only the fact text survives, id/status/confidence/anchor/subject/value are all lost. --kind, --status (comma-separated), --subject, --scope filter which facts are exported (default: every fact, any status).
--dry-run (report candidates without writing), --root <path>, --scope global|project|path (default project), --kind (default preference), --path <file> (required when --scope path, rejected otherwise — every bullet in this run binds to the same file/directory). Re-importing the same file skips bullets already imported at the same file:line + text, reported as skipped (duplicate). A candidate whose text already matches a fact in the store (the same check mem scan-session uses) is reported as skipped (already known) instead -- --dry-run cannot predict that kind of skip without opening the store, and says so. When that matched fact is still pending, the same sighting mem scan-session records is recorded here too (see mem review's row). The file's own mem integration block (between its token-goat-mem start and end markers, shared or per-tool) is skipped rather than imported back as facts, and an indented prose line under a bullet is joined onto it with a single space, so a wrapped bullet imports whole instead of as its first line (a heading or an indented numbered item ends the bullet). A code fence that never closes is treated as ordinary text, so it no longer hides the bullets after it. A joined bullet over 500 characters is reported as skipped (fact text exceeds 500 characters ...) and not stored. A file imported before bullets were joined has its wrapped bullets stored cut short: re-importing creates the full-text versions as new pending facts (their text differs, so neither duplicate check matches) and leaves the shorter ones untouched. Clear those with mem review --reject <id> while pending, or mem forget <id> once promoted. --captured-at <iso> back-dates every fact in the run to when the file was actually written (--captured-at "$(git log -1 --format=%aI -- CLAUDE.md)") instead of now; a malformed or future value fails the whole run with exit 1 rather than being reported per bullet.
A superseded fact's superseded_by (when the export recorded one) is restored as a fresh audit-log edge once every fact in the file has landed, so a winner named later in the same file, or already present in the target store, still resolves; a winner id that resolves nowhere is left alone rather than asserted. Still runs the same secret screening before writing. Idempotent: a fact whose id already exists in the target store is skipped as a duplicate, safe to re-run. --dry-run (report candidates without writing -- applies the same root-bounding and secret screening the real import does, and reports would-skip with the real refusal reason; duplicate-id conflicts are the one check it cannot make, since that needs the store, and the output says so), --root <path> (resolves .mem/allowlist for the secret screening, and bounds which scopeRoot values an imported fact may carry -- a fact bound outside it is skipped). Exactly one of --from-md/--from-json is required.
--kind, --subject, --scope, --hint-format (TGMEM/2 wire format for token-goat), --context-files <a,b> (scope=path matching, --hint-format only), --age-days <n>, --limit <n> (overrides the default 20), --root <path> (facts are bound to the project they were captured in: a project-scoped fact surfaces only from its own root, a path-scoped one only from a root containing its file, and global facts from anywhere), --stable (deterministic id-sorted output instead of relevance/recency order), --hint-style <full|terse> (default full; terse drops the CTA, shortens kind labels to pref/dec/fact/corr, and elides a body past 140 characters to ... (+N chars: mem show <id>) -- never mid-surrogate-pair, and full never elides), --since-epoch <n> (only include facts written after write-epoch n), --entity <value> (only facts carrying that extracted entity -- a file path, CLI flag, constant, version, or identifier -- matched case-insensitively; repeatable, and repeats AND together, so --entity src/cli.ts --entity --hint-format keeps only facts mentioning both; see mem facets --list-entities for what is available, and note it cannot be combined with --hint-format). Default (full) output ends with one trailing footer line (mem show <id> for detail; mem review to resolve contested/pending) instead of repeating a CTA per line. Ranking also consults the entity layer without any flag: identifiers in the query itself are matched against stored entities and fused as one more rank list, because BM25 stems src/retrieval.ts down to src/retriev/ts and cannot otherwise tell the fact naming that file from one merely using those three words. It is a vote and not an override -- a fact that only carries the identifier will not displace one that carries it and matches the rest of the query -- and it is silent for a query containing no identifier at all.
--root <path>, --related (also print the facts sharing the most entity/topic terms with this one, entity matches weighted above topic matches, target and superseded facts excluded, scope-contained to --root -- an association aid, not a ground-truth channel; pending/contested neighbours appear but stay labelled as such), --json (machine-readable, unstable pre-1.0; adds a freshness verdict that plain mem export/mem list --json do not, plus a sources array fed only by mem scan-session and mem import --from-md -- each writes one screened, truncated excerpt of the raw material (a user turn, a markdown line) the fact was pulled from; mem remember/mem suggest never write one, since the caller's own text already is the fact. [] therefore covers two cases: no source was recorded for this fact, or it came from a path that never records one). A superseded fact that mem forget --reason retired also prints forget_reason: <reason>. Also prints history: every audit row for the fact, oldest first. That is where an edited fact's previous text lives, in full -- mem edit overwrites in place, so nothing else in the store retains it. With --related also passed, --json additionally carries related: derived-at-read-time neighbours as above, each with its status and a shared-term score; [] means this fact shares no term with anything else in scope, not that the field was skipped.
The pending bucket sorts most-restated first: mem scan-session/mem import --from-md record a sighting (a sightings counter plus its own screened source excerpt, both written together) whenever a candidate's text matches a fact already pending, instead of discarding the repeat -- a pending fact sighted again prints sighted again <n> time(s) since beneath its summary line. Sightings are evidence for the human reading this bucket only; they never promote a fact, change its status, or feed recall -- a pending fact promotes only through --promote, however many times it has been sighted. A pending fact with a recorded source prints its newest excerpt beneath the summary line; a pending fact that is a correction, or that carries a subject, also prints may contradict <id> "<text>" naming the single best-matching live (active/pinned) fact it may contradict once promoted -- the same entity/topic-weighted lookup mem show --related runs, narrowed to one match; a label only, it never supersedes, changes status, or promotes anything; beneath it, a paste-ready mem forget <rival> --by <id> to run once this fact is promoted. An unanchored-but-checkable fact prints a paste-ready mem edit <id> --anchor "file-exists <path>" command (carrying --force when the fact is source_type=user, since mem edit refuses those without it) whenever the suggested predicate would already evaluate affirmed. --promote <id> / --reject <id> act on pending and contested facts — promoting a contested fact resolves the contradiction in its favor and supersedes its rivals, rejecting one reinstates the survivor; --reason <text> records why beside any of the three on its audit row (validated and secret-screened like --why; shown by mem log); --promote <id> --subject <key> --value <value> keys a pending fact as it activates it -- one transaction that also runs contradiction resolution against the new key, the step an unkeyed promotion's caveat otherwise sends you to mem edit for (both flags together, --promote only, validated and secret-screened like mem remember's; a contested fact is already keyed and is refused); --undo <id> reverses a --reject, restoring the fact to the status it had before (rejections only -- mem forget is a considered decision, not a review slip, and is refused by name); promoting a pending or contested fact with no subject/value prints a caveat noting contradiction resolution keys on those, so an unkeyed fact can never be superseded; --root <path>; --summary (print per-bucket counts instead of full listings); --section <pending|contested|contradicted|pins|unanchored> (restrict output to one bucket); --since-epoch <n> (only include facts written after write-epoch n).
--reason wrong|outdated|duplicate|unwanted records why the fact was retired in the same audit row (optional; any other value is refused, nothing written), and mem show prints it as forget_reason (forgetReason under --json) on a superseded fact. Bumps epoch.
--undo reverses the most recent edit, restoring only the fields that edit touched (it takes no other option, refuses anything whose last recorded action was not an edit, and refuses an edit written before the prior value was recorded rather than restoring nothing and reporting success).
Idempotent: marking twice does not double-count. Bumps epoch when it actually stamps a row: the usefulness signal feeds recall ranking, so a cached recall computed before the stamp is out of date. Only reachable where a session id is known. A --hint-format response logged under a session (--session-id <id>, or --hook-stdin reading a hook envelope's session_id) carries a mem used ... invocation in its footer line naming that session and every fact just sent. Those ids are candidates, not a verdict: pass only the facts that actually informed the work. Naming exactly the facts one recall surfaced (when it surfaced 3 or more) is refused as a rubber stamp, since it would make "used" mean "surfaced"; --all confirms they all helped. Claude Code's installed hooks run mem hook, which passes --hook-stdin for the recall events, so this is live wherever mem init claude-code ran. The shared markdown-style wiring for the other supported tools (codex, copilot-cli, copilot-vscode, copilot-visual-studio, copilot-jetbrains) invokes plain mem recall --hint-format --root . with no session id: no recall_log row is ever written there, the footer carries no invocation, and nothing can be marked useful until that wiring passes one too.
A rejection tombstone -- a superseded fact whose prior status was pending, i.e. the record of mem review --reject -- is never pruned by either bound: pruning it would let the same suggestion be filed pending again, silently reversing the rejection. This makes rejection-tombstone growth unbounded by age or count, bounded in practice only by how often facts get rejected. --json emits { epoch: <number> } (plain output unchanged).
Two facts that share topic terms but disagree in polarity (a negation, an unless/except exception, or X over Y preference roles swapped) are never clustered: the report lists them as not clustered -- these look like a contradiction, not a duplicate, each with a paste-ready mem edit <id> --subject <subject> --value <value>, and --apply never supersedes across such a pair. Only facts of the same kind and the same scope binding are ever compared -- a deliberate blind spot the two passes below run alongside this one to close, without loosening the comparison itself. Dry run by default -- bare mem consolidate prints the clusters and changes nothing; --apply marks every loser superseded (soft-delete, audit-logged, kept in the store), keeping the pinned member, else the most confident, else the newest. A pinned fact is never superseded, only listed. Alongside the Jaccard pass, the default (non---stale, non---cross-project) run also reports any live project-scope fact whose text exactly matches a same-kind global fact word for word -- exact normalizeFactText equality, not Jaccard, since a scope-family comparison this loose would let unrelated same-kind facts merge across every project -- but only once their subject and value also agree: two facts can share exact wording ("the default branch name") while one is a global default and the other a project-scope override of a different value, and that pair is a correction, never a duplicate, so it is left alone by both this pass and --cross-project below. --apply supersedes the project-scope copy, keeping the global one (widening a fact's own scope is not something this pass invents on its own). --cross-project runs a third, report-only pass instead, with no --apply path of its own: same-kind, same-text facts (subject and value agreeing, per the same rule) restated under two or more distinct projects, printing a paste-ready mem edit <id> --scope global for the newest occurrence -- widening a fact out of its own project is the user's call, never this command's. --threshold <0-1> sets the Jaccard floor (default 0.5: at that floor two facts must share more of their combined topic vocabulary than they differ on, which separates restatements from facts that merely discuss the same subject; the measure is scale-free, so unlike a BM25 score the same number means the same thing however large the store grows). --stale runs the other pass instead: active facts captured before the --stale-days <n> cutoff (default 90) that have gone unsurfaced for at least that long and that no surviving recall_log row marks useful, same dry-run-then---apply contract. This is a windowed "unsurfaced for N days" question, not a lifetime "never surfaced" one -- a fact surfaced once, long before the window, becomes eligible again once it goes quiet for the window's length. The marked-useful exclusion is not bounded by --stale-days, but it is bounded by how long the evidence lives: mem epoch --gc rotates recall_log rows past its own retention regardless of the useful mark, after which the fact is governed by its last-surfaced date like any other. For a fact that must outlive its own recall history, mem pin is the durable answer -- pinned facts are excluded from this pass by construction. Neither pass ever hard-deletes anything.
An evaluation surface, not a capture path: it writes nothing, and there is no flag that makes it -- the output is a report to read, and anything worth keeping is kept by typing mem remember. Off unless TOKEN_GOAT_MEM_DREAM_URL (an OpenAI-compatible chat-completions endpoint) and TOKEN_GOAT_MEM_DREAM_MODEL are set; TOKEN_GOAT_MEM_DREAM_API_KEY is optional, so a local endpoint that wants no auth is sent no header. With TOKEN_GOAT_MEM_DREAM_URL/_MODEL set, this command sends stored fact text to that endpoint -- point it at a local model if the store holds anything you would not paste into a hosted API. (Semantic recall/capture sends text too, when TOKEN_GOAT_MEM_EMBED_URL/_MODEL are configured; see the disclaimer below.) Sends active and pinned facts only, newest first, capped at 200 (a superseded fact is one the store has already decided is wrong, and an inference resting on it would carry the store's authority behind a retracted premise). Every returned candidate is checked before it is printed: it must cite at least two of the facts that were actually sent, by an index that resolves, and must not restate a fact already stored -- a candidate failing any of these is dropped rather than shown, so each printed from: id is one mem show can open. See Optional: mem dream. --timeout <ms> (default 60000), --json. Deliberately has no --root: dreaming reasons over the whole live store, and a flag that read as scoping while doing nothing would be worse than no flag.
No flags (or --backfill) extracts terms for facts that have none yet and reports the counts; --all re-extracts every fact -- the path after an extraction-rule change; --fact <id> shows one fact's entities and topics (short id prefixes work); --list-entities prints the distinct entities in the store with fact counts, most frequent first. Entities are stored with the spelling the fact used and matched case-insensitively, so --entity postgresql finds a fact that says PostgreSQL. The three modes are mutually exclusive. --json emits structured data: --list-entities --json gives an array of { term, facts }; --fact --json gives { fact, entities, topics }; backfill mode gives { facts, entities, topics } summary (plain outputs unchanged).
Idempotent: re-running upgrades mem's own entries in place, never duplicates them; an unstamped hand-written entry with the same identity aborts with a conflict error instead of being overwritten -- except a Claude Code hook whose command matches mem's own invocation shape for that event (an orphan left by an install that predates stamping), which is adopted in place, stamped, and reported in the install output, rather than treated as a conflict. --root <path> (project root, default current directory), --user (write the tool's user-level config instead of project-level, where it has both -- codex, copilot-cli, copilot-visual-studio, and copilot-jetbrains have no user-level target and reject --user; copilot-vscode --user writes only its user-level keybindings.json, skipping the project tasks.json and AGENTS.md block; opencode --user writes opencode's global ~/.config/opencode/AGENTS.md instead of the project's, on Windows too), --dry-run (print what would be written without touching disk), --force (claude-code only: skip the pre-flight check below). Installing both an AGENTS.md tool and a copilot-instructions.md tool writes mem's block into both files, which VS Code and Copilot CLI both read -- harmless, just redundant tokens. For claude-code, init first resolves the mem binary actually on PATH and checks it against the hooks it's about to write (by capability probe, not a version-number floor -- a stale binary lacking mem hook or one of its event names fails every hook silently otherwise); refuses and names the binary and what it can't run if the check fails, unless --force is given. Each probe gets a budget of 10 s by default (TOKEN_GOAT_MEM_PROBE_TIMEOUT_MS, an integer from 100 to 600000 milliseconds, changes it). A probe that gets no answer is retried once at three times the budget; if it still gets none it is reported as "could not probe <bin> within N s" (N is the budget of that last attempt, 30 by default), not as an old binary, and init refuses on it, suggesting re-running later or --force. A probe skipped because the binary's path carries a character cmd.exe would interpret (&, %, !, ^, |, <, >, a quote or a line break) is reported as skipped, naming the path, and init suggests --force or a path without those characters. The SessionStart and UserPromptSubmit hook entries init claude-code writes carry a timeout of 45 seconds so a stalled recall is cut off; the Stop and PreCompact entries carry none, and a timeout already on an entry is never replaced (see the Claude Code integration).
Retrieval is BM25 (lexical) by default and needs no configuration. Point these at any OpenAI-compatible embeddings endpoint -- OpenAI, Ollama, LM Studio, LiteLLM -- and recall additionally ranks by meaning, fusing the two lists with Reciprocal Rank Fusion:
| Variable | |
|---|---|
TOKEN_GOAT_MEM_EMBED_URL |
Full endpoint, e.g. http://localhost:11434/v1/embeddings. Setting it is what turns the feature on. |
TOKEN_GOAT_MEM_EMBED_MODEL |
Model name. Required whenever the URL is set. |
TOKEN_GOAT_MEM_EMBED_API_KEY |
Optional. Sent as Authorization: Bearer <key>; never logged, echoed, or printed by mem doctor. |
Wherever mem names the endpoint (mem doctor, an invalid-URL error, a failed request) it prints the host and port only: the path, the query string and any user:password@ in the URL are never echoed. mem doctor --probe-embeddings makes one real request to check that the endpoint answers.
A localhost endpoint keeps mem's zero-network property; a hosted one does not, and sends your fact text to that provider. Nothing is sent until you set the URL.
Once configured, mem remember and mem suggest embed each new fact in the background of the same
command -- after secret screening, never before -- and never fail, slow, or change their output if
the endpoint is down. Run mem embed once to backfill facts captured earlier.
Vectors from two different models are not comparable, and cosine similarity cannot detect the
mismatch: it happily compares them and returns a confident, meaningless number. Mem records which
model produced the store's vectors and refuses to rank against them when the configured model
differs, saying so on mem recall and mem doctor. mem embed --all is the migration.
mem consolidate can tell that two facts restate each other; it structurally cannot tell that a
third thing follows from both, because Jaccard over topic terms has no notion of entailment.
mem dream asks a model that question and prints the answer. Same shape as semantic recall above --
off until you set the URL, any OpenAI-compatible chat-completions endpoint:
| Variable | |
|---|---|
TOKEN_GOAT_MEM_DREAM_URL |
Full endpoint, e.g. http://localhost:11434/v1/chat/completions. Setting it is what turns the command on. |
TOKEN_GOAT_MEM_DREAM_MODEL |
Model name. Required whenever the URL is set. |
TOKEN_GOAT_MEM_DREAM_API_KEY |
Optional. Sent as Authorization: Bearer <key> only when set, so a local endpoint that wants no auth receives no header. Never logged or echoed; errors name the endpoint host, never its URL or key. |
It writes nothing, and no flag makes it. The output is a report; anything in it worth keeping is
kept by typing mem remember, which runs the same secret screening, anchoring, and contradiction
checks every other fact goes through. That is the whole design: a model is allowed to suggest what
might follow from the store, and is never allowed to add to it.
It sends stored fact text to a chat model, when configured. Semantic recall/capture sends fact
(and query) text to an embeddings endpoint too, when TOKEN_GOAT_MEM_EMBED_URL/_MODEL are set --
dreaming is not unique in this, it is just the other opt-in path that reaches off this machine. Point
either at a local model if the store holds anything you would not paste into a hosted API.
Only active and pinned facts are sent, newest first, capped at 200 -- a superseded fact is one
the store has already decided is wrong, and an inference resting on it would carry the store's
authority behind a retracted premise. The reply is treated as untrusted input rather than as an
answer: a candidate must cite at least two of the facts actually sent, by an index that resolves,
and must not restate a fact already stored. One that fails any check is dropped rather than printed,
so every from: id is one mem show opens.
$ mem dream
dream: qwen3:8b via localhost facts_sent=34
2 candidate inference(s) -- nothing was written; this is a report
[fact] deployment is entirely manual end to end
from: 3f2a... 9c14...
mem import --from-jsonandscope_root: ascope="project"/scope="path"fact'sscopeRootis an absolute filesystem path from the machine it was exported on.mem import --from-jsonimports it verbatim (full fidelity), so re-importing an export from a different machine — or a different path on the same machine — leavesscopeRootpointing at a path that may not exist there. For ascope="project"fact captured inside a git checkout with a remote, thescope_repoidentity below covers that case and the fact still surfaces; ascope="path"fact, or a project fact from a checkout with no remote, still binds to the path alone.
Project identity (
scope_repo): ascope="project"fact captured inside a git checkout also records<normalized remote>#<root relative to the working tree>, so it surfaces from a second clone of the same repository, from a git worktree, and after an export/import onto another machine. The subpath half is what keeps a monorepo honest: one remote, many project roots, sopackages/aandpackages/bare different projects and neither sees the other's decisions. Identity only ever widens recall — the absolute-path binding is checked first and unchanged, and a fact with no identity (captured before this existed, or in a checkout with no unambiguous remote) is matched by path exactly as before. Reading is local and read-only:gitis never invoked, only.git's own files are parsed. SetTOKEN_GOAT_MEM_PROJECT_IDENTITY=pathto opt out and keep the path-only binding at both capture and recall — the case for it is two clones that are deliberately not the same project, such as a fork kept for experiments.Contradiction resolution deliberately still keys on
scope_root, notscope_repo: that key decides the persistedsuperseded/contestedtransitions, there is no honest backfill for facts captured before identities existed, and widening it would rewrite facts across checkouts on the firstmem epoch --gcafter upgrade. The cost is stated plainly — two facts on one subject captured in two clones are both in scope and are not detected as rivals.
Paste this into a terminal (uses a throwaway home so it never touches your real data):
export TOKEN_GOAT_MEM_HOME=$(mktemp -d)
mem remember "uses pnpm, not npm" --kind preference --subject package-manager --value pnpm
# remembered preference fact 79bce136-679f-471b-8ccb-fd18df7d2b36
mem remember "switched to bun" --kind preference --subject package-manager --value bun
# remembered preference fact 21a1330e-95d3-453c-81f0-49c792e1488f
mem recall
# 21a1330e stored pref (unverified, 2026-07): switched to bun
# mem show <id> for detailBoth facts share the subject package-manager with different values — a contradiction. Recall already prefers the newer fact and hides the loser; epoch --gc persists that resolution:
mem epoch --gc
# epoch=3 contradictions_resolved=1 preferences_decayed_below_floor=0 pruned_superseded_facts=0 pruned_sources=0 pruned_audit_log_rows=0 pruned_recall_log_rows=0
mem list
# 21a1330e-... [preference/active] package-manager=bun switched to bun
# 79bce136-... [preference/superseded] package-manager=pnpm uses pnpm, not npm
mem log
# [2026-07-14T09:12:05.310Z] 79bce136 epoch_contradiction: Superseded by fact 21a1330e-... on subject "package-manager" (scope=global): value "pnpm" superseded by newer/higher-provenance value "bun".
# [2026-07-14T09:11:48.902Z] 21a1330e capture_explicit: stored active preference fact (scope=global)
# [2026-07-14T09:11:40.127Z] 79bce136 capture_explicit: stored active preference fact (scope=global)mem log is the audit trail for the whole store, newest first: every capture, edit, pin, forget,
review decision, and import. Narrow it with --fact <id> (which also finds facts gc has since
deleted), --event <name> (--event capture matches the whole capture_* family), and
--age-days <n>.
Anchored facts are re-validated on every recall. An anchor that tests false excludes the fact from ground truth and routes it to review:
mem remember "repo has a yarn.lock" --kind fact --anchor "file-exists yarn.lock" --scope project --root .
mem recall --root . --scope project
# 888ba2c0 fact (contradicted, excluded): repo has a yarn.lock
# mem show <id> for detail; mem review to resolve contested/pending
mem review --root .
# -- anchor-contradicted (suppressed from ground truth) (1) --
# 888ba2c0-... [fact/active] repo has a yarn.lock(IDs are random UUIDs; yours will differ.)
Anchors are pure, read-only filesystem/git predicates — no shell-out, no network, bounded I/O, and paths are confined to the given --root:
| Predicate | Affirms when |
|---|---|
file-exists <path> |
the file exists under root |
file-absent <path> |
the file does not exist |
file-newer-than <a> <b> |
a is the currently-active file relative to b (e.g. the newest lockfile is pnpm's) |
file-contains <path> <substring> |
the file contains the substring (bounded read) |
file-not-contains <path> <substring> |
the file does not contain the substring |
newest-of <expected> <candidate...> |
among expected plus every listed candidate, expected is the sole existing file with the greatest mtime (e.g. "the newest lockfile is pnpm-lock.yaml", not just "pnpm-lock.yaml exists") |
glob-exists <pattern> |
some file matches the glob (*, ?, and a recursive ** segment) |
git-branch-is <branch> |
the repo's current branch matches |
git-tracked <path> |
the path is tracked in the git index |
valid-until <ISO date> |
the date has not passed. The one predicate that reads no filesystem or git state -- for a fact that is true until a date rather than until a file changes ("until the v2 migration lands, keep the shim"). A bare YYYY-MM-DD is read as the end of that day in the machine's own local time zone, so valid-until 2026-12-31 is still affirmed during the 31st wherever mem runs; an unparseable date is rejected at capture rather than silently reading as expired |
package-version <path> <name>@<version> |
path (a package.json) declares name at version in dependencies/devDependencies -- declared-manifest check only, not the installed/lockfile-resolved version; comparison is exact string match or major-version-prefix match only (no semver-range-satisfaction), so a genuinely ambiguous comparison is unverified, never a guessed affirmed |
Each evaluation yields affirmed, unverified (missing file, no repo, malformed predicate — cannot confirm or deny), or contradicted. Only affirmed is ground-truth eligible.
Mem works standalone. When token-goat is on PATH, token-goat reads mem epoch (a monotonic integer) to invalidate its compaction cache. The TGMEM/2 wire format is published as an optional seam for host tools to adopt; token-goat does not consume it today. It is not unused, though: mem init claude-code installs hooks that run mem recall --hint-format on SessionStart and UserPromptSubmit, so the format is what mem emits into Claude Code on every session and every prompt.
The seam is one-directional (Mem reads nothing from token-goat), stateless (live calls, no caching), and self-caveating (display strings include their own trust caveats). Contested or low-trust facts are excluded from --hint-format entirely — only ground-truth-eligible or explicitly-caveated hints are emitted. Mem does not cache results; forget/edit reflect instantly. If mem is not on PATH or the call times out, token-goat falls back to no hints (fail-open).
--hint-format emits TGMEM/2 by default: a header line, then one line per fact (pref fresh=affirmed|unverified|contradicted id=<uuid> display="<caveated text>"), then at most one shared footer line naming what is worth following up on: footer mem show <id> for detail, extended with N more in scope, not sent when the per-kind caps dropped results (2 of the block's 14 lines are reserved for pinned facts before those caps see the list, so a pin always arrives even against a full set of better-matching facts), N withheld; mem review to resolve contested/pending when facts were held back from ground truth, and no match for this query -- showing recent facts instead when a non-empty query matched nothing at all -- no lexical hit, no embedding or usefulness signal either -- so every fact-line the response does carry is filler the caps swept in by recency, not an answer to the query. When an embedding backend is configured but the ranking fell back to text match alone, the footer says so: embedding backend unavailable; ranked by text match only when the endpoint failed or answered with vectors that did not match, and stored embedding vectors come from a different or unrecorded model; run mem embed --all; ranked by text match only when the store's vectors were made by another model. The endpoint itself is never echoed. A response carrying only such a clause (no facts, no review queue) still ends with the footer line. Those counts exist because their absence is unfalsifiable from the wire — a capped payload is byte-identical to a complete one, and a response with no fact lines is byte-identical to a project with no memory, even while a review queue fills behind it. The footer is prose for a reader, not a parseable field; a response with nothing to follow up on carries none. When the store itself could not be opened -- a permissions failure, a WAL lock, a schema mismatch -- the response is still TGMEM/2 with zero fact lines, but the footer says footer store could not be read; mem doctor shows the underlying error instead of carrying nothing at all: mem doctor runs the same call that just failed, but answers to a human on stderr instead of failing open onto the wire. The same reasoning covers the other two ways this call can go silent without there being nothing to say: a soft retrieval time budget cutoff returns zero fact lines (never a smaller slice -- TGMEM/2 has no way to mark a response partial, so a reduced set would be byte-identical to a complete one) with a footer saying footer hint set empty; retrieval ran out of its time budget, not out of facts -- mem recall shows them, and an internal error downstream of a successful store open carries footer hints unavailable; an internal error stopped retrieval -- mem doctor shows the store state.
TGMEM/2 moved the per-fact follow-up hint (mem show <id>, resolve via mem review, ...) out of every display string and into that one footer line, since repeating the same CTA on every line was pure overhead once a consumer already knows the pattern. display itself is unchanged otherwise — still self-caveating, still meant to be surfaced verbatim.
TGMEM/1 (the original format: per-line CTA baked into display, no footer line) remains fully supported for callers that still parse it — pass protocolVersion: 1 to buildHintFormat() when calling the programmatic seam directly. The CLI itself always emits the current default version.
Re-running mem recall --hint-format on every host-tool turn works, but it re-opens the DB and re-runs retrieval every time even when nothing changed. mem epoch is the cheap alternative: it prints a single monotonic integer that is bumped by every write that can change what a recall returns (remember, edit, forget, pin, review --promote/--reject, used, facets, the epoch --gc retention pass) and left untouched otherwise.
A host tool can use mem epoch to detect store changes and only re-run mem recall when the store actually changed. However, the epoch covers store writes only — it does not cover anchor verdicts (filesystem and git state, re-evaluated live on every recall) or preference decay (a function of time). A polling consumer should therefore either call mem recall on a time interval to refresh anchors and decay, or monitor for working-tree events (branch switches, dependency installs) that may invalidate anchors.
Store-only polling pattern (refreshes store state only):
last_epoch=$(mem epoch)
# ... later, on each turn ...
current_epoch=$(mem epoch)
if [ "$current_epoch" != "$last_epoch" ]; then
mem recall --hint-format --root "$project_root"
last_epoch="$current_epoch"
fiTime-interval pattern (refreshes all state including anchors and decay):
last_recall=$(date +%s)
# ... on each turn ...
now=$(date +%s)
if [ $((now - last_recall)) -gt 300 ]; then # 5 minutes
mem recall --hint-format --root "$project_root"
last_recall=$now
fimem epoch with no flags never mutates facts (no GC pass, no writes) — it is safe to call as often as you like as a cheap store-state check.
Integration guides in docs/integrations/:
- Claude Code
- Copilot CLI
- Copilot in VS Code
- Copilot in Visual Studio
- Copilot in JetBrains IDEs
- Codex
- opencode
Any other agent that can run a shell command (Cursor, Windsurf, Cline, Aider, ...) can use the same patterns: mem recall --hint-format at session start, mem remember as decisions land.
Token-Goat Mem runs on your machine and writes to your local SQLite database. The software is provided as-is, without warranty of any kind. DFK Helper LLC is not liable for any damages arising from use. Full terms, including the No Liability clause, are in the LICENSE file.
Mem stores facts you tell it to remember and suggests candidate facts for confirmation. Never persisted by default: secrets, credentials, PII, high-entropy tokens, full file contents. Enforcement layers: (a) only short extracted facts are stored; (b) secret-pattern and entropy screening (a bare UUID is stored only when an identifier word such as id, session or uuid directly labels it and no credential keyword such as key, token or auth appears in the fact or its stored source ref; the transcript pointer mem scan-session stamps itself is exempt from that keyword check, and only when the fact's creation recorded that exact ref, an exemption a later mem import --from-json of the fact voids; a UUID beside a credential keyword is refused at any entropy, hyphenated identifiers with short numbers such as v2 or 2026 are not flagged, and the file path in an import excerpt is not credential context); (c) suggested facts shown in mem review before surfacing. Local-only by default: with neither TOKEN_GOAT_MEM_EMBED_URL/_MODEL nor TOKEN_GOAT_MEM_DREAM_URL/_MODEL set, nothing leaves this machine. Set the former and mem remember/mem suggest/mem embed send fact text and mem recall sends the query text (including every prompt, if wired via a coding tool's UserPromptSubmit hook) to that endpoint; set the latter and mem dream sends stored fact text to it. Run mem review to audit all stored facts.
Two details of the screen. A 32, 40 or 64 digit hex value is treated as a content hash and stored, unless a credential keyword (key, token, secret, password and similar) sits before it in its sentence or on the line before it with no hash word (commit, sha, checksum) nearer to it, a credential keyword sits among the 8 words after it (the rest of its line and the next) with no hash word before that keyword or directly before the hex, the hex is a fact's whole --value under a credential subject or text, or invisible characters split it (every Unicode format character, such as zero-width characters and directional marks, and every variation selector are removed before screening; the stored text is unchanged); so fixed in commit <40 hex> and <40 hex> is the commit that fixed the token parser are stored, the signing secret is <64 hex> and <32 hex> is the production API key are refused, and a hashed directory name inside an anchor path is not read as a secret. A genuine hash that trips the screen is let through by adding the exact value to .mem/allowlist. Second, the source ref mem scan-session and mem import --from-md stamp on a fact (a transcript path plus turn, or an import file path plus line) carries a session id that credential words in its path could make look like a secret, so it is exempt from the keyword check, but only for the fact whose own creation record holds that exact ref. Where that stamp cannot be proven (the fact was recreated by mem import --from-json, mem epoch --gc pruned its creation record, or the store predates the marker) the screen fails closed: editing or promoting the fact is refused as a possible secret, and the refusal names .mem/allowlist. Add the exact value (the UUID) to .mem/allowlist in the project root, one value per line, and the same edit is accepted.
Token-Goat Mem is source-available under the PolyForm Noncommercial License 1.0.0.
Personal use: free. Includes hobby projects, individual productivity, personal study, and private coding. Individual developers may install and use the software on their own machines for individual productivity purposes without a commercial license.
Commercial use or shared infrastructure: requires a license. Contact token-goat@dfkhelper.com for details.
Patent Pending.
Full terms in LICENSE.
Built and maintained by DFK Helper. If it saves your tokens or your sanity, a star means the world.
