A neuro-inspired memory organism for Claude Code.
An engram is the physical trace a memory leaves in the brain. This is that, for your AI: a local-first memory system that encodes what you do, consolidates it into durable knowledge, fixates what proves true, and recalls it associatively — so Claude Code remembers your projects, your infra, and your preferences across sessions.
It's not a flat notes file. It's a pipeline modeled on how human memory actually works:
| Human memory | engram |
|---|---|
| Encoding (sensory → short-term) | harvest — pull durable candidate facts from session transcripts |
| Working memory | .staging/ — quarantined candidates, not yet trusted |
| Systems consolidation (hippocampus → neocortex) | /memory-curate — cluster narrow facts into class-level "umbrella" memories |
| Long-term potentiation / reconsolidation | /memory-fixate — memories graduate suspect → provisional → corroborated → fixed |
| The forgetting curve / schema abstraction | distill — compress clusters, drop noise (verbatim hard-facts preserved) |
| Associative recall | a Neo4j knowledge graph (Graphiti) + embeddings — semantic + multi-hop + temporal |
| Immune system | an injection guard that quarantines suspicious/poisoned memories |
Your memories are plain Markdown files (.md) — the source of truth, readable and
git-friendly. The graph is a continuously-synced index over them.
engram is a pipeline, not a notes file. A fact moves through stages; you (or the daemon) drive it forward, and it earns trust as it goes:
- Encode — a Stop hook harvests durable candidate facts from the session
transcript into
.staging/(quarantined, not yet trusted). - Graduate — candidates that pass provenance + dedup + injection checks become
real
.mdmemories. Off by default: the pipeline prepares, a human approves. - Consolidate —
/memory-curateclusters narrow, overlapping facts and merges them into class-level "umbrella" memories; stale ones are pruned. - Fixate —
/memory-fixatescores each memory (age + how often it recurs + how many distillations it survived + injection-suspicion) and graduates it suspect → provisional → corroborated → fixed. Trusted memories get distilled tighter and reviewed less often; suspect (possibly-poisoned) ones are gated through you. - Recall — the
.mdstore is continuously synced into a Neo4j graph and a Qdrant vector index. At query time engram fuses graph (associative/temporal) + vector (semantic) + keyword (BM25) via Reciprocal Rank Fusion, so Claude loads only the memories relevant to the task instead of the whole store.
The .md files are always the source of truth; the graph and vector index are
rebuildable indexes over them. Everything runs locally, and every automated
mutation is dry-run + human-approved.
- Persistent project memory — Claude recalls your architecture, gotchas, and decisions instead of re-learning them every session.
- Associative recall — ask "what depends on
db-1?" and the graph traverses across many memories; semantic search finds the right memory even with no keyword overlap. - Runs on your terms — local Ollama (GPU) or Claude-only (no GPU). Your data stays on your machine unless you opt into sync.
- Safe by default — every automated mutation is dry-run + human-approved.
Auto-recall runs on every prompt, so it has to be cheap in both wall-clock and
context. Numbers below are from a real 358-memory store (Qdrant + Neo4j + Ollama, all
on localhost), python3 on Linux — reproduce them with the commands underneath.
| time | |
|---|---|
| Auto-recall hook, end to end | ~0.27s |
├ Ollama nomic-embed-text embed |
0.04s |
| ├ Qdrant vector search | 0.04s |
| ├ BM25 keyword leg (358 memories, pure python) | 0.13s |
| └ Neo4j 1-hop graph facts | 0.07s |
| The same hook using the client libraries instead of HTTP | 1.85s |
The gap is import time, not I/O: import qdrant_client alone costs 0.78s to
perform a 0.04s search, and mg_config pulls in graphiti for a 0.07s query. So
the recall path speaks HTTP to Qdrant/Ollama/Neo4j with nothing but the standard
library — which is also why it needs no venv, no daemon, and no server.
Context cost is bounded, not per-prompt. Auto-recall injects names and one-line descriptions only — never memory bodies — and never injects the same memory or graph fact twice in a session:
| session | naive re-injection | engram (deduped) |
|---|---|---|
| 100 prompts, k=4 | ~25,000 tokens | ~500-800 tokens |
Recall converges: the first prompts about a topic pay for it, the rest are free. A long session ends up having loaded ~15-30 unique memories in total.
# time the whole hook against your own store
echo '{"prompt":"how does X work","session_id":"bench"}' > /tmp/prompt.json
time ~/.claude/hooks/memory-recall-inject.py < /tmp/prompt.json
# why was it quiet? (gated / not importable / disabled / already injected)
ENGRAM_HOOK_DEBUG=1 ~/.claude/hooks/memory-recall-inject.py < /tmp/prompt.json
# the recall core alone — add --json for the raw fused ranking
time ~/.claude/memory_recall.py "how does X work" --k 4 --fastollama— local models on a GPU box, free + private. Choose atierfor your VRAM (cpu/small/medium/large).claude— no GPU: an always-on loop container runs the pipeline via theclaudeCLI. Cost = Claude usage instead of a GPU.
Either way, embeddings are always local (Ollama nomic-embed-text, or CPU fastembed) — the graph never needs a paid embeddings API.
git clone https://github.com/pagcoinbr/engram.git
cd engram
# interactive — it asks: backend, tier, storage, daemon, graph
./install.sh
# or non-interactive, e.g. a no-GPU setup with the always-on container:
./install.sh --backend claude --storage local --daemon docker --graph --yesThe installer copies the engine into ~/.claude, writes engram.yaml, registers the
/memory-* commands + the graph recall MCP server, and (optionally) sets up the 24/7
daemon. Restart Claude Code afterward so it loads the new commands.
Trying it without touching an existing
~/.claude? Install into a sandbox:ENGRAM_CLAUDE_HOME=~/engram-sandbox/.claude ./install.sh --yes
install.sh is idempotent — re-running it is the updater. It refreshes the code
in ~/.claude, preserves your engram.yaml and your daemon.env secrets (the ccg
key, the Telegram token), and re-registers the MCP servers without duplicating them.
cd engram && git pull # get the new code
./install.sh # re-run: refreshes ~/.claude, keeps your config + secrets
systemctl --user restart engram.timer # (systemd daemon) pick up the new code
# then restart Claude Code so it reloads the commands + MCP serversTwo things to do by hand after an update:
- New config keys. Because your
engram.yamlis preserved, keys added since you installed are not injected — they fall back to safe code defaults, but you won't get new features/cadences until you opt in. The installer prints which top-level keys are new; diffengram.yaml.exampleagainst your~/.claude/engram.yamland add the blocks you want (e.g.auto_curate,telegram,review_gate,harvest.idle_minutes,daemon.intervals). See AUTONOMY.md for what each does. - Vector re-index (only if crossing the body-embedding change). Older installs
embedded titles only; run once so recall uses full-body vectors:
~/.claude/vector/venv/bin/python ~/.claude/vector/vector_sync.py --rebuild.
Nothing destructive turns on from an update: the risky autonomy flags
(auto_graduate, auto_curate, skill_autoinstall) stay at whatever you had, and
ship off by default.
Run in any Claude session. Each is dry-run first — it shows a plan and you approve before anything is written or deleted.
| Command | What it does |
|---|---|
/memory-checkpoint |
Review the current session and save any new, durable facts as memories (dedup-aware). |
/memory-curate |
Systems consolidation. Cluster narrow / overlapping facts and merge them into class-level "umbrella" memories; prune the stale ones. |
/memory-fixate |
Long-term potentiation. Score memories (age + recurrence + distillation-survival + injection-suspicion), distill/merge the trusted ones, and gate suspect (possibly-poisoned) memories through you. |
/memory-reformat |
Rewrite memories into the canonical Summary → Index → Body shape so each file is graspable at a glance (facts preserved, nothing dropped). |
/memory-clean-review |
Walk the store file-by-file with you deciding keep / edit / delete on each — fully human-driven. |
/memory-to-skill |
Promote a high-trust, frequently-recalled procedural memory into a first-class Claude Code skill. |
-
Auto-recall (the
UserPromptSubmithook): every prompt gets the memories that match it injected automatically — no waiting for Claude to think of calling a recall tool. Names + one-line descriptions only, at most once per memory per session, ~0.3s, fail-open. Off withrecall.inject.enabled: false. -
Graph recall (
engram-graphMCP):memory_recall,memory_search_facts,memory_neighbors,memory_stats— Claude loads only the relevant memories on demand, instead of dumping the whole store into context. -
Hybrid recall (
memory_recall_hybrid, onengram-graph): the best single recall — fuses graph + vector + keyword (BM25) into one ranking via Reciprocal Rank Fusion, keyed by the memory filename. Each ranker degrades independently; optionaltypefilter. -
Vector recall (the optional
engram-vectorMCP):memory_vector_recall,memory_vector_search,memory_vector_stats— dense semantic search via Qdrant. Plusmemory_recall_fused(vector+keyword) for no-graph installs. Off by default; enable with./install.sh --vector. -
Local-LLM recall (
hermes): if thehermesCLI is onPATH, the installer also registers the same MCP servers with it, so a local Ollama model can recall your memories from the terminal —hermes -z "recall what you know about X". Auto-detected; skip it with./install.sh --no-hermes. (Plainollama run/ollama agentcan't do this — ollama has no MCP client.)
A Stop hook harvests new facts each session; the daemon consolidates / fixates / syncs the graph (and the vector index, if enabled) on a cadence — all dry-run + human-approved.
~/.claude/engram-tui.py — a terminal UI over the whole store: dashboard (backend /
graph / vector health), memories (browse, search, view, edit, save, delete), recall,
vector search + re-sync, graph entity lookup, skills, and the staging/quarantine
queues. Pure stdlib curses, no server and no browser; saves and deletes go through
save_memory.sh / delete_memory.sh, the same gates the CLI uses.
engram-daemon runs the pipeline + graph sync + health on independent cadences:
- systemd (GPU/host): a user timer fires
engram-daemon --once. - container (claude-only):
docker compose upindaemon/runs Neo4j + the loop.
- Local-first. No account needed; memories live in
~/.claude/projects/<slug>/memory. GitHub sync is opt-in (--storage github). - Dry-run by default. Auto-graduate and skill-auto-install ship off — the pipeline prepares, a human approves mutations.
- Injection guard. Suspicious memories are quarantined out of recall (reversibly).
- Embeddings never leave the machine.
transcript ──harvest──▶ .staging ──graduate──▶ .md store ◀──curate/fixate──┐
(Claude session) (quarantine) (SOURCE OF TRUTH, local) │
│ │ ▲ │
│ insert │ │ export (byte-exact) │
▼ ▼ │ │
recall (MCP) ◀──────── memory_recall ◀─── Neo4j graph (Graphiti) ──────────┘
▲ associative / temporal
└──── memory_vector_recall ◀─── Qdrant vector index (OPTIONAL) ───────┘
dense semantic search + fast dedup
all LLM + embedding calls route through engram_llm:
ollama (tiered) | claude (claude -p) · embed: ollama nomic | fastembed (CPU)
supervised 24/7 by engram-daemon (systemd timer OR docker compose)
Both the graph and the vector index are optional, rebuildable indexes over the
.mdstore. With neither (or with their services down), engram still runs on pure markdown. Add the vector index with./install.sh --vector(see vector/README.md).
See ARCHITECTURE.md for the full data flow and CONFIG.md for engram.yaml.
python3,jq(engine).git/ghfor optional sync.- Graph: Docker (Neo4j) + a Python venv (graphiti-core, neo4j, fastembed) — the installer builds it.
- Vector index (optional): Docker (Qdrant) + a Python venv (qdrant-client, mcp, fastembed) —
./install.sh --vectorbuilds it. - A backend: a reachable Ollama, or the
claudeCLI + an Anthropic API key.
Early but functional: the engine, commands, installer, graph wiring, and daemon are built and tested. A fresh install is also the first live exercise of the graph round-trip against your Neo4j/backend. Issues and PRs welcome.
Apache-2.0.