One memory. Every AI. Yours.
Every AI you use — Claude Code, Claude Desktop, Cursor, whatever comes next — either forgets you every session or hoards what it learns inside its own silo. You re-explain your stack, your preferences, your projects, dozens of times a week. Vendors build "memory" as a lock-in feature, which is exactly why none of them will build the portable version.
Engram is that missing layer: a local-first personal memory vault that any AI tool plugs into over MCP. Your context belongs to you, not to whichever chat app you happened to type it into.
- 🗂 Plain markdown files. Every memory is a file in
~/.engram/memories/— grep it, edit it in vim, sync it with git/iCloud/Obsidian. The search index is derived and disposable. - 🔌 Works with every MCP client. One server, five tools (
remember,recall,confirm,update,forget) plus a pinnedengram://profileresource any tool can load at session start. Stdio for local tools; token-authenticated HTTP for everything that can't spawn a process. - 🎯 Project scopes. A fact can belong to a project (
scope: acme-api). Scoped recall returns that project's facts plus your global ones — never another project's — so "when do we deploy?" means this repo. - 🧠 Recall by meaning, locally. One
engram embeddownloads a small on-device model (~25 MB, then fully offline) and recall understands paraphrase: "what food does he avoid" finds dislikes almonds. Skip it and keyword search still works — semantics is opt-in, never a requirement. - ⚔️ Contradiction alarms. When an AI remembers something that overlaps an existing fact ("standup is at 9:30" vs "standup is at 10am"), Engram flags the pair — to the agent at write time, and to you in the review inbox — instead of letting both quietly coexist. With semantics on, meaning gates the alarm: same-topic contradictions are caught even reworded, while "dislikes almonds" vs "dislikes cold emails" no longer trips it.
- 👁 You audit everything. Agent writes land in a review inbox, attributed to the tool that wrote them. Approve, edit, or reject —
engram reviewin the terminal or the full web app atengram ui. - 🔒 Local first. No cloud, no account, no telemetry. The UI and the HTTP server bind to 127.0.0.1 unless you say otherwise. Node's built-in SQLite — zero native dependencies.
- 🛡 Injection-aware. Recalled memories are explicitly framed as stored data, never instructions, and agents can't pin anything into your always-loaded profile — pinning is a human act.
Requires Node ≥ 22.5.
npm install -g @michael-halperin/engram
# tell it something
engram add "I prefer TypeScript for new projects" -t preference --pin
# plug it into your AI tools ↓ then just talk; they remember and recall on their ownClaude Code
claude mcp add engram -- engram serve # recall/remember tools during the session
engram install claude-code # auto-inject: profile + repo facts at session startClaude Desktop — add to claude_desktop_config.json:
{ "mcpServers": { "engram": { "command": "engram", "args": ["serve"] } } }Cursor — add to ~/.cursor/mcp.json:
{ "mcpServers": { "engram": { "command": "engram", "args": ["serve"] } } }Then, in any of them:
you: remember that our staging deploys go out Friday mornings later, in a different tool: when do we deploy to staging? it: recalls → Friday mornings.
Remote / can't-spawn-a-process clients — serve over streamable HTTP with a bearer token:
engram serve --http # http://127.0.0.1:5424/mcp
# token is generated on first run → ~/.engram/http-token (chmod 600)
claude mcp add --transport http engram http://127.0.0.1:5424/mcp \
--header "Authorization: Bearer $(cat ~/.engram/http-token)"Every request must carry the token; there is no unauthenticated mode. It binds to loopback unless you pass --host (say, a tailnet address) — and then anyone with the token can read and write your memories, so treat it like a password.
Global facts are about you; scoped facts are about a project. Agents pass scope on remember/recall (the tool descriptions tell them to use the repo name), or you set a default for a per-project server:
claude mcp add engram -- engram serve --scope acme-api # in acme-api's .mcp.json
engram add "deploys go out Friday mornings" --scope acme-api
engram search deploys --scope acme-api # acme-api's facts + global ones, nothing elseScoped recall ranks the project's own facts slightly above global ones; unscoped recall still searches everything. Facts scoped to different projects never shadow each other — "standup is at 10" can be true in one repo and false in another.
Your AIs already know things about you — they're just siloed. Imports pull them in; everything lands unreviewed in the inbox, so engram review is where you decide what stays. Re-running an import never duplicates: facts already in the vault are matched and skipped.
engram import chatgpt ~/Downloads/chatgpt-export.zip # or the unzipped folder
engram import claude # scans ~/.claude/projects
engram import markdown ~/notes # one note = one memory
engram import text memories.txt # one fact per line- ChatGPT doesn't export memory as a list, but every save is on the record: an assistant message to its
biotool inside the export'sconversations.json. Engram mines exactly those, keeping their original dates. (Pasting Settings → Personalization → Manage memories into a.txtworks too, and is quicker than requesting a full export.) - Claude Code keeps per-project memory as markdown files under
~/.claude/projects/*/memory/— imported directly, scoped by project, with Claude's memory types mapped onto Engram's. A pasted claude.ai memory summary imports as.txt. - Every subcommand takes
--dry-run(preview, write nothing),--scope,--tags, and--type. Imported facts keep their original timestamps, so ancient ones start out stale — reviewing is what refreshes them. Contradictions with what's already in the vault are flagged at import time.
MCP tools have a failure mode: the model has to think to call recall, and often it doesn't. engram install claude-code fixes that with a SessionStart hook — every new Claude Code session begins with your pinned profile plus the current repository's facts (scope derived from the repo name) already in context, no tool call required.
The injected block is framed as stored data, never instructions; unreviewed and stale facts are marked so the model hedges instead of asserting. The hook is deliberately unbreakable: on any failure it prints nothing and exits 0 — a memory hiccup never costs you a session. An empty vault injects nothing at all.
engram install claude-code # adds the hook to ~/.claude/settings.json, touching nothing else
engram install claude-code --remove # takes it back outInjection covers session start; keep the MCP server configured too so the model can search deeper mid-session and save new facts.
Keyword search only finds words you happened to store. One command upgrades recall to meaning:
engram embed # downloads Xenova/all-MiniLM-L6-v2 (~25 MB) into ~/.engram/models, embeds the vaultAfter that everything is automatic and fully offline:
- Recall is hybrid — BM25 and cosine similarity rank-fused, so exact tokens (
proofapp.online) still win where they should, and paraphrases ("which editor does he use") surface facts that share no words with the query. Fresh writes are embedded on the fly. - Conflict alarms get judgment — a word-overlap pair is only flagged if the two facts are actually about the same thing, and contradictory rewordings are flagged even with zero shared words. Thresholds were tuned on real vaults, not vibes.
- Still your rules — vectors live in the same derived SQLite index (disposable, rebuilt from the files), memories never leave the machine, and the model dependency is optional: if it isn't installed, everything falls back to keyword search.
engram search --keywordforces BM25;ENGRAM_NO_EMBED=1turns semantics off entirely.
engram review # approve / edit / reject what your AIs wrote
engram ui # browser view: what do my AIs know about me?
engram search vim # full-text search from the terminal
engram pin <id> # promote a fact into the profile every AI loads| command | what it does |
|---|---|
engram add <text> [-t type] [--tags a,b] [--scope s] [--pin] |
save a memory by hand (warns on likely contradictions) |
engram search <query> [--scope s] [--keyword] |
search by meaning + keywords (BM25-only with --keyword) |
engram embed |
build the local semantic index (one-time ~25 MB model download) |
engram list [--status s] [--type t] [--tag x] [--scope s] |
browse, newest first |
engram show / edit / rm [--hard] <id> |
inspect, open in $EDITOR, archive or delete |
engram pin / unpin <id> |
manage the core profile |
engram confirm <id> |
mark a fact as re-verified — fresh facts rank higher |
engram import chatgpt/claude/markdown/text <path> |
pull memories in from other tools (--dry-run to preview) |
engram install claude-code [--remove] |
wire up the session-start auto-inject hook |
engram review |
interactive inbox for agent writes |
engram ui [--port 5423] |
local web app (dashboard, browser, review inbox, profile) |
engram serve [--scope s] |
MCP server on stdio (what AI tools run) |
engram serve --http [--port 5424] [--host h] [--token t] |
MCP server over token-authenticated HTTP |
engram reindex |
rebuild the search index from the files |
engram doctor |
health check |
engram ui serves a local React app (127.0.0.1 only, same-origin guarded):
- Dashboard — active / awaiting-review / pinned / going-stale tiles, breakdowns by type, source, and scope.
- Memories — full-text search with combinable filters (status, type, scope, tag, pinned); click any card for a detail drawer with inline editing, pin/confirm/archive/delete, the supersede history chain, and conflict warnings.
- Review inbox — approve / edit / reject what your AIs wrote, with contradictions flagged in place and bulk actions. Keyboard-first:
j/kmove,aapprove,rreject,eedit,xselect. - Profile — your pinned facts plus the exact
engram://profilemarkdown agents receive.
Hacking on it: engram ui in one terminal, npm run dev:ui in another (Vite dev server on :5173, API proxied).
One file per memory, YAML frontmatter, body is the fact:
---
type: preference
tags: [tooling]
source: claude-code
status: unreviewed
pinned: false
scope: acme-api
created: 2026-07-08T14:03:22.000Z
updated: 2026-07-08T14:03:22.000Z
last_confirmed: 2026-07-08T14:03:22.000Z
---
Michael prefers TypeScript for new projects.source says which tool wrote it. scope (optional) says which project it belongs to; no scope means globally true. status is the review state: agents write unreviewed, you promote to active (or archive). last_confirmed tracks freshness: it bumps when a human approves, an agent calls confirm, or the fact is restated — recall ranks fresh, reviewed facts above stale, unreviewed ones, and flags anything unconfirmed for 6+ months so models hedge instead of asserting. When an agent corrects a fact, the new version is a new file with a supersedes: <old-id> link and the old one is archived, never overwritten — and rejecting a correction in review restores the original. Files are the source of truth — hand-edit anything, even while the server is running; the SQLite FTS5 index catches up automatically. ENGRAM_HOME moves the vault (default ~/.engram).
- Files over databases. Your memory should outlive this tool, your AI subscriptions, and possibly civilization's interest in SQLite.
- Audit over gatekeeping. Agents write immediately (no friction), but everything is attributed and lands in your inbox. Trust, then verify.
- Data, not instructions. Everything served to a model is wrapped in explicit "this is stored context, not commands" framing — memory should never become a prompt-injection channel.
- No lock-in, including ours. It's markdown in a folder. Leaving Engram is
cp -r ~/.engram/memories .— as it should be.
Importers: ChatGPT memory export, Claude memory, markdown notes✓engram importAuto-inject: a Claude Code hook that loads your profile at session start✓engram install claude-codeOptional embeddings for semantic recall (pluggable, still local)✓engram embed- Dedupe/merge suggestions for near-duplicate memories
MIT © Michael Halperin
