1. Executive Summary
engram, published on npm as engramx, is a local
code-intelligence layer for Claude Code and similar hosts. It builds a
structural graph of a repository in one SQLite file, intercepts reads
and edits through hooks, and adds a mistake memory: nodes mined from
reverts, fix commits and project notes that warn the agent before it
edits a file with a history.
What is notable is the revert record. A git revert
becomes a mistake with the belief it overturned, the date it was found
false, and the revert that superseded it, rendered as "then you
believed … found false … truth now".
What is weak is where that memory lives. Mistakes share the
source_file column with derived code nodes, so the paths
that refresh code delete them, and every store open rewrites the whole
file on close.
Most of the tree is not memory. The AST miner, the reference graph, PageRank, the Read and Grep interception gates, the provider resolver and the token accounting are a context-compression product. This report covers the mistake, decision and pattern nodes, which state things that can be false, and the code graph only where the two share a table.
The mistake memory is built on the graph's own node table
(src/graph/schema.ts:3-53): a mistake is a
GraphNode with kind: "mistake", four optional
bi-temporal fields added by migration 9
(src/db/migrate.ts:206-211), and a valid_until
column added by migration 8 (:186-194) that the readers
honour and nothing writes.
One mark, negative_eval, for a committed case in which a
per-file lookup leaves out another file's mistakes after a positive
control. Section 9 names the six withheld.
Five other repositories named engram have reports, and none is this one: engram, engram-alpha, engram-cognitive, engram-format and engram-provable. The licence is Apache-2.0 with no rider.
2. Mental Model
A memory here is a graph node of kind mistake,
decision or pattern. None is ever a candidate:
a node exists or it does not, and a node's weight in retrieval is a
confidence score set by its miner.
Beliefs arrive from four producers, all regular expressions and git.
mineGitRevertspairs a revert commit with the commit it reverts and writesthenBelieved(the original subject),foundFalseAt(the revert's author time),truthNow("Reverted in <sha>") andappliesTo(a label derived from the changed paths), at confidence 0.9 (src/miners/git-revert-miner.ts:196-219).mineBugFixCommitsturns afix:-prefixed or issue-closing commit into"Bug fixed here: <subject>"at 0.6 (src/miners/git-bugfix-miner.ts:75-86,:126-141).mineSessionHistoryruns three regex families overCLAUDE.md,.claude/CLAUDE.md,AGENTS.md,.cursorrules,.cursor/rulesand every.engram/sessions/*.md(src/miners/session-miner.ts:14-29,:95-125). Abug: …line becomes a mistake at 0.6, adecided X over Yphrase a decision at 0.7, apattern: …line a pattern at 0.65.learnruns the same regexes over a caller's text and promotes any mistake it finds to 0.85 andEXTRACTED(src/core.ts:620-642).
Identity is derived from the words. Session nodes
take session_<kind>_<label> lowercased,
stripped to alphanumerics and cut at 60 characters
(session-miner.ts:31-39). Two lessons whose normalised text
shares its first 44 characters are one node, and the second replaces the
first. Revert and fix nodes are keyed on commit shas and replace
themselves on re-mine.
A belief stops being one in three ways, none of them a
decision about it. A full engram init deletes
every node and re-mines (src/core.ts:171-173), so git-mined
mistakes return, bounded to the last 200 or 300 commits, while every
learned node is gone. A single-file reindex deletes every
node whose source_file is that file, then re-inserts only
AST nodes (src/watcher.ts:124-138), so the file's mistakes
disappear until the next full init. The third exit,
valid_until, is declared and never set (section 5).
Diagram source
%% caption: how a mistake node is produced, where it is read, and the two paths that delete it without anyone deciding to
flowchart TD
RV["git revert commit"] --> MR["mineGitReverts:<br/>thenBelieved, foundFalseAt,<br/>truthNow, score 0.9"]
FX["fix: or closes #N commit"] --> MB["mineBugFixCommits:<br/>'Bug fixed here', score 0.6"]
NOTES["CLAUDE.md, AGENTS.md,<br/>.cursorrules, .engram/sessions"] --> MS["session regexes:<br/>bug:, fix:, decided X over Y,<br/>score 0.6 to 0.7"]
L["engram learn / POST /learn"] --> ML["same regexes,<br/>mistakes promoted to 0.85"]
MR --> N[("nodes table,<br/>kind = mistake,<br/>keyed by source_file")]
MB --> N
MS --> N
ML --> N
N --> E{"PreToolUse Edit or Write:<br/>source_file = edited path,<br/>score at least 0.8?"}
E -- "yes" --> W["landmine in additionalContext;<br/>deny if GUARD=2"]
N --> SS["SessionStart and PreCompact:<br/>3 newest by last_verified"]
N --> Q["query_graph: substring match,<br/>score x 2.5, warning block"]
INIT["engram init, full"] -- "clearAll, re-mine git and notes" --> N
RX["watch, reindex,<br/>opt-in reindex hook"] -- "deleteBySourceFile:<br/>the file's mistakes go too" --> N
VU["valid_until"] -. "read by 2 read paths,<br/>written by nothing" .-> N3. Architecture
engramx is a Node 20 TypeScript package with three
binaries: the engram CLI, an alias, and
engram-serve, a stdio MCP server
(package.json). Storage is sql.js, SQLite
compiled to WebAssembly: GraphStore.open reads the whole
.engram/graph.db into memory and save writes
the whole exported database back with writeFileSync
(src/graph/store.ts:29-40, :102-105).
close always calls save
(:669-672).
That makes every open-and-close a whole-file write, including reads.
Each hook invocation is a separate process, and mistakes(),
getFileContext() and queryGraph() all open and
close the store; queryGraph also increments
query_count on its seeds
(src/graph/query.ts:128). The only lock in
src/ guards init
(src/core.ts:82-95).
The hook layer is engram intercept, registered by
engram init into the project's
.claude/settings.local.json on PreToolUse, PostToolUse,
SessionStart, SubagentStart, UserPromptSubmit, PreCompact, CwdChanged
and Stop unless --no-hook is passed
(src/intercept/installer.ts:22-31, :105-155;
src/cli.ts:136-206). An HTTP server bound to
127.0.0.1 with Host, Origin, bearer-token and JSON
content-type checks adds POST /learn
(src/server/http.ts:336-367, :744-756).
Providers resolve per-file context packets from the graph, git,
Claude Code's own MEMORY.md index, an external
mempalace CLI, Context7, Obsidian and configured MCP
servers, caching results in a provider_cache table
(src/providers/). Of these, only the graph belongs to
engram's memory; the others are readers of someone else's store.
Deployment and ergonomics
One npm install -g engramx and engram init
in a project. No API key, no model call, no service; everything runs in
the hook processes. The store is a SQLite file any client can open, and
mistakes can be listed with engram mistakes, but there is
no command to remove one. The update check and optional providers reach
the network, which the README states.
4. Essential Implementation Paths
Mine on init. init runs
extractDirectory, mineGitHistory,
mineGitReverts, mineBugFixCommits and
mineSessionHistory (src/core.ts:112-120), then
either clears everything (full) or calls removeNodesForFile
for each sourceFile in the new node set (incremental), and
bulkUpserts (:160-174).
Teach. engram learn <text> →
learn(projectRoot, text) with the default source label
"manual" (src/cli.ts:622-634);
POST /learn passes the body's file, defaulting
to "http-api" (src/server/http.ts:363). Both
reach learnFromSession
(session-miner.ts:130-132) and
store.bulkUpsert.
Warn on edit. dispatchPreToolUse runs
handleEditOrWrite and then applyMistakeGuard
for Edit and Write, and applyMistakeGuard alone after
handleBash
(src/intercept/dispatch.ts:199-214).
handleEditOrWrite calls mistakes() with
sourceFile, a limit of 5 and
minConfidence: 0.8
(src/intercept/handlers/edit-write.ts:171-180;
src/intercept/safety.ts:48). The guard re-reads by file
through getNodesByFile, drops rows whose
validUntil has passed and rows under the floor, and denies
the call in strict mode
(src/intercept/handlers/mistake-guard.ts:133-138,
:314-335).
Warn on Bash. The guard's Bash arm scans every
mistake and matches metadata.commandPattern, which nothing
writes, or a sourceFile longer than five characters that
appears in the command (mistake-guard.ts:151-195).
Brief. SessionStart and PreCompact inject the three
newest by last_verified mistakes through
mistakes()
(src/intercept/handlers/session-start.ts:276-279;
src/intercept/handlers/pre-compact.ts:138). The Read path's
engram:mistakes provider lists up to five per file
(src/providers/engram-mistakes.ts:25-39).
Query. queryGraph seeds from
searchNodes, a LIKE over label and id ordered
by query_count (src/graph/store.ts:201-214),
multiplies a mistake's score by 2.5
(src/graph/query.ts:94-96), and renders matched mistakes in
a warning block above the nodes (:286-313).
MCP. Six tools, all reads: query_graph,
god_nodes, graph_stats,
shortest_path, benchmark and
list_mistakes (src/serve.ts:55-116).
Export into notes. engram gen writes a
marker-bounded block into CLAUDE.md and
AGENTS.md whose default view lists five mistakes and five
decisions (src/autogen.ts:86-102, :165-175).
engram memory-sync writes a similar block into the
project's MEMORY.md
(src/cli.ts:1613-1714).
5. Memory Data Model
| Column | Written by | Read by |
|---|---|---|
kind |
every miner | every mistake read |
label |
miner or learned text | all renders, searchNodes |
source_file |
the file a commit touched, a notes file's basename,
manual, http-api, or a caller's
file |
the per-file lookups, deleteBySourceFile,
removeNodesForFile |
confidence, confidence_score |
miner constant; learn raises mistakes to 0.85 |
the 0.8 floor; the enum only changes rendering |
last_verified |
commit time or mining time | ordering and --since |
then_believed, found_false_at,
truth_now, applies_to |
the revert miner only | the landmine and engram mistakes renderers |
valid_until, invalidated_by_commit |
nothing outside tests | engram:mistakes provider, mistake guard |
The validity column has no producer. Migration 8's
comment says valid_until is set "when the referenced
code was refactored away", and the provider's comment names the git
miner as the writer
(src/providers/engram-mistakes.ts:28-32).
src/miners/git-miner.ts contains no invalid in
any spelling, and the only assignment outside tests is
upsertNode binding whatever the node carries
(src/graph/store.ts:122-123). Two read paths filter on it.
core.mistakes(), which serves the Edit warning, the briefs,
the CLI and MCP, does not (src/core.ts:671-723), and
neither do queryGraph or engram gen.
The bi-temporal fields are one event.
foundFalseAt and lastVerified are both the
revert's author time; when the original belief started is not stored,
and nothing records when engram learned it apart from being overwritten
on each mine.
Scope is the directory. One graph.db
per project root. The source_file path is a relevance key,
not a boundary, and learned mistakes can name any path.
Declared and unwired: mesh sharing.
src/mesh/types.ts:91-104 declares a
mistake.shared payload with observedAt and
validUntil, and decision.shared beside it. No
code sends or receives either, and the mesh audit log has one writer,
engram mesh init, which records key_rotate
(src/cli.ts:753).
6. Retrieval Mechanics
There are three retrieval shapes, and none uses a vector.
- By file. Exact equality on
source_file, the lookup that powers the landmine. It is precise for git-mined mistakes, which carry a real path, and blind to session-mined ones, which carryCLAUDE.mdorsessions/x.md. - By recency.
mistakes()sorts bylast_verifiedand slices, so the SessionStart brief shows the three newest regardless of relevance. - By text.
searchNodesis a substringLIKEover up to 200 nodes per term, BFS then expands neighbours; mistakes are boosted but have no edges, so they only enter by matching the query's words.
The proactive floor is the one precision control, and it is a score
threshold: reverts (0.9) and learned mistakes (0.85) warn, mined fix
commits and session lines (0.6) do not. learn's default
source label reaches the Bash arm. A CLI-learned mistake carries
source_file = "manual", six characters, so every mistake
taught through engram learn matches any Bash command
containing manual (mistake-guard.ts:182) and,
under ENGRAM_MISTAKE_GUARD=2, denies it.
Injection is bounded: 3 mistakes in a brief, 5 per edit warning, 5 per file from the provider, labels truncated at 500 characters.
7. Write Mechanics
Writes are synchronous, local and model-free. init mines
and writes in one process; learn is one CLI call. A learned
mistake is retrievable immediately. No background pass rewrites memory;
the watcher and reindex hook touch one file at a time.
Nothing deletes a mistake on purpose, and two paths delete
them by accident. syncFile calls
deleteBySourceFile(relPath), which removes every node with
that source_file in one transaction
(src/graph/store.ts:155-165), then inserts
extractFile's AST nodes
(src/watcher.ts:124-130). Git-mined mistakes are keyed on
the file they describe, so editing that file under
engram watch, engram reindex, the opt-in
--auto-reindex hook or ENGRAM_AUTO_REINDEX=1
removes the warnings for exactly the file being changed. They return on
the next full init; learned ones do not.
Incremental init has the converse hazard, read and not
run. In incremental mode the AST miner emits nothing for an
unchanged file (src/miners/ast-miner.ts:671-677), while the
git miners emit mistakes and hot-file nodes for files regardless.
removeNodesForFile then runs for every such
sourceFile (src/core.ts:162-170), deleting the
unchanged file's code nodes, which the next incremental run also skips.
tests/incremental.test.ts runs in a directory that is not a
git repository, so the git miners contribute nothing there.
Re-ingestion loop. engram gen writes
mistake labels into CLAUDE.md and AGENTS.md,
and init mines both files with the session regexes. A
fix-commit label such as "Bug fixed here: fix: handle empty
token" contains a match for (?:fix|…):\s*(.{10,80}),
so the next init can add it back as a session mistake at 0.6 under
source_file = "CLAUDE.md". The miner does not skip the
<!-- engram:start --> block.
The CCS importer is not idempotent.
engram init --from-ccs gives every bullet of
.context/index.md a randomUUID() id, so a
bullet under an "Issues" heading becomes a mistake at 0.9, and an
incremental init followed by another import duplicates it
(src/ccs/importer.ts:23-35, :76-88).
Operational cost
- Write: synchronous, no model, one whole-file SQLite write per store close.
- Background: none for memory.
- Read: a store load and a whole-file write on every hook that
consults the graph; injection bounded to a few lines per event, placed
in hook
additionalContextrather than a stable prefix.
8. Agent Integration
The agent receives memory without asking: landmines on Edit and
Write, a brief at SessionStart and before compaction, a "KNOWN
ISSUES" section in Read packets, and a warning block in
query_graph results. It can list mistakes through MCP and
cannot write, correct or delete one through it.
It can write through a shell. engram learn is a CLI
command, and the HTTP POST /learn accepts any holder of the
local token. A learned mistake is promoted to the nagging tier by
construction, so the one channel with no provenance is the one that
warns loudest.
The guard's default is contradicted in the tree.
currentGuardMode returns permissive when
ENGRAM_MISTAKE_GUARD is unset
(mistake-guard.ts:40-53), while the same file's header says
"unset / 0 → no-op (default — zero production
overhead)" (:4-8), the dispatcher's comment says
opt-in (dispatch.ts:204-205), and server.json
tells MCP registries "Unset = off, zero overhead".
9. Reliability, Safety, and Trust
Concurrent hooks are last-writer-wins, and the project
knows. Every process loads the file, mutates or only reads, and
writes the whole file back. A learn that lands while a
PreToolUse hook holds an older snapshot is erased when that hook closes.
The repository's own two-process test states it: "SQLite's
last-writer-wins semantics mean one write may clobber the other when two
sql.js processes save concurrently", and asserts only that one of
the two files' nodes survived
(tests/watcher.test.ts:377-404). writeFileSync
is not a temp-and-rename, so a crash mid-write can truncate the
store.
Provenance is good for reverts and absent for the
rest. A revert mistake carries both shas, the original author
and the reverted files in metadata
(git-revert-miner.ts:206-212). A learned or session mistake
carries a label and a file name.
Prompt-injected memory. Anything that can write a
matching line into CLAUDE.md, AGENTS.md,
.cursorrules or .engram/sessions/ plants a
mistake or decision on the next init, and anything that can run
engram learn plants one that warns immediately. There is no
quarantine state.
Uncertainty is a number. The score sets a threshold on two warning paths; nothing can say "on record, not believed".
Capability marks:
negative_eval— awarded; evidence in section 10.tombstone— no delete verb exists for a memory, and a re-mine re-asserts whatever the sources still say.trust_state—confidenceis an enum,EXTRACTED | INFERRED | AMBIGUOUS, and every read of it changes rendering (query.ts:309,edit-write.ts:104). The filter is onconfidence_score, a float.bitemporal—valid_untilhas readers and no producer, and the revert miner'sfoundFalseAtequals itslastVerified. One clock, stored twice.scope_enforced— one database per project is a physical partition; thesource_filepredicate is relevance, not scope.audit_log— the meshaudit.jsonlhas one writer, recording identity key rotation;hook-log.jsonlis tool telemetry. Neither records a memory mutation.human_review— no state waits for anyone;engram mistakesdisplays.
10. Tests, Evals, and Benchmarks
Nothing was built or run for this report; everything below is from
reading the tests at the pin. CI runs npx vitest run on
Ubuntu and Windows under Node 20 and 22
(.github/workflows/ci.yml:10-58).
The negative case.
mistakes() — sourceFile filter seeds two learned mistakes
each under src/auth.ts and src/db.ts
(tests/intercept/mistakes-filter.test.ts:35-47).
"filters to only mistakes matching the given sourceFile"
asserts the src/auth.ts lookup is non-empty and that every
result's sourceFile is src/auth.ts
(:62-70). The fixture guarantees the excluded material is
present, and the positive side is asserted first, so an empty result
fails.
Well-formed cases on an unproduced state.
tests/providers/engram-mistakes.test.ts:140-172 seeds a
stale, a future-dated and an undated mistake in one file and asserts the
provider keeps two and drops the stale one;
tests/intercept/handlers/mistake-guard.test.ts:233-254 does
the same through the guard. Both seed validUntil by hand,
which is the only way it is ever set.
The floor, tested in halves. "does NOT surface a
low-confidence mistake" seeds only a 0.6 mistake and asserts zero
matches (mistake-guard.test.ts:185-198), and a separate
case seeds only a 0.9 one and asserts one. Each half alone could pass on
a guard that returned nothing, or everything.
A precision gate on the miner. "engram README
corpus extracts ZERO garbage mistakes (frozen fixture)" runs the
session regexes over a saved README and asserts no mistake, decision or
pattern (tests/mistake-memory.test.ts:209-228), with the
comment "don't silently accept". It pins false positives on one
document; recall is not measured.
Not covered. No test runs syncFile on a
file that has mistakes, runs an incremental init inside a git
repository, or checks that a learned mistake survives init.
The bench/ harnesses measure token counts of context
packets, and bench/README.md says the main runner "does
not run four live agents". No paper describes engram;
docs/FRONTIER.md cites others' arXiv papers.
11. For Your Own Build
Steal
- Mine reverts into structured regret. A revert is the one commit that says in so many words that an earlier belief was wrong; keep the overturned subject, the date and the superseding sha as separate fields.
- Split browsing from nagging with a floor. Everything inferred stays listable; only high-confidence memory interrupts a tool call.
- Freeze a prose fixture against the extractor. A regex miner's failure is false positives on documentation, and one saved README with an assertion of zero catches the loosening.
- Keep the model-facing surface read-only when writes have no provenance.
Avoid
- Keying memory on the same column as a derived index. A delete meant to refresh derived rows will take the memory with it.
- Whole-file writes from every reader. With several hook processes per turn, a store that saves on every close loses writes by design.
- A validity column the readers trust and nothing
sets. It reads as a guarantee in the schema, the comments and
the changelog (
CHANGELOG.md:507), and behaves asNULLeverywhere. - Writing memory into the files you mine. Export and ingest need a boundary the miner respects.
Fit
This suits a solo developer on one machine who wants a coding agent warned before it edits a file with reverts in its history, and who already wants the code graph. The memory is a side effect of the graph rather than a store with its own lifecycle. Anyone who needs taught lessons to persist, several agents writing at once, or a way to retract a wrong warning should read the revert miner and build the store around it themselves.
12. Open Questions
- How often do concurrent hook processes lose a write in a real Claude Code session with parallel tool calls? Nothing here measures it.
- Does the incremental-init deletion described in section 7 occur as read? It was traced, not reproduced.
- Was
valid_untilever written by an earlier version of the git miner, or planned and never landed? The changelog entry does not say. - Is the permissive guard default intended, given that
server.jsonand the file header say off?
Appendix: File Index
- Schema and storage:
src/graph/schema.ts,src/graph/store.ts,src/db/migrate.ts. - Write:
src/core.ts(init,learn),src/miners/session-miner.ts,src/miners/git-revert-miner.ts,src/miners/git-bugfix-miner.ts,src/ccs/importer.ts,src/server/http.ts. - Deletion by refresh:
src/watcher.ts,src/intercept/handlers/post-tool.ts,src/intercept/installer.ts. - Retrieval and injection:
src/core.ts(mistakes),src/graph/query.ts,src/providers/engram-mistakes.ts,src/intercept/handlers/edit-write.ts,src/intercept/handlers/mistake-guard.ts,src/intercept/handlers/session-start.ts,src/intercept/handlers/pre-compact.ts,src/intercept/dispatch.ts,src/intercept/safety.ts. - Export:
src/autogen.ts,src/intercept/memory-md.ts. - MCP:
src/serve.ts,server.json. - Declared, unwired:
src/mesh/types.ts,src/mesh/audit.ts. - Tests:
tests/intercept/mistakes-filter.test.ts,tests/providers/engram-mistakes.test.ts,tests/intercept/handlers/mistake-guard.test.ts,tests/mistake-memory.test.ts,tests/watcher.test.ts,tests/incremental.test.ts.
Recorded searches
Checked against the checkout at the pinned revision.
grep -rn 'validUntil\|valid_until\|invalidatedByCommit\|invalidated_by_commit' src | grep -v '^src/graph/schema.ts'—store.tsbinds and reads the columns;engram-mistakes.ts:32andmistake-guard.ts:137,156filter;mesh/types.ts:103declares a string; no assignment.grep -rn 'validUntil\|valid_until' --include='*.ts' . --exclude-dir=node_modules --exclude-dir=.git | grep -v '^./src/'— every assignment is intests/.grep -n -i 'invalid' src/miners/git-miner.ts— no match.grep -rn "mistake.shared\|MistakeSharedPayload\|decision.shared" src --include='*.ts'—src/mesh/types.tsonly.grep -rn 'logAudit(' src --include='*.ts'—cli.ts:753and the definition.grep -rn 'commandPattern' src --include='*.ts'— read inmistake-guard.ts; no writer.grep -rn -i -E 'forget|dismiss|unlearn|tombstone|suppress|blocklist|denylist' src --include='*.ts'— no memory verb.grep -rn -i -E 'approve|pending|review' src --include='*.ts'— migrations and hook preview only.grep -rn -i -E 'user_id|tenant|namespace|agent_id|scope' src/graph src/db src/core.ts— no match.grep -rnE "confidence ?(===|!==|==) ?\"|'(EXTRACTED|INFERRED|AMBIGUOUS)'|\"AMBIGUOUS\"" src --include='*.ts'— renders atquery.ts:309andedit-write.ts:104, the stats count and DDL defaults.grep -rn 'syncFile(' src --include='*.ts'—watcher.ts,cli.ts(reindex),post-tool.ts.grep -rn -i 'lock' src --include='*.ts' | grep -v -i 'block\|clock\|unlock'—init.lockincore.tsand a hook-log comment.grep -rln -i -E 'concurren|parallel|Promise\.all' tests --include='*.ts'—core.test.ts,watcher.test.ts,providers/lsp.test.ts; onlywatcher.test.ts:377runs two writers.grep -rn '"sessions"\|sessions/' src --include='*.ts'— the session miner reads.engram/sessions; nothing writes it.grep -rn 'engram:start\|engram:end' src/miners/session-miner.ts— no match.grep -rn 'incremental: true\|--incremental' src— the CLI flag only.grep -rliE 'arxiv|bibtex|@article|@misc|doi\.org' . --exclude-dir=.git --exclude-dir=node_modules—docs/FRONTIER.md, citing other work; noCITATION.cff.
History
2026-09-26 — df445d31…
— first reading, at the head of main, a commit dated 22
September 2026. One mark, negative_eval. Screened before
reading: 2 auto-run surfaces (.cursorrules, an
engram-generated structure summary, and server.json, an MCP
manifest naming the engramx npm package), 3 build-time
execution points (a postinstall that prints a banner, two
prepublishOnly builds), 5 dependency files inside the
cooldown — every file in a depth-1 clone dates to the tip — and 3
unpinned surfaces. .cursorrules, CLAUDE.md and
CONTEXT.md were read as data. Nothing was installed, built
or run.