1. Executive Summary
Engram Alpha is graph memory for AI coding assistants: ~31,000 lines of Rust across four crates (~45,000 counting their test modules) plus a 12,300-line evaluation crate, MIT, release v0.9.1, 142 commits since 3 July 2026, shipping as a JetBrains plugin and a VS Code extension on three marketplaces with a browser demo of the real pane. Not to be confused with Engram — a different project of the same name, already in this atlas.
Alpha is part of the product's name, not a maturity label:
the marketplace identifier is techtheist.engram-alpha, the
installed binary is engram-alpha, and nothing in the README
applies the word to the software's state. The stability claim the
repository does make is its own status line — "early development,
heavily dogfooded, benchmark-driven — retrieval changes cite a measured
run or they don't ship" — and the second half of that sentence is
the one this report can check.
A memory is a typed node joined by typed edges — but the vocabulary
is per-graph configuration, not a closed enum.
GraphConfig (config.rs) stores the ontology,
the policy numbers and the brief shape as one document inside the graph
itself, travelling with migrate and riding along in
exports. The shipped preset is nine types — Principle,
Decision, Caution, Problem,
Resolution, Insight, Intent,
Anchor, Tombstone — over seven verbs:
about, because, answers,
builds-on, replaces,
conflicts-with, needs. Three other presets
ship beside it (research, minimal,
general) and a graph may define its own type set, its own
verbs, and its own custom fields.
The rule that makes that safe is stated at the top of the module and
enforced throughout: roles, never names. Engine logic
keys on the role flags a type or verb carries — worklist,
supersession, contradiction — never on the
strings, so a renamed or wholly replaced ontology keeps every behaviour.
hub.rs:104 is the shape of it: the contradiction gate tests
edge.edge_type.as_str() == contradiction_verb, where the
verb comes from the graph's config, not from a literal. Two hard
invariants hold across any configuration — edges stay sentence-shaped,
and exactly one supersession verb plus one contradiction verb must
exist, "they are what make the graph active" — with
graph_config_validation_guards_hard_invariants and
shipped_presets_are_valid_and_complete asserting both.
There is deliberately no MCP write surface for the
ontology: reshaping it is a pane gesture, "like pin and
delete".
Its trust model states two principles the rest of this corpus
mostly violates, and
crates/engram-core/src/policy.rs names both:
Time doesn't validate.
stableknowledge holds its trust flat until a liveconflicts-withedge (the judged-evidence event) stampsdemoted_at; only then does it ramp down, and withdrawing the evidence withdraws the demotion.
Exposure doesn't validate. Retrieval stamps
last_seenfor observability only — it proves a note was findable, not that it is true. Trust anchors onconfirmed_at, which only deliberate acts refresh. Otherwise a broad recurring query would keep an attractive but wrong note alive forever — retrieval certifying its own outputs.
That second sentence is the exact failure this atlas records in Core Memory ("recall raises the class… still a use signal feeding a trust field"), in NOOA's myelination, and in the decay curves of Mnemopi and PowerMem. Engram names it and refuses it. The header adds that both principles were "learned from external feedback on v0.4.1" — a trust model changed by review rather than defended.
Drift is treated the same way: surfaced for review, never demoting, because it is "environment-dependent (wrong cwd, feature branches) and a sticky stamp from a bad scan would mass-bury the graph." A system that knows which of its own signals are too noisy to act on is rare.
The write path resolves instead of queueing.
WriteOutcome::Created returns the look-alike pairs the
write just queued, with the reason in the type: "returned so the
writer judges them in the same turn instead of leaving them for the next
session's brief (PLAN §7: detection is local, judgment is the
assistant's)." Deterministic local detection, model judgment, in
one turn — the shape resolve, don't just
detect asks for.
Deletion leaves a tombstone, and every write is checked
against it. A hard delete from the pane or the HTTP surface
mints a Tombstone-role note — "Removed:
<title>", with the victim's type, id, the
person's reason and, by default, the removed text in the body — and an
assistant can author one and link it replaces a still-live
victim. write_warnings then reports any tombstone-role node
within the warning similarity of a new note, across types, as
{reason: "tombstoned", note: <why it was removed>};
search names its tombstone hits, and
check_claim files them under retracted. The
write is never refused — "models nominate, people judge" —
which is where section 9 draws the line.
And the benchmark data labels its own shipped row and
publishes the columns it loses. The current headline is the
eval README's Against every baseline table, measured on 22
August 2026 on the shipped 0.8.10 stack over the enriched corpus,
receipts arms-0810-100-1500.json and
posttune-0810-100-1500.json. At 1,500 notes with every fact
questioned:
arm R@1 R@5 oblique focus noise tok/query FP
rag (pure vectors) 0.66 0.79 0.45 0.10 0.91 2,721 1.00
engram (shipped) 0.69 0.79 0.39 0.52 0.55 297 0.01
Weighted for how often each phrasing occurs, engram reads 0.93 to
rag's 0.91; pure vectors keep the oblique column at both sizes (0.45 to
0.39 here, 0.87 to 0.85 at 100 notes) and tie or edge R@5, and the
README prints those cells beside the nine-fold token gap rather than
instead of it. The habit is older than the table:
bench-100.json, the first committed run (3 August 2026, the
0.7.2 instrument on the pre-enrichment corpus), labels its shipped row
in the data —
"kw 0.15 · reranker VOTES <- ships today" — and
records rag ahead of it on weighted recall, 0.992 to 0.989.
contradictions-500.json separates the two ways
contradiction detection fails — "missed_by_retrieval": 0,
"missed_by_judgment": 3 out of 100 — which is a diagnostic
decomposition almost nobody reports, and floor-500.json
sweeps twenty-one score floors with unanswerable
controls at each point, so the precision gained is
priced against the answerable questions declined.
And it runs on a corpus it did not write.
eval/LONGMEMEVAL.md reports the full 500 questions of LongMemEval-S —
real multi-session chat, ~115k tokens of haystack per question, evidence
sessions labelled, 30 questions deliberately unanswerable — ingested
as-is, one verbatim note per chat turn, with no extraction and
no model anywhere in the ingestion or grading path. Five arms over
identical haystacks; the receipt is
eval/results/longmemeval-s-full.json:
arm R@1 R@5 MRR tok/query
engram 0.909 0.953 0.929 207.5
rag 0.909 0.974 0.937 2,654.4
grep 0.706 0.864 0.775 4,761.0
curated-file 0.919 0.919 0.919 2,999.0
whole-file 1.000 1.000 1.000 122,515.5
No row is an outright win, and that is the point of publishing all five. Engram matches pure vectors at R@1, gives up 0.02 at R@5, loses R@1 to a blind 3,000-token file dump — and delivers an eighth of rag's tokens doing it, with both of its recall figures computed over the set calibrated delivery left rather than over a fixed ten. The 30 unanswerable questions draw 2 empty deliveries, 28 warned, 0 unwarned, with the weak line auto-fitted per store on 27 of 30 graphs.
The limits are real and the project states most of them itself: there is no scope key of any kind, the external run grades retrieval at session level (any turn from the right session counts, which flatters dumps), and the harness never asks what the same warning line costs on the 470 answerable questions — the offline suite, which does ask, answers 47% at both 100 and 1,500 notes.
2. Mental Model
A node is a claim of a kind the graph's ontology declares, and the
kind carries the epistemics: under the shipped preset a
Decision is not a Caution is not an
Insight, and the roles each kind carries are what the
engine reads. Durability is separate — stable,
episodic, volatile — and decides whether time
is allowed to erode it at all.
Trust is computed at read time from durable anchors,
so nothing depends on a background pass having run. The anchor picks the
starting value: created_at alone starts at
TRUST_UNSEEN_START; confirmed_at (a deliberate
update, or "Confirm still true") restarts the clock at
TRUST_CONFIRMED_START; approved_at starts
higher and ramps to a floor. trust_override — the pane's
pin — short-circuits all of it: pinned nodes never decay, never
auto-archive, and evidence events skip them, because "a human said
'forever', so only a human unsays it" — while contradictions still
surface for review.
A node whose computed trust falls below STALE_TRUST is
stale: still searchable, buried by the multiplier,
flagged to the assistant, and put in the pane's review queue for a human
decision. Nothing disappears silently.
The other half of the state machine is the suspects
table: look-alike pairs with a similarity, an optional local-NLI hint
(contradiction | entailment | neutral) and a score whose
column comment reads "models don't validate". A suspect is
suspected, then confirmed or
dismissed, and the verdict vocabulary is
conflict, replaces, dismiss.
There are two ways into that queue, and the second one reads titles because the measurement said to. Above the similarity floor a pair is a look-alike and queues on similarity alone. Below it, down to a separate NLI floor, a logic layer reads "the two bare TITLES", and a confident contradiction between titles that plausibly name the same subject queues too. The reason is stated as a number rather than an intuition: similarity alone "cannot tell a contradiction from an agreeing restatement (MemStrata AUROC 0.59; the KnowledgeDrift gate probe reproduced it)", and on that same probe the titles separated the two "far more sharply than the claim text (negation 0.99 vs 0.61)" — so the below-floor path deliberately drops the first body sentence the claim text appends, because it "only dilutes what the titles say." A reproduced published result, and a feature choice justified against it.
The two dials are kept from contaminating each other. Only pairs the similarity path could have raised are allowed to inform the similarity dial, because letting the logic layer's verdicts into that fit "would drag the floor toward the NLI band it never reads." A calibration that learns from decisions its own mechanism could not have produced is a slow way to drift, and this one is fenced against it.
A tombstone is identified by role rather than by name. The flag saying a node is a removal record "rides on every surface", on the stated ground that "a custom ontology can call its tombstone anything, and a reader skimming for X must not take "Removed: X" for a memory of X." Making the meaning structural rather than lexical is what stops a renamed type from turning a deletion marker back into an assertion.
The constants in that machine are per-graph, and two of them are
fitted rather than chosen. Engine::auto_tune runs at every
session boundary: dial one refits the conflict-suspect floor from the
graph's own judged suspects by maximising balanced accuracy over
midpoint candidates, and dial two refits the "likely not in memory"
line. Each move is damped to half the distance to the fit
(AUTO_TUNE_DAMPING = 0.5), clamped into a stated range,
suppressed below AUTO_TUNE_MIN_DELTA, gated behind minimum
volumes — 200 notes and 20 judgments with at least 3 on each side for
dial one — and journalled as an auto_tuned row.
policy.auto_tune = false opts a graph out entirely. The
field lesson behind it is written down in the project's own chronicle:
"register-dependent score scales — absolute thresholds don't
transfer between graphs, relative mechanisms do."
Diagram source
%% caption: trust moves on evidence and on people — a live conflicts-with edge demotes and its withdrawal lifts the demotion, a human pin turns decay off, and retrieval stamps last_seen without moving anything
stateDiagram-v2
[*] --> Unseen: created_at only, trust starts low
Unseen --> Confirmed: a deliberate update or Confirm still true
Unseen --> Approved: explicit approval, higher start, ramps to a floor
Confirmed --> Demoted: a live conflicts-with edge stamps demoted_at
Approved --> Demoted: a live conflicts-with edge stamps demoted_at
Demoted --> Confirmed: the evidence is withdrawn, the demotion lifts
Approved --> Pinned: trust_override, decay off
Pinned --> Approved: only a human unpins
Confirmed --> Stale: trust falls below the floor, buried not deleted
Stale --> Confirmed: reviewed and confirmed in the pane
Confirmed --> Archived: valid_until set
Unseen --> Unseen: retrieval stamps last_seen and moves nothingThe self-loop at the bottom is the design. Retrieval touches the graph and changes no trust, so a note cannot certify itself by being popular.
3. Architecture
Runtime. One binary, and at runtime one heavy
process per machine with any number of deliberately light ones around
it. The machine core holds every open store and its
locks, the three local models, the pane's web server and every MCP
session; it binds 127.0.0.1:8787, runs as a hidden
core subcommand, is spawned detached by whichever command
first needs it, and ends only on engram-alpha stop.
serve is a launcher that registers the repository and
exits. engram-alpha mcp is always a bridge — it
never opens a store on any backend, proxying the client's stdio session
to the core over streamable HTTP, and failing with an error in
.engram/mcp.log rather than "silently opening the store
in-process". status, doctor,
stop and the hooks are one-shot REST clients. Discovery is
two JSON advertisements plus a GET /health check, so a
stale file is harmless, and ~/.engram/registry.json lists
which repositories have graphs.
That consolidation is the change a reader of the previous release has to notice, because it moves a guarantee. When each project directory ran its own process over its own file, reading the wrong project's graph was not expressible. One core holding every store makes it a routing decision — see the binding ladder in section 8.
engram-core carries the engine, engram-mcp
the tool surface, engram-http the REST and SSE API,
engram-cli the process model and the per-client setup
writers, with a Vue frontend for the pane and editor integrations for
JetBrains and VS Code published to the JetBrains Marketplace, the VS
Marketplace and Open VSX. There is a standalone browser demo running the
real pane over an invented project, and the feature screenshots in the
documentation are generated from that demo by a headless-browser
manifest (frontend/scripts/shots/), so every image is one a
reader can reproduce.
Persistence. TepinDB (store_tepin.rs) —
a single self-describing graph.tepin file holding
documents, keyword index and vectors, from the tepindb
crate the same author publishes, inspectable with
npx tepindb — is where every new graph has been born since
v0.6.2; SQLite (store_sqlite.rs) sits behind the same
store.rs trait and docs/storage.md calls it
"only a migration source", with a graph.db
migrating itself at the daemon's next open. Four tables:
nodes, edges, suspects,
audit, plus a meta table holding the embedding
model identity and vector width so a store cannot be silently read with
the wrong embedder, and — since v0.9.0 — the store's own
encryption_state.
At rest, two switches.
~/.engram/settings.json carries encrypt_graph
(off by default, because a sealed graph costs the
npx tepindb inspection) and encrypt_history
(on by default, formalising what the harvester had done since v0.8.4).
Sealing is field-level in seal.rs — zstd then
XChaCha20-Poly1305 over titles, bodies, tags, code refs, custom-field
values, edge notes and the audit journal's before/after images, under a
machine key in the OS keystore — and the state lives in the store's own
meta as plaintext | sealing | sealed | unsealing, so a
migration killed halfway resumes at the next open and the settings file
only ever carries intent. Structure, timestamps, ids and embedding
vectors stay open, and the module says why for the vectors:
"documented inversion risk — vectors recover gist, not text."
The keyword index is a blind index: one synthetic _kw token
stream per node, identity tokens on a plaintext store and keyed
HMAC-SHA256 digests on a sealed one (seal.rs:216-224), so
term frequencies and document lengths are preserved and a golden test
asserts bit-identical BM25 rankings across the two states.
Modules worth naming. config.rs holds
GraphConfig — the ontology, the custom-field definitions,
the policy numbers and the brief composition as one stored document,
with the four shipped presets and the validator for the hard invariants;
seal.rs the at-rest codec and the blind keyword tokens;
nli.rs runs a local natural-language-inference model for
contradiction hints; policy.rs holds the trust constants,
the auto-tune bounds and their rationale; onnx.rs
centralises inference-session policy (arena, thread and batch-width
knobs, read from ENGRAM_ONNX_*); redact.rs
exists at all; cortex.rs, digest.rs,
rag.rs, hub.rs carry retrieval and briefing;
migrate.rs handles schema movement.
Background work. Deliberately little, and none of it
load-bearing for a read. Trust is a read-time computation. The suspects
queue is filled synchronously by the write that created the look-alike.
What does run is Engine::validate_graph, one
session-boundary pass — archive what decayed, retire what a supersession
superseded, refit the two auto-tune dials, rescan for conflicts, count
drifted code_refs — journalled as a
graph_validated row with its own summary note, and backed
by a six-hourly sweep. Drift scans surface for review without
demoting.
Since v0.8.4 a second detached daemon runs alongside that sweep: the history harvester, a 60-second loop (fs-notify-accelerated in v0.8.7) that tails coding-assistant transcripts into a separate episodic store — so "deliberately little background" understates a continuous transcript-tailing loop, even though it feeds a sibling store and not the curated read.
Deployment and ergonomics
One binary, one store file per project, one core process per machine,
plus optional local models. install.sh and
install.ps1 fetch the binary and stop; wiring a repository
is the explicit engram-alpha setup --cli …, and
serve names what it found and did not wire — "found
codex, gemini — installed but not connected to Engram in this repo"
— rather than touching a config it did not write. The models are an
embedder (bge-small-en-v1.5), a reranker
(jina-reranker-v1-turbo-en) and an NLI model
(deberta-v3-small-tasksource-nli, a 172 MB quantised ONNX
export the project made itself) — all small enough to run on a developer
machine, which is the point. No service to stand up and no API key
required to store anything.
The NLI slot has been swapped twice on measurement, and the module
says what each swap bought: MNLI-only models "presuppose
co-reference" and call unrelated same-register notes confident
contradictions — "Engram uses Rust" against "TepinDB is on
crates.io" scored 0.99 — where the multi-task model judges them
neutral, halving false alarms at the shipped gate. Predecessors stay
selectable, and the swap contract is a directory holding
model.onnx, tokenizer.json and a
config.json whose id2label covers the three
classes.
The pane is the operating surface and it is unusually complete: a live graph, a review queue, conflict judgement, retirement, pinning, an ontology editor, a feed timeline and a theme menu. The browser demo means a reader can exercise the correction flow without installing anything, which is the best documentation decision in the repository.
The daemon's memory footprint is measured with a committed
before/after pair. eval/results/mem-daemon-before.json and
-after.json walk the same six stages — startup with three
models, one search, ten searches, an NLI claim check, a conflict scan,
thirty seconds idle — under stock onnxruntime defaults and under the
shipped ones: 669,696 → 896,000 KB against 667,648 → 696,320 KB. The
difference is an inference batch width capped at 2 instead of
fastembed's 256; scripts/mem-probe.sh is the harness, and
three other suspects (the ORT arena, per-session thread pools, malloc
free-list retention) are recorded as measured and refuted.
4. Essential Implementation Paths
- Schema:
crates/engram-core/src/schema.rs—nodes(:6),edges(:29),suspects(:52), the append-onlyauditjournal (:67),meta(:88). - Types and vocabularies:
crates/engram-core/src/types.rs— open string-backed name types, durability, statuses,WriteOutcome::Createdreturningsuspects,MergeOutcome. - Ontology and per-graph config:
crates/engram-core/src/config.rs—GraphConfig,OntologyConfig,TypeRoles/VerbRoles(thetombstonerole at:298-307),FieldDef/FieldKind(:51-96), theengram,research,minimalandgeneralpresets,describe_ontology. - Trust:
crates/engram-core/src/policy.rs— the two principles, the anchors,STALE_TRUST,trust_override, theAUTO_TUNE_*bounds. - Auto-tune:
engine.rs—auto_tune(:1235),fit_conflict_floor(:1268),fit_weak_line(:1341), called fromvalidate_graph(:1173). - Contradiction:
crates/engram-core/src/nli.rs;hub.rs:104gates on a live edge carrying the graph's contradiction role withvalid_untilunset. - Consolidation:
engine.rs—merge_nodes(:3090). - Deletion and tombstones:
engine.rs—delete_node(:1915),delete_node_with_tombstone(:1947),write_warnings(:3308),is_tombstone(:4854),tombstone_note(:4863);check_claim'sretractedbucket (:3763). - Stores:
store.rs,store_sqlite.rs,store_tepin.rs,migrate.rs;seal.rsfor the at-rest codec. - Retrieval and delivery:
rag.rs,cortex.rs,digest.rs;Engine::search(engine.rs:2581) applies the delivery floor, the knee cut and the session-diverse selection, then attaches one-hop neighbours (:2662);search_confidence(:2797) returnsnone | weak | strong;time_filter_clocked(:2738) andcheck_clock(:1754) are the temporal grammar's boundary. - Redaction:
redact.rs—scruband, for custom fields,scrub_fields. - Surfaces:
crates/engram-mcp/,crates/engram-http/,frontend/,engram-vscode/, the JetBrains plugin. - Evaluation:
eval/src/andeval/results/— 108 committed result files, 66 of them JSON — pluseval/src/longmem.rs(the LongMemEval adapter),eval/src/chains.rs(the supersession-chain bench) andeval/src/sessions.rs(the multi-session coverage bench).
5. Memory Data Model
nodes carries id, type,
title, body, durability,
source, session_id, created_at,
valid_from, valid_until,
status, code_refs, tags,
last_seen, confirmed_at,
approved_at, demoted_at,
trust_override, and fields — a JSON object of
custom-field values. edges carries the same temporal pair
plus confidence, strength, note
and a status.
Bi-temporality is real and used, on two axes.
valid_from / valid_until are validity time and
created_at is record time; hub.rs:104 treats
an edge as live only when valid_until.is_none(), and
types.rs:404 documents a neighbour as
"superseded/archived (valid_until set)". Archival
is closing an interval rather than deleting a row. The engine's axis
says when a claim held as canon; the second axis is the graph
owner's and says when the thing happened: a date-kind custom field, or a
from..to pair of them, becomes the event clock a
search window can be aimed at (section 6), which is what a historic
import needs when everything is captured today and happened over
years.
Custom fields are declared in the graph and validated at the
write boundary. FieldDef
(config.rs:51-96) names a field, a kind —
text, number, bool,
date, enum, url — whether it is
required, which types it applies to, whether it is indexed
(joins the note's embedding and the keyword channel, with the indexed
set fingerprinted so a change re-embeds exactly once) and whether it is
shown on brief lines. check_fields_ext
(engine.rs:1654) is the one gate every surface passes:
unknown names fail, kinds must match, required fields must be present,
and reserved built-in names cannot be shadowed; a refusal is a
teaching error that names the whole roster and the exact call
shape, so an agent that has never seen this graph corrects itself in one
step. update_node merges the map and a null
deletes a key; renaming a field moves every stored value with the
definition, as renaming a type does. The one exception is
engine-authored: the tombstone a delete mints skips required-field
enforcement, because "field config must never veto a user's
delete."
The trust anchors are three separate durable facts,
not one score: confirmed_at (a deliberate act),
approved_at (explicit approval), and
demoted_at (contradicting evidence landed). Keeping them
apart is what lets the policy say withdrawing the evidence withdraws
the demotion — with one blended number that operation is
impossible.
code_refs ties memory to the artifact it
describes, which is the property Magic Context is credited for here.
There is no scope key. No user, project or workspace column; isolation is one graph per project directory. Coherent for an editor plugin, and it means the schema cannot host two projects without a migration.
What a user may reshape, and the guard on each
The pane's ontology editor writes GraphConfig — types,
verbs, the trust and threshold constants, and the brief composition —
and three of its rules are worth naming because each closes a way the
reshape could strand data.
- A rename is the migration gesture, not a relabel.
rename_typeandrename_verb(engine.rs:2473) move the stored rows along with the name, and the editor's card shows how many nodes will follow. - A save cannot drop a vocabulary that still holds
data. Removing a type that has nodes fails with "type …
still has N node(s) — rename it (bulk retype) or retype them first"
(
engine.rs:2345), and the same for verbs on edges. There is no path where an edit orphans knowledge. - The hard invariants survive every configuration.
Exactly one supersession verb and one contradiction verb must exist, or
validation refuses the document — the graph cannot be configured into
inertness. A type carrying the
tombstonerole may not also carryworklistoranchor(config.rs:1466-1471), because "a tombstone is a closed record of a removal — it can't be open."
Two smaller per-graph features sit in the same document.
Version tracking (off by default) stamps every new node
of a versioned type with the graph's current working
version, set by the set_version MCP tool, so a note records
which release it was captured under; Principle and
Anchor carry the versioned: false role in the
shipped preset, because "a value or a code subject transcends any
single release", and the stamp is provenance only — nothing in
trust or ranking reads it. And a reserved handoff tag
(config.rs:568) gives an open worklist note guaranteed top
placement in the next session brief, which is reactive
session-to-session memory without a new node type.
6. Retrieval Mechanics
Vectors from bge-small-en-v1.5, a keyword weight of
0.15, and a reranker (jina-reranker-v1-turbo-en) that
votes rather than decides — a distinction the ablation
measures rather than asserts. Across every keyword weight in
bench-100.json — the 3 August 2026 ablation on the first
instrument's 100-note corpus — reranker VOTES beats
reranker DECIDES on weighted recall (0.991 vs 0.980 at the
shipped 0.15) and on oblique questions (0.91 vs 0.80). A reranker
allowed to overrule the retriever loses obliquely-phrased hits; one that
contributes a vote does not.
The score floor is a published tradeoff curve rather than a constant.
floor-500.json walks twenty-one floors and reports, at
each, recall@5, oblique recall, mean results returned, mean tokens, and
two decline rates — for answerable questions and for
unanswerable controls. At floor 0 the system returns
9.95 results and 512 tokens and declines nothing including the controls;
by floor ~0.62 it returns 1.03 results and 55 tokens, declines 62% of
controls and 38% of answerable questions. Every operating point is
priced.
Trust multiplies the score, so a stale node is buried rather than filtered — it stays findable, which is what makes the review queue meaningful.
Delivery is a second stage with two cuts, both
reranker-gated. search
(engine.rs:2581) fetches wide, reranks, drops everything
under policy.delivery_floor, truncates to the limit, then
applies a knee cut: sort the delivered score curve, find the
largest relative drop over policy.knee_cliff, and discard
the tail below it. The reasoning for gating both on the reranker is
stated at the call site — the fused hybrid score is a different scale,
so a floor read against it would be read on the wrong ruler. The
consequence for every number in this report: engram's recall is measured
over a set the system has already trimmed, while the RAG arm it is
compared against keeps its full ten.
A third cut is about provenance, not score. When
more candidates survive the floor than fit the list, each additional hit
from a session already holding a slot is demoted
policy.session_diversity_demote rank positions before the
final cut — two by default (policy.rs:157), a rank rather
than a score on purpose, because "absolute score thresholds don't
transfer between graphs while relative mechanisms do." It changes
selection and order only; scores, the floor, the knee and the verdict
still read the cross-encoder's scale, and a graph whose notes share one
session is byte-identical to the cut without it. Every hit carries its
session_id, so the provenance the demotion acts on is
visible where it acts. The number was chosen from a committed sweep and
its cost is measured in section 10.
The abstention line is fitted from probes the graph writes
about itself. fit_weak_line
(engine.rs:1341) synthesises questions that have no answer
— half question-shaped templates over vocabulary borrowed from the
graph's own node titles, half ICT transplants: real sentences
from the store with their subjects coined out — then measures how high
the reranker's top score climbs against each, and takes the
larger of the two families' q90 as the fit — the line
must clear whichever register this graph's noise speaks loudest in. The
fit is then damped halfway and clamped, with the lower clamp
relative to the delivery floor rather than absolute, so a line
that could never fire is impossible and one noisy probe register cannot
teleport it. Borrowed vocabulary is the load-bearing part and the
comment says why: generic probes under-read how high this
graph's scores can go, and the first live fit found in-register
questions reaching 0.32 against a 0.25 line fitted from generic wording.
What clears the line is answered; what falls below is delivered warned,
never cut. No other report in this corpus describes a system
calibrating its own "I don't know" threshold from synthesised
unanswerable queries — MetaClaw is
the nearest, and it replays real past turns against candidate
policies rather than manufacturing negatives.
Search is time-scoped, and the window has two
clocks. A single temporal grammar (timespec.rs)
adds after, before,
during_version and order to the MCP
search, applied before the reranker and every cut
so "a scoped verdict describes the scoped set," with ordering applied
last so a time-ordered read has the same membership as its relevance
twin. during_version resolves a window from the audit
journal's version_switched rows with no new storage, and
order: "recent" folds a history restatement under its
newest form. By default the window reads created_at —
when the knowledge was captured, and the comment at
store.rs:562-567 explains why not
confirmed_at: "a node reconfirmed last week is still
July's decision." A date_field selector re-aims the
same window at the event clock the graph's owner
declared: one date-kind custom field matches when its value falls inside
the window, and a from..to pair matches when the node's own
validity span overlaps it, an absent from reading since
forever and an absent to still valid
(timespec.rs:84-98). Nodes without the field drop out,
because they cannot answer an event-time question.
check_clock (engine.rs:1754-1790) refuses an
undeclared or non-date field, and a clock without a window, as loud
errors — "an unaimed clock filters nothing and would silently return
the default result while looking like an event-time answer." The
history layer has no custom fields, so date_field on
scope: "history" is refused too.
Beneath the curated graph sits a second, sealed episodic
history store (history.tepin, one per project):
the harvester tails chat transcripts from seven assistants — Claude
Code, Codex, Gemini CLI, opencode, Kilo, Antigravity, IBM Bob — as
Session/Message nodes cross-linked to curated memory by a
born-in provenance edge. Its isolation is physical, not a
filter: curated search, brief, drift and decay never see it
(history.rs:7-11). Harvesting is opt-in
(HistoryConfig.enabled defaults to false) and the store is
sealed at rest by the encrypt_history
switch, on by default — the sharper half of a deliberate two-store split
in which the curated graph is redacted and sealed only if its owner
turns the second switch. The blind keyword index gives the history layer
a BM25 channel of its own, fused with vector candidates under the
curated layer's weights. The MCP tools expand_history and
list_sessions read it on request.
7. Write Mechanics
A write is synchronous and returns more than an id.
WriteOutcome::Created carries the node, any
warnings, and the suspects — look-alike pairs
the write just created — "so the writer judges them in the same turn
instead of leaving them for the next session's brief."
The division of labour is stated: detection is local,
judgment is the assistant's. Similarity and the NLI hint are
computed by small local models; the verdict — conflict,
replaces, dismiss — is the assistant's, and
the column comment beside the NLI score says why: "models don't
validate." A probability is a hint, not a ruling.
Correction is a family of moves rather than a delete: a
replaces edge, a conflicts-with edge that
stamps demoted_at, closing valid_until to
archive, approving, pinning, or dismissing a suspect. Every one of them
appends to the audit journal.
Deletion leaves a trace, and the trace carries the
text. delete_node_with_tombstone
(engine.rs:1947-2029) is the pane's default delete and the
HTTP surface's
DELETE /nodes/{id}?tombstone=true&reason=…: it mints a
note of the first type carrying the tombstone role — title
"Removed: <victim title>", body "Deleted
<type> "<title>" (id …)" with
the reason under Why and, with keep_text
(the default), the victim's body under Removed text,
its tags and its code refs — and then cascades the victim. The comment
says why the text rides along: a mint that embedded "only the
victim's title plus the reason" meant "a paraphrase of the
buried CONTENT landed nowhere near it", so the marker "guarded
the headline, not the knowledge." keep_text=false is
the purge shape, identity and reason only. The tombstone is
Source::User, stable durability so it never decays, takes
over the victim's about edges to anchor nodes so the
removal stays attached to the code subject it happened under, carries no
other edge — "edges never dangle" — and skips required-field
enforcement. When the ontology declares no tombstone type the call
degrades to a plain delete, "the config decides, never the
caller". Hard delete has no MCP path; the assistant's version is to
add_note a Tombstone and link it
replaces the victim, which archives the victim behind the
marker through the ordinary supersession machinery
(an_authored_tombstone_that_replaces_its_victim_buries_it_traceably).
merge_nodes is the consolidation move,
and its edge cases are where the design shows. Several notes stating one
truth collapse into one survivor: tags and code_refs union,
live edges rehome onto the survivor, victims are archived behind a
supersession edge rather than deleted, and MergeOutcome
reports per victim how many edges moved and how many were deliberately
left — already present on the survivor, internal to the merged set, or
an incoming supersession, which is part of the victim's own
story and does not travel. An archived survivor is refused outright. And
the assistant cannot merge away a pinned node: with
source == Source::Claude a pinned victim raises
Error::Pinned carrying the instruction to "tell the
user and let them merge this pair in the pane" — the same
human-only boundary the pin draws everywhere else. Four committed tests
cover the union, the pin refusal, the outgoing-but-not-incoming
supersession rule, and a live conflict rehoming onto the survivor.
The audit journal also carries events that are not node or edge
mutations: audit_activity writes rows against the entity
type session, which is where graph_validated
and auto_tuned land — so a threshold the system moved on
itself is on the same tape as a human's approval.
The audit journal is the richest in this corpus. One
row per node or edge mutation, insert-only, carrying
before_json and after_json in full, the
action from an eleven-value vocabulary
(created | updated | approved | unapproved | pinned | unpinned | demoted | undemoted | archived | deleted | imported),
the origin surface
(pane | mcp | daemon | cli | library), and the writing
process's session_id, cwd, pid
and version. title is snapshotted so it
"survives deletion".
Knowing which surface made a change — a human in the pane, an assistant over MCP, a daemon — is the distinction most audit logs here collapse, and it is exactly what you need when a memory is wrong and you are deciding whether to distrust the model or the person.
8. Agent Integration
An MCP server for the assistant, an HTTP surface, a JetBrains plugin, a VS Code extension, and a standalone pane. The screenshots show the intended loop: the graph fills in live while Claude Code works in the terminal below.
Which project a session serves is a five-rung ladder, and the
last rung is the one to read carefully. An explicit
--db pins it; otherwise the bridge asks the client for
roots/list and binds the first file:// root,
re-resolving on roots/list_changed so one db-less config
entry follows a client across workspaces; otherwise the bridge's working
directory; otherwise a machine-level default agent
project setting; and otherwise the home graph
— "the session binds the core's /mcp endpoint rather
than dying." Every rung is logged, so mcp.log always
names the one that bound.
Rung five is a deliberate trade and the report's sharpest reservation about the process model. A session that cannot establish its workspace does not fail; it reads and writes somebody else's graph — specifically the home one.
The mitigation is brief called with a
project: a name, an id or any absolute path inside a
registered root rebinds the running session and returns that project's
brief in one call (crates/engram-mcp/src/lib.rs:871-897),
refusing an unregistered selector with the known roster and
leaving the binding untouched when it refuses, so a hallucinated path
can never birth a graph.
Sessions stranded on rungs four or five get a one-line hint above
their brief pointing at it. Folding the rebind into brief
is the field lesson from the project's issue #4, where an injected
"call brief first" overruled a workspace rule that
said set_project first; one tool means there is no wrong
first call left to make. The clients that need it are named in the docs
— Windsurf and Devin CLI advertise roots and never answer them — and
setup writes each client what it can consume: a
SessionStart brief hook for Claude Code, Codex, Devin CLI
and Bob (Devin and Codex only accept injected context inside a
hookSpecificOutput JSON envelope, so the two share one
wrapper), an always_on rule for Windsurf, and an
AGENTS.md block for the rest.
The pane's Processes census shows each live session's current binding. Bridges hold a 15-second-heartbeat lease that expires after 45 seconds, so a crashed client leaves the list within three missed beats, and a failed registration never blocks bridging: the census is observability, not control.
The assistant's authority is broad on judgment and bounded on trust
in one place by construction and in another by instruction. It writes
nodes and edges, and it judges the suspects the write hands back.
Pinning is human-only in the code: no MCP tool writes
trust_override, retrieval moves nothing, and a pinned node
ignores the assistant's evidence entirely until a human unpins it.
Approval is not: approve_node is an MCP tool
(crates/engram-mcp/src/lib.rs:1356) that restarts trust at
100%, Engine::approve takes no source and gates on nothing,
and the whole restraint is the tool description — "ONLY on explicit
user demand or after word-by-word verification against current
reality." The audit row records origin: mcp, so an
assistant's approval is distinguishable after the fact; it is not
preventable before it.
The pane is a genuine review surface: stale nodes queue for a decision, conflicts are judged there, decisions are retired there, and the browser demo lets a reader do all three without installing anything.
9. Reliability, Safety, and Trust
audit_log — earned, and it sets the
bar. Insert-only, one row per mutation, full before and after
JSON, an eleven-value action vocabulary, the originating surface, and
process context. Deletion-surviving titles.
bitemporal — earned.
valid_from / valid_until on nodes and edges
against created_at, used on the read path to decide what is
live; and a second, owner-declared validity axis — a date-kind custom
field or a from..to pair — that a search window can be
aimed at instead of the capture clock, with the boundary refusing an
unaimed or undeclared clock rather than dropping it.
trust_state — earned.
suspects.status is a persisted
suspected | confirmed | dismissed; nodes carry a
status and three distinct durable epistemic stamps,
confirmed_at, approved_at and
demoted_at, which the policy reads separately and which the
audit journal names as actions.
human_review — withheld, and the
paragraph that used to award it contains the reason. Three gestures here
really are human-only by construction, and they are worth keeping:
pinning, because no MCP tool writes trust_override;
reshaping the ontology — types, verbs, custom fields — which has no MCP
write surface at all; and merge_nodes, which refuses a
pinned victim when the caller is the assistant. But none of those is a
memory waiting to be admitted. A pin makes an existing node immune to
contradicting evidence; it does not hold one back. What would be the
admission gate is approval, and approval is the gesture that is not
reserved: approve_node is an MCP tool
(crates/engram-mcp/src/lib.rs:1356),
Engine::approve takes no source and gates on nothing, and
the whole restraint is the tool description — "ONLY on explicit user
demand or after word-by-word verification against current reality."
A sentence in a description is a request to the model. The suspects
queue points the same way: the write hands the look-alike pairs back so
the assistant judges them in the same turn. The audit row
records origin: mcp, so an assistant's approval is
distinguishable after the fact; it is not preventable before it.
negative_eval — earned. The floor sweep
carries unanswerable controls at every operating point
and reports controls_declined, so the evaluation asserts
that material which should not be returned is not returned — and prices
the answerable questions lost at each threshold. The LongMemEval run
extends the same discipline onto a corpus the project did not write: 30
labelled never-answerable questions, scored on whether an answer went
out unwarned.
tombstone — withheld, on one word of the
definition, and the near-miss is the narrowest in this report.
The Tombstone type is a durable record of a removal that
carries the removed text, tags and code refs, stable so it never decays,
and it is consulted on every write: write_warnings
(engine.rs:3308-3340) walks the new note's nearest
neighbours above policy.warn_similarity (0.70) and reports
any tombstone-role node among them, across types, as a
tombstoned warning carrying the tombstone's own account of
why — the comment names the gap it closes, "a Decision re-deriving a
tombstoned Decision sailed through". search counts
tombstone-role hits and appends a tombstone_note so a
compact scan cannot read "Removed: X" as a memory of X;
check_claim sorts tombstone hits out by role before the NLI
runs and files them under retracted (:3763);
the write-time canon check and the suspect recorder skip tombstones on
both sides so a replaces verdict can never archive the
marker (:2997, :4216). Seven committed tests
cover the 0.9.2 behaviour, and the one that matters here asserts the
boundary in its own words:
writes_near_a_tombstone_warn_tombstoned_across_types panics
on anything but Created — "a tombstone never blocks a
write — it warns." The definition this atlas applies asks for a
record keyed on the value "so later extraction cannot re-assert
it"; here the re-assertion is created as live canon, ranked,
briefed and delivered like any other note, with a warning on the write
result that the writer may act on or not. The changelog states the
choice as a principle — "The write is never blocked: models
nominate, people judge" — and it is coherent with the rest of the
trust model, where the assistant judges and a person decides. It is not
the mark. Two shapes the project already has would earn it without
touching that principle: the same-type near-duplicate short-circuit,
extended to a tombstone neighbour, would return Matched
with the tombstone's reason instead of creating — the collided form, a
nomination that leaves nothing live to judge later; or a note created
with a tombstoned warning could be held out of the brief
and ranked as stale until a person confirms it — the suppressed form.
What is not a near-miss any more: the brief never shows
tombstones (hidden_brief()), and the key is an embedding
neighbourhood, so a paraphrase below 0.70 lands with no warning — a
threshold the purge shape (keep_text=false) makes easier to
fall under, because it embeds the headline alone. A dismissed suspect
and a replaces edge remain keyed on ids.
scope_enforced — not found. No scope
key on either table.
Other observations:
redact.rsdrives the write path.redact::scrubruns on title and body in the node write paths (engine.rs:290-291,:2890-2891) andscrub_fieldson every string-valued custom field: named patterns (PEM/AWS/JWT/GitHub/Slack/OpenAI/key=value) plus a high-entropy backstop judged per separator-delimited segment (segments under twelve chars abstain) so a compound identifier is not masked. Redaction runs before anything reaches either store; sealing is the separate, switchable layer described in section 3, and the curated graph ships with it off.metapins the embedding model identity and vector width, so a store read with the wrong embedder is detectable rather than silently wrong, and records the store's ownencryption_state, so a store read without its key renders "[sealed — encryption key unavailable]" in place of a value — a placeholder, "never garbage, never an error" — rather than failing or leaking.- The external dataset is SHA-256 pinned and re-verified on
every open, not only on download, and the reason is written at
the function: "a truncated download must never quietly grade as a
small corpus." Both digests live in
longmem.rswith a committed test asserting they are well-formed. The data itself is never checked in — the repository carries the loader, the digests and the provenance note. - The system moves its own thresholds. Two auto-tune
dials rewrite
GraphConfigat session boundaries. Every move is damped, clamped, gated on volume and journalled, and a graph can opt out — but a reader comparing two installations should expect their delivery and conflict thresholds to differ, because each was fitted to its own graph. - The loop is scored against the people in it.
Engine::nli_agreementjoins each judged suspect's storednli_labelwith the verdict a person reached and reports the confusion matrix: hits, false alarms, misses, passes, and an agreement rate that is absent until a hinted pair has been judged. Surfaced asGET /conflicts/agreementand a Checkup panel block. Two properties make it worth naming. It counts misses — the judge confirmed a pair the model called entailment or neutral — so the measure is not only about the alarms the model raised. And it is read-only, never a calibration input: the auto-tune dials do not consume it, so the model cannot chase its own agreement score. The type's own comment scopes the claim to what the row can carry: conflict and replaces both land as confirmed, so agreement reads "the hint said contradiction and the judge kept the pair" and no finer. Almost nothing in this corpus measures whether its automatic nomination agreed with the human who adjudicated it, and the systems that do usually feed the answer straight back into a threshold. - Most numbers are self-generated. The offline suite's graphs, questions and controls are the project's own synthetic corpora. LongMemEval is the exception and it is one corpus, graded on the retrieval half only.
10. Tests, Evals, and Benchmarks
eval/ is a first-class crate with 108 committed result
files, 66 of them JSON — bench-100.json,
contradictions-500.json,
floor-100/500/1500.json, floor-dial3-*.json,
longmemeval-s-*.json, chains-200x20.json,
budget-*.json, sessions-*.json,
posttune-*.json and their .log companions —
and this is the part of the project most worth copying.
The ablation labels its own shipped row.
bench-100.json holds thirteen configurations with a runtime
stamp ("embeddings_are_fake": false, seed, embedder,
reranker), a corpus profile down to the verb mix and code-ref
distribution, and a phrasing mix of 45% lexical, 45% paraphrase, 10%
oblique. One row reads
"kw 0.15 · reranker VOTES <- ships today", and pure
RAG beats it on recall. It is the first committed run, 3 August 2026, on
the pre-enrichment corpus; the current headline is the 22 August table
in section 1, where the shipped row wins the weighted headline and R@1
and pure vectors keep oblique recall. Publishing the row you lose,
beside the row you ship, in the machine- readable artifact, is the
strongest form of the discipline this atlas credits Perseus Vault and memsem for.
Contradiction detection is decomposed, not scored.
contradictions-500.json: 100 contradictions, 97 caught,
"missed_by_retrieval": 0,
"missed_by_judgment": 3; 100 entailments with 78 supported.
Separating a detector's retrieval failures from its judgment failures
tells a maintainer which half to fix, and almost nothing in this corpus
reports it.
The floor sweep has controls. Unanswerable questions
at every threshold, with controls_declined rising as the
floor rises — so precision is never claimed without the recall it
cost.
The process model has its own test suites, which is
the shape the rest of the repository has.
crates/engram-cli/tests/ carries
process_model.rs, roots_binding.rs,
session_brief_hook.rs, setup_windsurf.rs and
setup_devin.rs — the binding ladder, the convergence of
concurrent launchers on exactly one core, and the per-client setup
writers, tested as integration rather than asserted in a doc. One of
them pins a case that is easy to get wrong under lazy opening: a
resolved store survives its project being unregistered. Beside them sit
three end-to-end journeys over the real binary — e2e.rs
("every test is a story a user actually lived", including a
version update reproduced by running one binary as two versions),
e2e_wiring.rs, and e2e_encryption.rs, which
encrypts, proves the bytes went dark, searches and reads prose
throughout, restarts, decrypts and proves the bytes are back, with the
sealing key in a file fallback so nothing touches a real keychain. The
store trait has a conformance battery of its own:
store_battery in
crates/engram-core/src/tests.rs runs one sequence — nodes,
edges, tags, redaction, suspects, audit — against both backends, on the
stated contract that "whatever passed on SQLite must pass unchanged
on TepinDB."
The external corpus
eval/LONGMEMEVAL.md and eval/src/longmem.rs
run LongMemEval-S (Wu
et al., ICLR 2025, MIT): 500 questions, each over its own multi-session
chat haystack of ~115k tokens, evidence sessions labelled, 30 questions
deliberately unanswerable. The receipt is
longmemeval-s-full.json — the whole population run,
questions_run equal to questions_total, mean
493 turn-notes per store, and the table reproduced in §1 above.
What the design gets right, and it is most of it.
- The population is not sampled. Every question, with
questions_runandcappedin the receipt so a partial run cannot be mistaken for a full one. - No model in the ingestion or grading path. One note per chat turn, verbatim, filler included; a question counts as answered when a note from a labelled evidence session lands in the delivered set. Deterministic and judge-free, which is the same standard the offline suite meets.
- The unflattering register is chosen deliberately and labelled. Engram ships no extractor — the deployed shape is typed notes an agent wrote — so as-is ingestion measures the floor for the product, not its intended operating point, and the page says exactly that.
- One embedder, five arms, identical haystacks, so what separates the rows is the retrieval stack.
- The runtime swap is verified rather than asserted.
The full run used bge-small served by Ollama on GPU instead of
onnxruntime on CPU, to bring ~16 h down to ~2 h. Both smoke receipts are
committed —
longmemeval-s-smoke20.jsonand-gpu.json— and every arm's R@1, R@5, MRR and token mean agrees to four decimal places. The claim of grade-identity is checkable from the repository, and it holds. - The rows that do not flatter are published.
whole-filescores a perfect 1.00 and its column of interest is the 122,515 tokens per question it costs; the blind 3kcurated-filebeats engram on R@1 (0.919 vs 0.909); pure vectors beat it on R@5.
Three things a reader has to hold while reading the table.
- The token column compares two different delivery formats. The engram arm is billed for a title plus a matched snippet per hit; the RAG arm is billed for a title plus the whole turn, for a fixed ten hits. Part of the 13× is calibrated delivery returning fewer notes — which is a real product behaviour, and it makes engram's recall harder-won, since its R@5 is computed over a set already trimmed — and part is snippet-versus-whole-note framing. A vector stack that also delivered snippets would close some of that gap.
- Session-level grading is generous to dumps, which the page states plainly: any turn from the right session counts, so a blind selection that happens to keep one filler line from the evidence session scores as a hit. The token column is the only thing separating present from readable here; the focus/noise metrics that do that job in the offline suite are not run.
- This is the retrieval half. The published benchmark grades a generated answer; these numbers grade a delivery, so they are not comparable with published LongMemEval scores and the page says so in its second paragraph. The reserved online half is named as the plan of record.
The chat ontology is the interesting part of the
adapter. Rather than forcing the software ontology onto chat —
where every turn lands as Insight and the type layer
contributes nothing — the run defines a two-type ontology entirely
as per-graph data: statement (a user turn,
rank_prior 0.05, so a first-party source outranks a
restatement at equal relevance) over reply (an assistant
turn, no prior, muted). Role is the one distinction an as-is ingester
can make without a classifier.
the_chat_ontology_validates_and_the_engine_accepts_its_types
asserts the engine's write boundary accepts the fitted types and refuses
the stock one they replaced. Zero engine changes — which is the
per-graph GraphConfig machinery demonstrating itself on a
register it was never written for, and the strongest available evidence
that the "roles, never names" rule is real rather than aspirational.
The gap in the external run is the warn rate on answerable
questions. The abstention result is strong — 30
_abs questions, 2 empty deliveries, 28 warned, 0 unwarned,
weak line auto-fitted on 27 of 30 stores. But
weak_evidence_top is read only in the abstention branch of
run_question; the answerable path computes rank and cost
and never asks for a verdict. So the run prices the false-positive side
of the calibrated line and not its cost, and a line that warns on
everything would also score 0.00 here. The offline suite does measure
that cost and does not hide it:
posttune-0.9.0-100-1500.json pairs
controls_unwarned 0.000 with answerable_warned
0.473 at 100 notes, and 0.016 with 0.472 over 375
controls at 1,500. So the warning line is well-behaved at both graph
sizes the project sweeps, and the price of it is that nearly half of
answerable questions come back carrying a "likely not in memory" flag.
The 1,500-note figure reads 0.011 in
posttune-0810-100-1500.json because the blind keyword index
normalises BM25 slightly differently than per-field scoring and the weak
line re-fitted from 0.898 to 0.878; the changelog counts it as "4 →
7" unwarned controls, and the receipt's 0.016 over 375 is six.
Running those same two counters over LongMemEval's 470 answerable
questions is a small change to run_question, and it would
turn the abstention paragraph from a one-sided result into a priced one
— on the corpus where it would matter most, because it is the one nobody
here wrote.
Sessions, measured against the recall they might cost
sessions-100-500.json plants multi-session
clusters on top of the regular crowd — one invented subject,
one complementary aspect per session, four entailed recaps beside each
aspect in the same session — and asks one aggregation question per
subject, scoring coverage: how many of the subject's
sessions reach the delivered list. The regular single-gold questions are
re-asked under the same knob as the price. At 420 and 1,875 notes:
demote cov@5 (420) cov@5 (1875) full coverage R@5 regular (1875) oblique (1875)
0 0.625 0.707 0.625/0.76 0.772 0.325
2 0.792 0.827 1.000 0.772 0.325
5 0.958 0.960 1.000 0.770 0.322
8 1.000 0.987 1.000 0.776 0.339
The single-gold columns are identical to three decimals at demote 0,
1 and 2, and the recall cost appears only from 5 upward on the 420-note
seed; the shipped constant is "the top of the measured free
zone." The real-data check found the knob inert on
the chat register: longmemeval-s-turns50-demote0.json and
-demote2.json are identical to the digit at 100 questions,
because engram's delivered list there is already trimmed to two or three
hits, the pool never exceeds the limit, and the selection never engages.
The eval README says so and says what owns the remaining gap — the
delivery trims, not the cut.
Two attacks on the delivery floor, both priced, neither shipped
The fixed delivery_floor of 0.22 was measured on the
note register, and on registers whose whole score scale sits lower it
behaves like the hard abstention gate the project had already refused
three times. floor-dial3-snippet.json and
floor-dial3-rerankfull.json price two fixes on one
sweep.
Dial three fits the floor per graph as a quantile of the
phantom questions' score body; it is recall-free at 100 and 500 notes
and costs 0.01 R@5 and 0.02 oblique at 1,500. Full-note reranker
input lets the cross-encoder judge title plus whole body and wins
on the note register uncut — oblique 0.34 to 0.41 at 1,500 — and
collapses on chat: longmemeval-s-turns50-rerankfull.json
reads R@5 0.957 to 0.777 against the same 100 questions, multi-session
coverage 0.557 to 0.246, delivery over-trimmed from 111 to 67 tokens per
query.
The two are antagonistic — full-note input raises phantom scores with answer scores, so the dial-three fit misfires on it — and the README's verdict is that neither ships as a default: full-note input stays a per-graph knob "refuted as a default by the chat receipt", and the auto-tune dial is not written until the fit is validated on the register it exists for, which needs a chat-register floor sweep that does not exist yet. The 0.22 stands, with both alternatives "priced instead of promised."
The --lme-turns 50 receipts that carry those numbers are
labelled: capped: true, turns_cap: 50,
questions_run: 100 of 500, and the README says they are
"not comparable to full-haystack numbers". They are a tuning
loop — about seven minutes on the GPU embedder instead of hours — and
the labelled answer sessions are always kept so a cap can never grade
retrieval on an unanswerable world.
Supersession, measured against an ablation
chains-200x20.json builds ADR-shaped history — 20 chains
of 3 generations, each replaces-ing the last, 40
supersessions over a 660-node graph — and runs the identical store with
and without them:
R@1 R@5 pollution head_first tokens
superseded 0.75 0.85 0.00 1.00 219.6
flat 0.50 0.83 0.88 0.59 292.8
rag 0.30 0.97 0.97 0.41 2,144.2
grep 0.00 0.67 0.70 0.02 2,656.8
curated-file 0.00 0.00 0.00 0.00 3,000.0
whole-file 1.00 1.00 1.00 0.00 165,115.0
pollution is the share of questions where a retired
generation was delivered, head_first the share where the
current one won the ranking. Pure vectors have the best R@5 on the board
and deliver a retired answer 97% of the time — which is the clearest
statement in this repository of what the graph is for. The
receipt also records retired_searchable: 0.0,
retired_fetchable: 1.0,
history_reachable: 1.0: retired generations leave the
search path and stay one link away.
Levers that were tried and refuted
budget-500.json is six configurations × four retrieval
limits over a 1,500-node graph and 858 questions, and it refutes the
obvious lever. Raising the limit from 10 to 30 moves the RAG arm's
weighted recall not at all — 0.9231 at every limit — while
tokens_mean goes 2,673 → 4,069 → 5,428 → 8,070 and
noise climbs 0.907 → 0.968. The shipped stack behaves the
same way, 0.9330 → 0.9343 across a 2.7× token increase. Spending
more delivered tokens buys nothing measurable, in either
stack.
The same file prices the two delivery trims directly.
engram open+no-trims reaches 0.9257 weighted recall with
noise 0.919 and 536 tokens; the shipped configuration
reaches 0.9330 with noise 0.546 and 293 tokens. Removing
the floor and knee roughly doubles both the tokens and the noise without
buying recall — which is the ablation that turns "calibrated delivery"
from a claim into a measurement. And a row that does not flatter sits
beside it: engram kw0, the keyword channel switched off
entirely, edges the shipped keyword weight of 0.15 on weighted recall at
limits 20 and 30 (0.9355 vs 0.9346).
The README keeps a standing list of mechanisms that did not survive
measurement — graph spreading activation, a deciding cross-encoder,
deeper reranking, and hard abstention, which was
measured, priced and rejected because refusing to return candidates
costs real answers. That last decision is why the budget rows all read
false_positive_rate: 1.0: that field counts unanswerable
questions that returned anything at all, and this system
deliberately always returns something and warns instead. The metric that
judges the warning is the pair controls_unwarned /
answerable_warned, and it holds its shape with scale —
0.000 / 0.473 on a 100-node graph, 0.016 / 0.472 on a 1,500-node one
with 375 controls. Between one and two controls in a hundred slip
through unwarned, and nearly half of answerable questions carry a
warning they did not need.
The encryption change ships with a regression
receipt. 0.9.0-ladder-run.log re-measures the full
ladder, 10 to 1,500 notes with real embeddings, on the blind
_kw keyword path, and the changelog's claim is that it
reproduces the previous note-register baselines "to the digit"
at every rung — R@5 0.94 and 198 tokens at 100, 0.78 and 297 at 1,500 —
with sealed-state parity asserted separately by the bit-identical-BM25
golden test. The post-tune receipt beside it is the one that moved, by
the 0.005 in the weak-line fit described above.
Every number in this section is read from committed receipts; the two LongMemEval smoke receipts agree field by field.
What the evaluation does not establish: what a model does with any of these deliveries. Both halves of the corpus — synthetic and external — are graded on retrieval, so the numbers price what reaches the context window and say nothing about what an answerer makes of it. The project names that gap itself and reserves a section for closing it.
11. For Your Own Build
Steal
- "Exposure doesn't validate." Stamp retrieval for observability and let it move nothing. A trust signal fed by recall is retrieval certifying its own outputs, and it keeps attractive wrong notes alive forever.
- "Time doesn't validate" — for stable knowledge. Decay episodic and volatile material; hold stable knowledge flat until judged evidence lands. Age is not disagreement.
- Make demotion reversible.
demoted_atis set by a liveconflicts-withedge and lifted when that edge is resolved, dismissed or deleted. A correction that cannot be withdrawn makes reviewers reluctant to correct. - Decide which of your own signals may not act. Drift is surfaced and deliberately never demotes, because a bad scan would mass-bury the graph. Knowing a signal is too noisy to be trusted with a write is a design decision worth writing down.
- Return the conflicts from the write that created them. Detection local, judgment the model's, both in one turn — instead of a queue somebody reads next session.
- Log the origin surface.
pane | mcp | daemon | cli | libraryon every audit row answers "was this the human or the assistant", which is the first question when a memory turns out wrong. - Label the shipped row in the benchmark file. Not the README — the data.
- Key your engine on roles, not on type names.
worklist,supersession,contradictionas flags a configured type or verb carries is what lets a user rename or replace the whole vocabulary without breaking a single behaviour — and it is what let the same engine run a two-type chat ontology on an external corpus with no code change. Declare the invariants that must survive any configuration (here: exactly one supersession verb, exactly one contradiction verb) and test them against every shipped preset. - Fit the "I don't know" line from probes your own store generates. Coin a subject that cannot exist, phrase the question in vocabulary borrowed from your own titles, and see how high the ranker climbs with no answer present. That ceiling is your abstention threshold, it needs no labels, and it moves with the graph — a constant fitted on someone else's corpus will not transfer.
- Damp, clamp and journal every self-tuning move. Half the distance to the fit, a stated range, a minimum delta below which nothing moves, a volume gate, an audit row, and a per-graph off switch. Self-tuning without those five is a system that silently drifts away from the behaviour you tested.
- Publish the before/after when you fix a resource bug. Six named stages, two committed JSON files, and the refuted hypotheses beside the confirmed one.
- Make a delete leave a record, and say what the record is not. A tombstone note with the victim's type, title, id and the reason is more than most systems here keep. Then decide whether the write path reads it; here it does not, and a design that says so is easier to finish than one that assumes the model will.
- Ship a knob at the top of its measured free zone. The session-diversity demotion was swept from 0 to 8 with the recall columns beside the gain, the default sits at the last value where recall did not move, and the real-data check that found it inert is committed next to the synthetic one that found it useful.
Avoid
- Letting a reranker decide. Measured here across every keyword weight: DECIDES loses oblique recall against VOTES. If you rerank, fuse.
- A single blended trust number. Three separate anchors are what make "withdraw the evidence, withdraw the demotion" expressible at all.
- Treating an NLI score as a verdict. The column comment is "models don't validate"; the score is a hint and the judgment is elsewhere.
- Reading a retrieval-graded benchmark as a benchmark score. The offline suite's corpus is the project's own, and the LongMemEval run grades deliveries rather than answers. Both are labelled as such in the repository; a reader quoting the numbers elsewhere is the one who drops the label.
- Pricing an abstention mechanism on the abstention questions alone. A line that warns on everything scores a perfect false-positive rate. The warn rate on answerable questions is the other half, and where this project does measure it, it is 47% at both 100 and 1,500 notes.
- Drawing a human-only line in a tool description. Pinning here is human-only because no tool sets it; approval is "human-only" because a sentence asks the model not to. Only the first survives a model that does not read the sentence.
- Comparing delivered-token counts across different delivery formats. A snippet-per-hit stack against a whole-document stack is measuring two things at once. Say which part of the gap is fewer results and which is shorter ones.
Fit
This suits a developer who wants project memory inside the editor,
with a graph they can see and correct, and small local models rather
than an API key. The editor integrations and the browser demo make it
unusually easy to evaluate before adopting. The configurable ontology
widens that fit: the shipped preset is software-decision shaped, but
research and minimal ship beside it and a
graph can define its own type and verb set, which is the mechanism the
external run exercises.
It is single-project and has no scope key, so it is not a fit for a team store or anything multi-tenant, and the README's own status line is "early development".
Take the policy module even if you take nothing else. It is a few hundred lines of constants and rationale, and it is the clearest statement in this corpus of which signals are allowed to change what an agent believes.
12. Open Questions
- What does the calibrated warning cost on LongMemEval's
answerable half? The harness computes the verdict only for the
30
_absquestions. Two counters over the other 470 would price it. - What does a model do with these deliveries? Every arm is graded on retrieval, so the comparison between a 208-token focused delivery and a 2,654-token whole-turn one is a comparison of inputs, not of answers. The online half is planned and reserved.
- How much of the LongMemEval result is the fitted chat
ontology?
--lme-ontology defaultruns the stock types beside it and the page lists that pair as reserved, so the type layer's contribution on this corpus is named but not yet a number. - How much does the encrypted history store get used in practice? The transcript-harvest layer is opt-in and physically isolated from curated recall; whether teams enable it, and how much it grows, is not observable from the tree.
- What does the agreement rate read on a real graph? The scoreboard is computed and rendered; no committed result file carries a value from a graph anyone actually judged, so the measure exists and its answer does not.
- How often does a session land on the home graph?
Rung five is logged per session in
mcp.logand the pane census shows the current binding, but nothing counts the fallback, so the frequency of the failure mode the ladder was built around is not observable from the tree. - What does the dial-three floor fit read on the chat register? The project names the missing artifact itself — a LongMemEval floor sweep with raw score curves — and has said the auto-tune dial waits for it.
- How often does a
tombstonedwarning stop a re-assertion? The write lands either way; whether the writer then deletes, rewrites or ignores it is the model's decision and nothing in the tree counts it. The audit journal has the material — acreatedrow with atombstonedwarning followed by adeletedrow or not — so the number is computable from a live graph. - What fraction of resurrections fall under the warning line? The key is cosine similarity to the tombstone at 0.70, and a rewrite that keeps the claim and changes the words is exactly the case a value key exists for; no committed probe measures the miss rate.
- What happens to a pinned node that is genuinely wrong? Evidence events skip it by design; contradictions surface for review, and nothing forces the review.
Appendix: File Index
Schema and types
crates/engram-core/src/schema.rs,types.rs,id.rs
Ontology and per-graph configuration
config.rs—GraphConfig,OntologyConfig,TypeRoles(including thetombstonerole),VerbRoles,FieldDef,FieldKind,presets(),describe_ontology
Trust, judgment and calibration
crates/engram-core/src/policy.rs,nli.rs,hub.rs;engine.rs—auto_tune,fit_conflict_floor,fit_weak_line,validate_graph
Stores
store.rs,store_sqlite.rs,store_tepin.rs,migrate.rs,registry.rsseal.rs— the at-rest codec, the machine key,EncryptionState, the blind keyword tokenstimespec.rs— the temporal grammar andTimeClock
Retrieval and briefing
rag.rs,cortex.rs,digest.rs,engine.rs,harness.rs
Inference runtime
onnx.rs— session policy, batch width, thread and arena knobs
Safety
redact.rs,error.rs
Surfaces
crates/engram-mcp/,crates/engram-http/,frontend/,engram-vscode/
Runtime and process model
crates/engram-cli/src/lazy.rs,main.rs,setup.rs,doctor.rs— the core spawn, the bridge, the one-shot REST clients and the per-client setup writerscrates/engram-core/src/settings.rs— the machine-level default agent projectcrates/engram-core/src/store.rs—resolve_db_path, thegraph.db/.tepinresolution every registry consumer has to go throughdocs/runtime.md,docs/multi-project.md— the process map and the binding laddercrates/engram-cli/tests/—process_model.rs,roots_binding.rs,session_brief_hook.rs,setup_windsurf.rs,setup_devin.rs,e2e.rs,e2e_wiring.rs,e2e_encryption.rsinstall.sh,install.ps1,hooks/— the fetch-and-stop installers and the portable brief hook scripts
Evaluation
eval/src/—longmem.rs(LongMemEval adapter),chains.rs(supersession chains),sessions.rs(multi-session coverage),generate.rs,arms.rs,run.rs,ollama.rseval/LONGMEMEVAL.md,eval/README.mdeval/results/—bench-100.json,contradictions-500.json,floor-100/500/1500.json,longmemeval-s-full.json,longmemeval-s-smoke20.jsonand-gpu.json,chains-200x20.json,budget-50/100/500.json,posttune-0.9.0-100-1500.json,posttune-0810-100-1500.json,sessions-100-500.json,sessions-100-seed2/3.json,floor-dial3-snippet/rerankfull.json,longmemeval-s-turns50-demote0/demote2/rerankfull.json,0.9.0-ladder-run.log,ladder-100/1500-enriched.json,mem-daemon-before/after.jsonand logsscripts/mem-probe.sh,scripts/screenshots.sh
Searches that ground the absence claims above (run at the pinned commit):
rg -n 'is_tombstone|roles.tombstone' crates/engram-core/src/engine.rs—write_warnings(:3325), the canon check (:2997),check_claim(:3769), answer candidacy (:3965),suspects_near(:4216,:4232), and the helper (:4854); no site refuses, collides or hides a write on a tombstone match — every consumer warns, labels, or skips.rg -n 'trust_override|set_trust_override' crates/engram-mcp/src/lib.rs— one hit, the read-onlypinnedfilter inlist_nodes; no tool sets it.rg -n 'async fn approve_node' crates/engram-mcp/src/lib.rs— one hit.rg -n 'set_graph_config|rename_field|rename_type|PUT /config' crates/engram-mcp/src/lib.rs— test-module hits only; no ontology or field-definition write tool.rg -l agreement eval/results/— empty; no committed value fornli_agreement.rg -n -i 'arxiv|bibtex|@article|doi\.org' README.md docs/ eval/— the TAA-k citation in the eval README and LongMemEval; no paper for engram itself, and noCITATION.cff.rg -n '^fn .*tombston' crates/engram-core/src/tests.rs— twelve cases; the two that write a claim after its tombstone (writes_near_a_tombstone_warn_tombstoned_across_types,an_authored_tombstone_that_replaces_its_victim_buries_it_traceably) assertCreatedwith atombstonedwarning, never a refusal.
History
2026-09-19 — re-pinned to cb7a3944….
human_review is withdrawn, on the evidence
the awarding paragraph already set out beside it. What the code reserves
to a person is real and stays in the report: no MCP tool writes
trust_override, the ontology has no MCP write surface, and
merge_nodes refuses a pinned victim from the assistant.
None of those holds a memory back pending a decision — a pin makes an
existing node immune to contradiction. The gesture that would be the
gate is approval, and approve_node is an MCP tool whose
entire restraint is a sentence in its own description, re-verified at
this pin at crates/engram-mcp/src/lib.rs:1356; the suspects
queue is handed back for the assistant to judge in the same turn. The
previous record's lib.rs:1102-1109 anchor no longer
resolves and has been corrected. The other four marks stand. Screened
again first; nothing was installed and no suite was run.
2026-09-16 — 7fa2e462…
— re-read at a commit dated 16 September 2026, eleven commits past the
previous pin. All five marks re-tested and held. Release 0.9.4 adds a
second route into the suspects queue and justifies it with a measurement
rather than an intuition: below the similarity floor a logic layer reads
the two bare titles, because similarity alone "cannot tell a
contradiction from an agreeing restatement (MemStrata AUROC 0.59; the
KnowledgeDrift gate probe reproduced it)", and on that probe titles
separated the two at 0.99 against 0.61 for the claim text. The two
auto-tuned dials are fenced from each other — only pairs the similarity
path could have raised may inform the similarity dial, since the logic
layer's verdicts "would drag the floor toward the NLI band it never
reads." And the removal flag is now carried as a role on every
surface rather than as a type name, so a custom ontology renaming its
tombstone cannot let a reader "take "Removed: X" for a memory of
X." Screened before reading, from a full clone: three auto-run
surfaces, no build-time execution point, three unpinned dependency
surfaces and three dependency files inside the seven-day cooldown; an
agent-addressed instruction file was recorded as data. Nothing was
installed, built or run.
2026-09-04 — 9a24db99…
— re-pinned the same day at release v0.9.2, three commits on. Screened
again: three auto-run surfaces, five files inside the seven-day
cooldown, two unpinned surfaces; nothing installed or run. v0.9.2 is the
write-path half of the tombstone role: write_warnings
reports any tombstone-role neighbour of a new note, across types, as
tombstoned with the removal's reason; the delete mint
carries the victim's body, tags and code refs by default and rehomes its
about edges; search names its tombstone hits;
check_claim gains a retracted bucket; the
suspect recorder skips tombstones on both sides; and the assistant's
bury gesture — author a Tombstone, link it
replaces the victim — is documented and tested.
tombstone stays withheld on the one clause the project
declines by design: the write is created and never refused, so the
record is consulted and the re-assertion is not prevented. The section 9
paragraph now names the two shapes already in the engine that would earn
it. The benchmark paragraph in section 1 is rewritten around the eval
README's Against every baseline table and its 22 August
receipts; bench-100.json is kept as the 3 August first run
where the shipped-row label appears, dated as such, and the corpus
superlative is dropped. Sections 6 and 10 date the same file.
2026-09-04 — f13d45a9…
— re-pinned at release v0.9.1 (142 commits, ~31,000 lines of Rust across
four crates, ~45,000 with their test modules). Screened again first:
three auto-run surfaces (.claude-plugin/,
.claude/settings.json, hooks/), four files
inside the seven-day cooldown, two unpinned surfaces, a
CLAUDE.md addressed to a reading agent; nothing was
installed, built or run. No capability mark moves. What moved upstream:
a tombstone type role and a delete that mints one, custom
fields with a graph-declared event clock on search
(date_field), at-rest encryption as two machine switches
with a blind keyword index that keeps BM25 identical, session-diverse
delivery at the cut, brief absorbing
set_project, installers that stop after fetching the
binary, setup writers for Devin CLI, Codex, Windsurf and Bob, three
end-to-end suites over the real binary, and the sessions bench, the
delivery-floor bake-off and the 0.9.0 regression receipts under
eval/results/. The bitemporal evidence gains
the second axis; tombstone is withheld with the near-miss
named — the record exists and the write path does not read it. Three
published claims were wrong at every earlier pin and are corrected in
the body: approval was described as an act the assistant cannot perform,
and approve_node has been an MCP tool since the first
reading with no source gate behind it; storage was described as SQLite
with a TepinDB backend beside it, and every new graph has been born on
TepinDB since v0.6.2 (21 July 2026), with the SQLite driver a migration
source — the project's own docs said graph.db until the
v0.9.1 sweep, which is where the reading took it from; and the open
question "does the TepinDB backend match the SQLite one … no parity
comparison found" named a store_battery conformance
test that has run both backends since v0.6.0. The stack row is promoted
from seeded to reviewed with a graph arm for the one-hop neighbours
every hit carries.
2026-08-22 — 2605d842…
— re-pinned at release v0.8.9 (112 commits, ~27,800 lines of Rust across
the crates, 38,700 with their test modules). Screened again first: three
auto-run surfaces, two files inside the seven-day cooldown, two unpinned
surfaces, and a CLAUDE.md addressed to a reading agent;
nothing was installed and no test was run. The trust engine did not move
— policy.rs, nli.rs, rag.rs,
cortex.rs and digest.rs are unchanged — and no
capability mark changes. The release is a process model: one machine
core holding every store, an MCP server that is always a bridge, a
five-rung project-binding ladder whose last rung is the home graph, a
set_project tool for clients that advertise roots and never
answer them, and four new integration test files under
engram-cli. Engine::nli_agreement adds a
read-only confusion matrix of the model's hint against the human's
verdict, deliberately excluded from the auto-tune inputs.
resolve_db_path now mediates every registry read, after
stale graph.db entries made live TepinDB stores report as
missing. Section 3 gains the runtime topology and section 8 the binding
ladder, which is where the consolidation costs something: separation
between projects was a property of the process layout and is now a
property of routing.
2026-08-15 — 15fbe809…
— re-pinned at release v0.8.7 (98 commits, ~30,200 lines across the
three core crates / 33,500 with engram-cli;
engram-mcp 3,046→3,778). Screened again; the
.claude/settings.json auto-run surface is still present,
nothing installed or run. No capability mark changed: all five hold, and
time-scoped search — the headline addition (719f7b56…)
— filters the record-time (created_at) axis with recency
ordering, not a new validity axis, so bitemporal is not
upgraded. Two additions are new context: a sealed, opt-in,
XChaCha20-encrypted episodic history store that
harvests seven assistants' transcripts (40b919bf…
accelerates its sweep with fs-notify), physically isolated from curated
recall; and a time-scoped search grammar over both
layers. Two prior claims are corrected: redact.rs is now
traced to the node write paths (with a per-segment-entropy fix), and
"deliberately little background" is qualified by the new harvester
daemon. The engram-cli crate was added. No paper for engram
itself; the only external citation remains LongMemEval.
2026-08-09 — cbc6f0b8…
— ten commits past the previous pin, covering releases 0.8.2 and a
drafted 0.8.3. Two published claims were wrong in the same direction and
are corrected here. The ontology was described as a fixed set of
eight node kinds; it is per-graph configuration —
config.rs, unchanged since the previous pin, stores types
and verbs as a document inside the graph, engine logic keys on roles
rather than names, three presets ship, and the hard invariants are
asserted by test. The same document carries version stamping and the
reserved handoff tag, a rename bulk-retypes the rows it
renames, and docs/customization.md described all of it at
the previous pin. Auto-tune was not described at all,
though both dials predate the previous pin: dial one fits the conflict
floor from judged suspects, dial two fits the abstention line from
synthesised unanswerable probes, and both run at session boundaries
under damping, clamps, volume gates and an audit row. The report also
read Alpha as a maturity label; it is part of the product's
name — the marketplace id is techtheist.engram-alpha and
the binary is engram-alpha — and the stability statement
the repository actually makes is its README status line. What moved
upstream: LongMemEval-S at full population, the first external corpus,
under a chat ontology defined purely as per-graph data;
merge_nodes with four committed tests; a supersession-chain
bench with a no-supersession ablation; budget sweeps that refute the
spend-more-tokens lever; and an inference batch-width cap with a
before/after daemon-memory receipt. The open question "how does any of
it perform on a corpus the project did not generate" is answered and
removed; the criticism replacing it is narrower — the external run
prices the false-positive side of the calibrated warning line and not
its cost on the 470 answerable questions. Screened before reading: 1
auto-run surface (.claude/settings.json hooks), 0
build-time exec paths, 2 unpinned dependency surfaces and 4 inside the
seven-day cooldown. Nothing was installed, built or run; the committed
receipts were read, and the CPU and GPU smoke runs compared field by
field.
2026-08-06 — 5721848a…
— first reading. The slug is engram-alpha because
engram is taken by a different project
of the same name; the two are unrelated. Screened before reading: 1
auto-run surface, 0 build-time exec paths, 2 unpinned dependency
surfaces and three inside the seven-day cooldown. Nothing was installed,
built or run; the committed evaluation artifacts were read, not
regenerated.