npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

token-goat-mem

v0.4.1

Published

Local-first long-term memory for AI coding agents (Claude Code, Copilot, Codex): durable facts with trust levels and anchor-based staleness detection

Readme

Token-Goat Mem

Token-Goat Mem

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.

PolyForm Noncommercial requires Node.js

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


The problem

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.

What changes

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

How it works

  1. Explicit capturemem remember "uses pnpm not npm" --kind preference stores 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 separate sources table mem show --json exposes, which is designed to hold redacted content previews and is not yet written to by any capture path) and timestamp. --kind is required: preference, decision, fact, or correction. Saying the same thing again reaffirms the existing fact rather than writing a second row: captured_at and confidence are refreshed, and the command prints reaffirmed instead of remembered. 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 -- so identical text carrying a different value stays two facts for contradiction resolution to key on. Only live facts match: a pending one is never reaffirmed (that would promote it without review), a superseded one is never resurrected, and mem suggest never reaffirms at all, since derived text must not refresh a fact the user did not restate.
  2. 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), or contradicted (suppressed, flagged in review).
  3. Recall with trust levelsmem recall --kind preference returns 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.
  4. Review and resolutionmem review lists 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 next mem epoch --gc reinstates the survivor (back to pinned if it was pinned) instead of leaving it withheld.
  5. Forget and editmem forget <id> soft-deletes a fact (marks it superseded, kept for audit); mem edit <id> updates text, subject/value, anchor, or scope, and mem edit <id> --undo walks the last edit back. Both bump an internal epoch so the token-goat seam always sees the latest state.

Install

Requirements: Node.js 18 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.

Verify the install

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.

Where data lives

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.

CLI

| Command | What it does | |---------|-------------| | mem remember <text> | Store a new fact. --kind preference\|decision\|fact\|correction (required), --subject <key> + --value <value> (paired, for contradiction detection), --anchor <predicate> (optional), --scope global\|project\|path (default global), --source-ref <ref>, --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). | | 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 both a Stop and a PreCompact hook (the same command 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:), 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. | | mem export | Writes stored facts to stdout. --format json\|md (default json): 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). | | 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>. --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). --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. | | 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. 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. | | 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. --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. | | 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. --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). 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. | | mem review | Pending, contested, anchor-contradicted, and unanchored-but-checkable facts for human resolution. 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. 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; --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). | | mem forget <id> | Soft-delete a fact (marks superseded, kept for audit) and audit-log it. Bumps epoch. | | mem edit <id> | Change a fact's --text, --subject/--value (paired), --anchor, --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. --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). | | 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. 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 ready-to-run mem used ... invocation in its footer line naming that session and the facts just sent -- Claude Code's installed hooks pass --hook-stdin, 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. | | 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. | | 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. 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. | | 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. 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. | | 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. 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. | | 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 | Read-only environment/DB health check: db path, WAL journal mode, foreign-key setting, schema tables, current epoch, fact counts by status, source/audit-log row counts, embedding configuration (endpoint host and model -- never the API key) and coverage, the hint budget (how many facts are recallable against the at-most-14 lines one --hint-format block carries, and how many are pinned against the 2 reserved slots), and scope placement (project/path-scoped facts whose scope_root no longer exists on disk -- unreachable from any session, and healthy by every other line here). No options. | | mem init <tool> | Wires mem into a coding tool's config -- claude-code, codex, copilot-cli, copilot-vscode, copilot-visual-studio, or copilot-jetbrains -- automating what docs/integrations/*.md otherwise asks you to hand-copy. 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), --dry-run (print what would be written without touching disk). 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. | | 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.

Optional: semantic recall

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

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.

Optional: mem dream

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-json and scope_root: a scope="project"/scope="path" fact's scopeRoot is an absolute filesystem path from the machine it was exported on. mem import --from-json imports it verbatim (full fidelity), so re-importing an export from a different machine — or a different path on the same machine — leaves scopeRoot pointing at a path that may not exist there. For a scope="project" fact captured inside a git checkout with a remote, the scope_repo identity below covers that case and the fact still surfaces; a scope="path" fact, or a project fact from a checkout with no remote, still binds to the path alone.

Project identity (scope_repo): a scope="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, so packages/a and packages/b are 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: git is never invoked, only .git's own files are parsed. Set TOKEN_GOAT_MEM_PROJECT_IDENTITY=path to 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, not scope_repo: that key decides the persisted superseded/contested transitions, there is no honest backfill for facts captured before identities existed, and widening it would rewrite facts across checkouts on the first mem epoch --gc after 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.

Walkthrough

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
# stored pref (unverified, 2026-07): switched to bun
# mem show <id> for detail

Both 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

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

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.

Optional token-goat seam

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

TGMEM wire format

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

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.

Cheap polling with mem epoch

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

Time-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
fi

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

Works with

Integration guides in docs/integrations/:

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.

Disclaimer

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; (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.

License

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 [email protected] 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.