1. Executive Summary
ThoughtDAG is a context graph a person edits: an infinite canvas where each node is a question and its answer versions, each wire is context, and "what the model sees is exactly what wires into the node." What it records is what a model was shown, hashed at dispatch. Its weakness is at the canvas's edges: recall reads every project on the machine, archive does not reach a dossier sentence written from the turn before it was archived, and the log that holds each request hash drops its oldest entries past a cap.
Six things here are memory in the atlas's sense, and they are wired
together. The canvas persists — one IndexedDB key per canvas with a
debounced folder backup as a real .thoughtdag.json file —
and a generation's input is a deterministic walk of the graph
(store/context-builder.ts:274-463): materials first, dashed
reference blocks, then the solid chain in order, the question last,
archived nodes skipped at every layer. Each generation records the
fingerprint of everything upstream of the node with the node's own
content blanked (upstreamFingerprint,
:174-196), and recomputeStaleness
(slices/nodes.ts:274-292) marks a live node
stale when the fingerprint drifts from the recorded
one. A stale answer enters downstream context prefixed "[Stale: this
answer was written against an earlier version of its upstream]"
(:223,:441), and Replay regenerates stale nodes in
dependency order.
Second, a profile: two documents, preferences and
identity, one fact per line (lib/profile.ts). After an
ordinary generation a decision model — or the chat model when none is
configured — decides whether the exchange said something durable, and a
constitution in code decides admission: identity only when the user
stated it, a credential pattern blocks, three additions per
canvas per visit, duplicates and unknown update targets refused
(admissionCheck, lib/memory.ts:106-125). An
admitted fact is merged by a model that rewrites the document, newest
wins, with an eight-second Undo. Both documents ride every ordinary
generation as a [Memory] block.
Third, dossiers: one maintained document per topic,
four sections — what it is, decisions taken, where it stands, what is
open — each sentence citing the turns it rests on
(lib/dossier.ts). The chat model writes them from turns a
decision model has labelled by topic, and project facts the memory judge
files wait in a dossier's inbox until the next update.
Fourth, Session Atlas: the desktop shell fences the
session directories of Claude Code, Codex, DeepSeek Harness and Pi
(desktop/main.js:195-205), reads them and never writes
them, and mirrors a session onto a canvas turn by turn, appending
idempotently past a per-session ledger. A run a turn delegated to a
subagent lands as a branch beside that turn
(lib/atlas/subagents.ts).
Fifth, the why layer: a CLI that reduces those
session files — and the memory files Claude Code and Codex keep for
themselves — to an index of what each turn did to which files, papers
and URLs. It answers why <path>,
find "<phrase>" and recall, serves the
four questions read-only over MCP, and inside the app answers
recall: with the switch on, an ask brings in up to two
topic dossiers and verbatim excerpts from any agent's past sessions
within a token ceiling, listed and strikeable on the node
(lib/recall.ts:210-390). The canvas's own record enters the
index through an adapter that leaves archived nodes out
(lib/adapters/thoughtdag-canvas.ts:81-85).
Sixth, an agent lane: the model picker lists Pi's
and Codex's own models, and picking one hands the node's compiled
context and question to that runtime in a working directory
(lib/agents/agent-runtime.ts). A guard
(runtime/agents/pi-guard.mjs) asks the person, on the node,
before a tool reaches outside the working directory; the turn's tool
calls come back as footprint attachments excluded from context, and
downstream context carries one line naming the files the turn touched
(store/context-builder.ts:327-395).
The record is of what the model was shown, not what it
concluded. At dispatch the streaming slice writes a
commit event carrying the SHA-256 of the canonical request
— messages, image digests, model, tool flags — its message count and
lane (store/streaming.ts:366-375). The hash covers the
[Memory] block, any recall block and the execution-record
header the route picks, because all three are set before dispatch. The
canvas event log (slices/events.ts) is metadata-only,
survives undo, persists with the canvas and exports as CSV, and the
canvas adapter projects the commits of live nodes into canonical
context.committed events. Past 10,000 events it keeps the
newest 9,000 (:16,:24), so it is a bounded record rather
than an append-only one. The benchmark under benchmark/
applies the same discipline to a claim: nine endpoints, 1,215 captured
conditions, conditions defined as graph operations, traces immutable,
and a STATUS.md that records its own corrections.
A decision model decides whether the model is told an answer
is stale. With a judge configured, judgeStaleness
(lib/stale-judge.ts:36-69) asks whether an upstream change
bears on each newly stale answer, and a verdict under 0.5 dismisses the
mark. shownStaleIds (:23) subtracts it, and
the badge, the panel's preview
(focus-panel/FollowUpInput.tsx:69-74) and every request
path (slices/llm.ts:128,262,334,404,462,542,
slices/evaluator.ts:79,
lib/experiment-loop.ts:77) compile from that one set. The
dismissed answer stays in context, unlabelled; the dismissal writes no
canvas event.
Replay reads the same set. Its chip and confirm dialog count and
price shownStaleIds (App.tsx:713,1701), and
replayStale snapshots it
(slices/evaluator.ts:112). The stale set passes over
archived answers (slices/nodes.ts:281-282), a skip edcbe62d…
added, so Replay neither counts nor regenerates one. Two paths run an
archived node all the same. rerunNode has no archived check
(slices/evaluator.ts:36-44), so a manual rerun runs. And
after every completion triggerAutoReruns
(store/streaming.ts:623-667) reruns each node with
autoRerun whose ancestors include the completed node,
archived or not; a Replay wave's reruns pass through that hook, so an
archived reviewer below a replayed node is regenerated with them.
One mark: negative_eval, for the why layer's
hidden-reads case, the archived-canvas case and the benchmark's compiler
equivalence. audit_log is withheld on the event log's cap;
human_review, tombstone,
trust_state, bitemporal and
scope_enforced are withheld too, each a near miss stated in
section 9. No paper; the repository cites itself as software.
2. Mental Model
A belief here is a node the graph can reach. It enters three ways: a
person asks and a model answers on the canvas; a person imports material
— a PDF passage with its page, a link snapshot with its fetch time, a
note; or the atlas mirrors a turn another agent held, frozen as a
source snapshot beside the editable copy. A fourth way is
recall: a past turn or a dossier found by the question's words or topic,
listed on the node and carried into that one request. A node is
used when a wire leads from it to the node being asked: solid
for the full turn, dashed for a quoted reference, and the walk is the
same whether a person is previewing the request or the model is
receiving it.
An answer is believed exactly as written until its upstream changes, at which point it is stale — present, labelled as written against an older input — until a person replays it. A decision model can rule that the change does not bear on the answer. That ruling removes the label from what the person sees and from what the model is sent alike.
It stops being used by exclusion, not erasure. Delete the wire and
the node stays on the canvas, reconnectable; archive the node and it is
dimmed, skipped by every walk on its canvas, and left out when the why
layer next projects the canvas record
(thoughtdag-canvas.ts:81-85,:220); delete it and it is
gone, with an event in the log and no other record. The desktop rewrites
that record within a minute of a change
(lib/canvas-record.ts:14,:69-78), and the next query
re-reads a record whose size or mtime moved and replaces its lines
(cli/src/lib.ts:613-636,:655-690).
Two copies outlive an archive. A mirrored turn's text is not in the
canvas record at all — the index reads it from the runner's own session
file (thoughtdag-canvas.ts:106-107). And a dossier sentence
written from the turn before it was archived stays in the dossier recall
brings in whole. An answer is never overwritten — a regeneration is a
new version beside the old, each carrying the model that wrote it, the
time and the hash of what it saw.
The profile and the dossiers are the layers a model writes. A fact the memory judge admits is merged into a document by a model that rewrites the lines, "the newest observation wins"; a dossier is rewritten from the turns it has not read and the facts filed for it, and a sentence is dropped "only when the new excerpts contradict or supersede it". Each profile line carries a note of the fact that wrote it, its canvas, its time and its evidence. Neither layer keeps a record of a line it removed: the profile keeps forty changelog notes, and an Undo restores the prior document together with its prior changelog and notes.
What a model is told about a thing it is not shown has three answers here. A stale answer is shown, with a mark. An archived node is not shown, and nothing in the context says so. An agent turn's tool output is not shown, and the context says which files the turn touched and that their contents were left out.
Diagram source
%% caption: a person's wires are the retrieval; the same compiler, given one stale set with the judge's dismissals and archived answers removed, feeds the preview and the request, and Replay reruns that set; recall and the profile join the request before dispatch and the commit hash covers them; archive leaves the canvas walks and the index projection, while dossier sentences, mirrored session text and autoRerun keep reaching it
flowchart TB
P["person: ask · wire · archive · edit · strike a recalled item"] --> G[("canvas graph<br/>IndexedDB per canvas · folder backup")]
A["Session Atlas: Claude Code · Codex · DSH · Pi<br/>read-only session files"] --> G
G -->|"canvas record within a minute<br/>adapter drops archived nodes"| X[("why index ~/.thoughtdag<br/>sessions · canvases · agent memory files")]
A -->|"session text, mirrored turns included"| X
G -->|"live answer, upstream fingerprint drifted"| S["stale set"]
S --> SJ{"stale judge<br/>p below 0.5?"}
SJ -->|"shownStaleIds: dismissals removed"| C["buildContext<br/>materials → references → chain → question<br/>archived skipped · stale marked"]
SJ -->|"shownStaleIds"| RP["Replay<br/>counts and reruns the shown set"]
RP -->|"each completion"| AR["autoRerun pass<br/>archived nodes not skipped"]
C --> PV["will-send preview"]
X -->|"recall: two dossiers + excerpts<br/>light · deep · full reach<br/>active canvas left out"| R["recall block"]
PR[("profile: preferences · identity<br/>a note per line")] -->|"[Memory] block"| Q["request"]
C --> Q
R --> Q
Q -->|"commit: sha256(request) · n · model"| E[("event log<br/>metadata only · newest 9,000 past 10,000")]
Q --> LLM["model"]
LLM -->|"answer version + context hash + per-input parts"| G
LLM -->|"memory judge"| J{"constitution in code<br/>stated identity · no credential · 3 per canvas"}
J -->|"preference · identity: model rewrite<br/>new lines re-checked · at most two dropped"| PR
J -->|"project: topic dossier inbox or memory inbox"| D[("dossiers<br/>four sections · cited turns")]
D --> X3. Architecture
src/ is a React 19 and Vite application:
App.tsx (2,229 lines), the canvas components over
@xyflow/react, a zustand store split into slices
(nodes, llm, attachments,
highlights, history, events,
evaluator, roles) with
context-builder.ts, streaming.ts and
projects.ts beside them. lib/ holds the
mechanisms: memory.ts, profile.ts,
dossier.ts, recall.ts, topics.ts,
judge.ts, stale-judge.ts,
why-bridge.ts, canvas-chat.ts,
context-bundle.ts, graph.ts,
canvas-record.ts, persistence.ts,
local-backup.ts, the adapters/ for each
runner's session format, atlas/, events/ and
agents/.
server.mjs is the only backend: an Express proxy over
the Vercel AI SDK with web and scholarly search, MCP clients from
mcp.config.json, PDF extraction and URL fetch. On the
person's machine it also mounts the agent runtimes under
/api/agents, the other agents' session files under
/api/roots, and /api/judge, a pass-through
that forwards a decision request to any http or https URL the body names
(server.mjs:686-707); the hosted twin forwards only to
three named decision hosts.
runtime/ is plain Node that the desktop shell, the
harness plugin host and the proxy share: agents/pi.cjs over
pi --mode rpc, agents/codex.cjs over
codex app-server, the guard, ops.cjs,
fs-diff.cjs, sessions-fs.cjs and
terminal.cjs.
desktop/main.js is the Electron shell: fenced session
roots, backup writes, protocol links, the agent runtimes over IPC, a
private canvas record per project under
~/.thoughtdag/canvases/ (:536-560), and the
why layer bundled as a library behind eighteen why:* IPC
handlers (:889-909). dsh/lib/index.js mounts
the same why functions as HTTP routes under the harness plugin host.
cli/ is the why layer; functions/ the
Cloudflare twin of the proxy with no storage; protocol/ the
Context Bundle v0 proposal; benchmark/ the cases, gold,
suites, tools, compiled artifacts, runs and canvases.
Persistence is object storage into IndexedDB with a one-second
debounce and a flush on page hide (lib/persistence.ts). The
profile documents, the memory inbox and the switches live in
localStorage (lib/ui-store.ts:323-338), and
each canvas's ask-the-canvas turns under their own key, the last forty
(components/CanvasChat.tsx:21-24). The why layer keeps
fact-index.json, interpretation-cache.json,
text-index.jsonl, topics.json and
dossiers.json under ~/.thoughtdag/, written
through a temp file at 0600 in a 0700 directory
(cli/src/lib.ts:589-596). The first three carry the index
version and are rebuilt whole when it changes
(:91,:536-541); topics.json and
dossiers.json carry versions of their own
(:1243,:1405). Dossier writes are read-modify-write of the
whole file under their own lock, and past a five-second wait "the
write goes ahead unlocked" (:1445).
Deployment and ergonomics
Nothing to run for the desktop app beyond the installer; from source,
npm run server and npm run dev, a
.env with at least one key or a provider connected inside
the app, Ollama for a fully offline model. The decision model is
optional: DEFAULT_JUDGE is enabled with no provider, and an
OpenRouter key, saved with the interfaces or typed into the judge's own
field, makes OpenRouter's Jev endpoint the provider without a further
setting (lib/judge.ts:185-191). The provider list's
off entry leaves every decision to its rule. Every decision
falls back to a rule when no judge answers within six seconds, and a
failure skips the judge for a minute. Recall, topics and dossiers need
the why layer, so the web demo has none of them.
4. Essential Implementation Paths
- Compile.
partitionContext(lib/graph.ts:80) splits a node's upstream into materials, references and the mainline.buildContext(context-builder.ts:274-287) callscompileContext(:291-463), which walks them in that order, dedups attachments by fingerprint and honours excluded and included attachment ids. It skipsarchivedat every layer (:402,:419,:433), turns an excluded footprint into one line naming its files (:327-395), folds an imported turn's tool steps into one execution record (:98-156,:386-389), prepends the stale mark to a chain response in thestaleIdsit is given (:441), and returns messages with a parallelsourcesarray.hashContext(:160-165) andupstreamFingerprint(:174-196) hash it, the fingerprint over the tool steps' raw shape;upstreamParts(:203-220) records a hash and a 240-character opening per upstream node. - Dispatch and record.
streaming.ts:255-275inserts the[Memory]block after the last answer;:278fetches recall items on a fresh ask, stores them on the node and inserts the recall block at the same seat;:311-312swaps the execution record's header for an agent or a request that declares tools.:366-375computes the SHA-256 of the canonical request and logscommit. On completionwriteFinal(:216-252) appends the response as a version withlastContextHashandlastContextParts, a store subscription rerunsrecomputeStaleness(store/index.ts:89-93),:533-540firesjudgeMemorywith the active canvas, and:542-543run the autoRerun pass and the paradigm cascade. - Stale judge.
recomputeStalenessskips notes, materials and archived nodes, records the drifted fingerprint per node and importsjudgeStaleness, which compareslastContextPartswith the live parts, sends up to eight cases todecideStaleness(judge.ts:137), and stores a verdict keyed on that fingerprint.shownStaleIds(stale-judge.ts:23) subtracts dismissals; the badge, the preview, the context chain, the replay dialog's count,replayStale(slices/evaluator.ts:108-139) and every request read it. - Plaque summaries.
generateSummary(streaming.ts:34-87) asks the answering model, or the background model for an agent's answer, for one JSON ladder (lib/ladder.ts): a topic copied from inside the takeaway, the takeaway, a reason, and three or four sentence numbers whose verbatim text becomes the abstract. The thread it is shown is the ancestors' own plaques inside 1,400 tokens, archived steps skipped (lib/ladder-context.ts:29-44). The one-line fallback prompt's map lines come fromcollectMapLines(streaming.ts:91-107), which filters archived ancestors before it picks the takeaways and the opening question (:93). Summaries are display-only and never enter context or fingerprints. - Judge and admit.
judgeMemory(memory.ts:180-222) takes the typed path when a judge answers —decideMemoryreturns durable, category, stated and covers as probabilities, cut at 0.6, 0.7 for project and 0.7 for stated (judge.ts:69-93), and the chat model then phrases one sentence — or the prompt path, a versioned prompt parsed as one JSON line. Either verdict passesadmissionCheck; a preference or identity goes tomergeIntoDoc(profile.ts:91-119) with the verdict's evidence and canvas as its origin, a project fact tofileProjectFact(memory.ts:137). - Dossier.
buildDossier,updateDossierandrebuildDossier(dossier.ts:85-129) fetch the topic's unread labelled turns throughdossierNewTurns(cli/src/lib.ts:1484-1497), have the chat model return four sections as JSON with source keys validated against what it was shown, and write throughsetDossier(:1461), which consumes only the pending facts the model read. - Recall.
fetchRecallItems(recall.ts:210-390) asks the judge which topics the question is about, brings at most two built dossiers whole and asks whether the question wants a detail (:246-258). It then looks up at most four terms with typo correction and merges topic-labelled hits. Without a judge it reads the 16 best hits in full, and at the light reach the 40 best. At the deep reach the judge first reads a snippet of each of up to 2,000 hits; at the full reach, the opening of every turn the index lists (:303-314,turnsJson,cli/src/lib.ts:1111-1161). Those go forty to a request, eight requests in flight, and at most 80 that pass are read in full (recall.ts:322-353). The judge keeps items at 0.5 and holds back those from 0.3, and the budget decides what comes in. The switch, reach and amount are snapshotted on the node at the ask (ui-store.ts:473). - Ask the canvas.
buildCanvasChat(lib/canvas-chat.ts:100) numbers the live nodes, archived ones and frames left out (:43-48), and sends an outline of each with its parents, the focused node's chain, materials and references frompartitionContext, and the last eight turns;askCanvas(:153) calls the model and keeps on the turn which nodes went out in which layer. - Why.
cli/src/lib.ts: collectors per runner andparseMemory(:510) reduce session and memory files to facts;eventsOfreads a canvas record through the canvas adapter (:294-301);hitsFor(:799) hides read-like ops unless--include-reador nothing else happened;findJson(:1088-1103) filters by a working directory when one is passed;labelStart(:1292) runs the topic labelling job. - Agent turn.
agentOutbound(agent-runtime.ts:168) picks the route;compileForAgent(:268) splits the compiled messages into the question and one block;materialsToDisk(:224) copies materials;adoptAgentTurn(:444) re-reads the session file and excludes the footprints.pi-guard.mjs:75blocks a call outside the working directory when nobody can be asked;codex.cjs:35mapsallowtodanger-full-access. - Mirror, bridge, bundle, export.
importOrAppendSession(atlas/canonical.ts);dsh/lib/index.jsfor the harness's fork, inject and follow-up writes and the/why/*routes;compileContextBundle(lib/context-bundle.ts);export.tsfor the event log as CSV.
5. Memory Data Model
ThoughtData (src/types.ts) is the node:
question, response, responses[]
with parallel questions[], generatedBy[],
generatedAts[], editedAts[],
summaries[], summaryTypes[],
summaryTypeConfidences[] and
summaryConclusiveness[] (display only);
responseIndex; archived and
archivedAt; lastContextHash,
lastContextParts and lastGeneratedAt;
stepKind; autoRerun and
autoRerunRounds; importSource and
source, the frozen import snapshot; recall,
recallItems[] and recallMeta;
recallSource on a note cited from the why layer. Edges are
solid, dashed, orange or red.
A node that ran an agent turn also carries agentSession,
pendingApprovals and approvals[], one record
per decided question with the tool, the question as shown, the outcome,
the time and any value (src/types.ts:250-270). A project's
metadata holds agentCwd and agentGuard
(store/projects.ts:47-51).
A RecallItem (src/types.ts:138) names its
kind (turn or memory), runner, session, turn, title, working directory,
file, the words that found it, the text, its price, an
excluded flag, the judge's relevance, a cached card and the
topics it was reached by, and for a dossier the topic and its update
time.
MemoryDoc (lib/profile.ts:19-20) is
text, updatedAt, a changelog capped at forty
notes and notes, one LineNote per line keyed
by the line's text: the time, the canvas, the evidence —
stated, inferred, manual or
folded — and the line it replaced when a rewrite swapped
exactly one for one. Profile is two of them. The legacy
MemoryEntry list folds into the documents on first use,
project entries into the inbox, and the original list is copied to
thoughtdag.memory.legacy.
A Dossier (cli/src/lib.ts:1391-1402) holds
four sections of {text, src[]}, a sources map,
a covered list of the turn keys it has read, a
pending inbox, a changelog and builtAt.
topics.json holds the topic table and, per turn key, one
probability per topic. A turn key is a source and a position:
<file>#<index> for labels
(cli/src/lib.ts:1249) and
<session>#<turn number> for
covered (:1488, turnNumber
:864-866). A canvas turn's index is its rank among the
record's live nodes (thoughtdag-canvas.ts:81-83), so
archiving or deleting a node moves every later node's key onto its
neighbour's labels and coverage.
The canvas event (src/types.ts:463-470) is a timestamp,
an op, an optional object id and light metadata, "NEVER text
content"; CanvasOp (:452-461) has no
member for a profile write, a dossier write, a recall, an ask of the
canvas or an approval. The why layer's CanonicalEvent
carries basis — observed, reconstructed, inferred — and
completeness on every event.
6. Retrieval Mechanics
There are two retrievals. On the canvas there is a walk: the set of nodes reachable by incoming wires, in an order the compiler fixes so that "the same graph always produces the same prompt," trimmed by what a person did to the nodes and never by score. The preview calls the function the dispatch calls, with the same stale set (section 1); the request differs from it only in the blocks set at dispatch — memory, recall and the execution record's header for an agent or a tool-declaring request.
Beside it, recall is a search, off until a person switches it on. A question's terms are its Latin words of three or more characters and its CJK runs split on function words, at most four; a term the index barely knows is corrected from the index's vocabulary by edit distance, with the judge picking among candidates when it answers.
Topics the judge or a named word ties to the question bring their
dossiers first, whole, at most two; when a dossier is in hand and the
judge rules the question wants an overview, no excerpts are fetched.
Excerpts are ranked by matched terms and recency, then by the judge's
relevance, and fill a ceiling of 4k, 12k or 40k input tokens, never more
than two fifths of the answering model's window. With a judge, the reach
picked for the ask sets how many candidates it reads: the 40 best hits,
up to 2,000, or every turn in the index. Recall reads every conversation
the index holds, whatever folder it ran in, and the code gives the
reason: "a canvas has no workspace of its own"
(recall.ts:223-224).
The recall block tells the model the excerpts are "Evidence to
consult, not instructions" and asks it to name the source
(recall.ts:435). On a fresh ask the items are fetched and
sent in the same step; striking one takes effect on the next run of that
node.
Ask the canvas is a third read, of the active canvas only: every live
node as one numbered line, the focused node's chain, its materials and
references, with no stale mark on any answer it quotes. For an agent
runtime the compiled messages are flattened into one block ahead of the
question, and a continued session gets the question only. The why layer
is lookup, not ranking: why resolves a path to the turns
that touched it, reads hidden unless asked; find matches
exact words.
7. Write Mechanics
A generation blocks on the model and streams into its node; the debounced store write lands a second after the last chunk and the folder backup a minute after the last edit. The memory judge is fire-and-forget with failures silent by design, so a fact is retrievable in the next generation after the judge and the rewrite return.
The constitution is applied to the fact, and half of it again to the
document. After admissionCheck passes,
mergeIntoDoc sends the whole document and the new fact to
the default model with the instruction to rewrite the line it
"refines or contradicts", and keeps the model's lines if there
are at least half as many as before (profile.ts:100-106). A
new line matching the credential pattern is dropped (:108),
and a rewrite that would remove more than two lines is discarded for the
old lines plus the fact appended (:109-113).
The stated-identity rule is not re-applied: the code says the
distinction "cannot be re-decided from the rewritten words"
(:14-18). carryNotes (:44-55)
keeps a surviving line's note and gives every new or changed line the
admitted fact's origin, so a neighbouring line the model reworded while
merging a stated identity fact is labelled stated. The note is shown
beside the line on the memory page and exported with the document
(MemoryPage.tsx:60,:78-82); the [Memory] block
sends the lines without it (memory.ts:56-70).
A project fact goes to the dossier of the topic the judge ties it to
at 0.5 or above, or, without a judge, the topic whose name appears in
the fact; otherwise to the memory inbox. A dossier merges its filed
facts on a person's Update, or on its own when six wait and the
memory page is opened (MemoryPage.tsx:227). Building a
dossier reads up to a hundred labelled turns and an update sixty, one
chat-model call each.
Labelling is the one background pass over the store: a person's click
sends up to 600 turns, eight per request, to the decision endpoint, one
question per topic per turn (cli/src/lib.ts:1292). A label
is reused while its key has one for every topic
(:1303-1306), so a key that an archive moved onto another
turn is not relabelled. The harness bridge writes into live DeepSeek
Harness sessions, and the agent lane edits files in its working
directory behind the guard.
Operational cost
A generation costs its model call. A memory judgement costs one
decision call and one phrasing call with a judge, or one chat call
without, plus a rewrite call when the document is not empty. Recall
costs up to three decision calls per ask with a judge — topic, detail
and relevance — plus one per term it corrects, and none without one. A
deep or full reach replaces the relevance call with one per forty
candidates, which the project prices at about $0.0007 each
(recall.ts:154-157). Picking full in the menu shows the
index's turn count with an estimated time and price first; once it is
set as the default, an ask runs it without that dialog
(RecallChip.tsx:61-63). The benchmark pilot ran nine
endpoints on free tiers, about 30,000 input and 94,000 output tokens per
model.
8. Agent Integration
A model sees the compiled messages with a role as system prompt, the
[Memory] block, the recall block when recall is on, and the
question; it may call web and scholarly search, which start switched off
like recall (ui-store.ts:226-228), and any configured MCP
tool. It cannot edit the graph, the profile or a dossier.
An external agent sees four read-only questions over MCP —
why_check, why_file, find and
recall_turn — and find reaches the memory
files Claude Code and Codex keep. Inside DeepSeek Harness the plugin
host serves the why layer as /why/* routes, including
dossier and topic writes, to the embedded canvas.
Pi, Codex and the harness's own agents, picked as models, get the canvas's compiled context as one block ahead of the question, the materials copied to disk, and their own tools in the working directory. A runtime's questions land on the node as one form, and one permission switch per canvas maps ask, auto review and allow onto each runtime. The approval list is appended to and de-duplicated by request id, and the session record is merged rather than replaced.
The person's surfaces are the design: the canvas, the node panel with its versions, will send preview and recall section, the ask-the-canvas dialog, the memory page with the two documents and their line notes, the dossiers, the topic table and the inbox, and the folder backup. "The graph is acyclic. You are the loop."
9. Reliability, Safety, and Trust
Audit — withheld. The event log records every
semantic mutation of the graph with an id and metadata, survives undo,
and records at dispatch a hash of what was sent, recall and memory
blocks included. It is not append-only: logEvent keeps the
newest 9,000 entries once the array passes 10,000
(slices/events.ts:16,:24), a production delete of mutation
records that leaves no trace of the trim. Beside the cap: profile
rewrites, dossier writes, topic labels, stale-judge verdicts and
ask-the-canvas requests write no event; an agent turn's approvals are
recorded on the node; and an agent-lane commit hashes the
compiled messages under lane proxy
(lib/api.ts:263) when the runtime received a flattened
block. The canonical projection drops the commits of archived nodes
(thoughtdag-canvas.ts:220). The profile's and the dossiers'
changelogs are capped at forty notes, and an Undo restores the prior
changelog with the prior document.
Negative evaluation — awarded. The why layer's
committed case asserts read-only turns stay out of a populated result by
default and come back on request
(cli/test/why.test.mjs:131-145). A second case writes a
canvas record holding a live node and an archived one, and asserts the
archived node has no turn, wire or commit in the projection and that
find misses its words while finding the live node's
(cli/test/canvas.test.mjs:178-195). The equivalence suite
asserts the product compiler's node order for every pruned condition
against a reference, with the compiled artifacts committed.
Human review — withheld. The canvas is authoring: a person wires, archives and edits, and the preview shows what will be sent. The memory inbox is the nearest thing to a gate — a project fact no topic claims waits there, reaches no context, and leaves when a person files it to a dossier or discards it. Whether a fact waits is decided by the judge's topic probability: at 0.5 or above it skips the inbox for a dossier's pending list, which merges on its own at six. A preference or identity fact is written before the person is told, with an Undo. The tool-approval cards and the question form gate tool calls, not memory.
Trust state — withheld. Stale is a mark prepended to
text that flows. The stale judge's verdict is a probability that removes
the mark and keeps the answer in context. A profile line's evidence note
is a discrete field, stated or inferred among
four values, that no read path filters on. basis and
completeness on every why event are displayed and never
filtered; conclusiveness levels on takeaways are display only. Archive
is the field that excludes, and it is a person's put-aside, keyed on the
node.
Tombstone — withheld. Archive excludes a node from
its canvas's walks and from the index's projection of the canvas record,
and delete leaves no record. Both are keyed on the node: the same words
re-asked, re-imported or mirrored from a session come back, and a
mirrored turn's archive leaves its session text in the index. A
dossier's covered list keeps an update from re-reading a
turn whose sentence a person deleted by hand; it is keyed on the turn's
position, and Rebuild empties it.
Bitemporal — withheld. Every version carries when it was generated and hand-edited; a dossier's now section is the model's summary of the newest turns; no record carries when what it says was true.
Scope — withheld, by design. A canvas is its own
store and the wire is the only filter within it. The profile is global
to the install. Recall leaves out the active canvas's own record and
searches everything else, on the stated ground that a canvas has no
workspace (recall.ts:223-224), so an ask on one canvas can
carry another project's Claude Code memory entry. findJson
filters hits by a working directory on the session or the memory entry
whenever one is passed (cli/src/lib.ts:1088-1094), and it
is exposed as the why:find IPC option
(desktop/main.js:893) and a /why/find query
parameter (dsh/lib/index.js:1405). No caller in the tree
passes it.
The comment that the filter "serves the CLI and MCP"
(recall.ts:224) is not borne out: the CLI's and the MCP
server's find go through findHits and
renderFind and take no working directory
(cli/src/main.ts:289-295,
cli/src/lib.ts:1549-1553). The test named for the filter
asserts only that hits carry the field
(cli/test/memory.test.mjs:139-146).
What leaves the machine. A decision request carries
the exchange for the memory judge, and for labelling each turn's opening
300 characters of question and 700 of answer from every indexed agent
session. For recall it carries the question and 700-character excerpts:
up to forty at the light reach, a snippet of each of up to 2,000 hits at
the deep one, and at the full one the first 300 characters of question
and answer of every live turn and memory entry in the index, the active
canvas's record aside (recall.ts:303-353,
cli/src/lib.ts:1111-1161). Ask the canvas sends the canvas
outline and the focused chain to the chat model.
Read-only where it says so. Source sessions and
agent memory files are read and never written. The harness bridge's
fork, inject and followup write
into live harness sessions, fenced to the local host. The proxy's
/api/agents, /api/roots and
/api/judge sit behind a CORS rule admitting any localhost
origin on any port and a bind to 127.0.0.1 unless
HOST says otherwise
(server.mjs:578-590,:1297-1299); /api/judge
forwards to any http or https target, the loopback included.
A correction culture.
benchmark/STATUS.md records that the wave-2 reasoning
comparison was two different models, withdraws the generalisations drawn
from it, corrects the statistic to a paired McNemar test after a
reader's audit, fixes a scorer provenance bug, and says the sixth
condition has not been run — dated, in the file the numbers live in.
10. Tests, Evals, and Benchmarks
The CLI has 79 cases under node:test in ten files: the
why layer (22), the event contract (12), the canvas adapter (11),
DeepSeek Harness sessions (9), agent memory files (6), dossiers (5), Pi
sessions (5), topics (4), setup (3) and MCP (2). The harness file
aliases test to test.skip when Node lacks
zstd, so below Node 22.15 its nine cases report as skipped. The dossier
cases cover the storage contract: a topic's unread turns, a write,
pending facts that survive an update which did not read them, and five
concurrent filings that all land. The topics cases run labelling against
a stub endpoint and assert the key is not written to disk.
memory.test.mjs:139 is titled "find --cwd style
filtering is available to hosts through the JSON api"; it imports
the bundle, discards the import with void lib, and asserts
one hit's cwd field, so the filter it names has no test.
The recall ranking and its reaches, the stale judge and Replay's
snapshot, the summariser's thread and map lines, the profile rewrite and
its line notes, ask the canvas and the agent lane have no test under
cli/test/ or scripts/. The app has a
Playwright persistence smoke test and a layout test, and scripts assert
the undo rules over the history slice
(scripts/test-undo.mjs), the subagent fan-out over Pi and
Claude Code session fixtures and the backup write; there is no unit
suite for the store.
The memory judge's twenty-case golden set runs the prompt path
through the live proxy (scripts/test-memory-judge.mjs), and
JUDGE_PROMPT_VERSION guards that prompt. The typed path's
bars — 0.6, 0.7 and 0.7 — and the document rewrite have no golden
set.
The benchmark is the serious artifact. pilot-v1 is nine
families of arithmetic dependency graphs at three propagation depths,
compiled through the product's own buildContext, checked
for node-order equivalence, captured as immutable traces, and scored by
a declarative scorer that re-scores from traces. The captured conditions
are five — clean, polluted, source prune, subgraph prune, recompute
descendants. For glm-4.5-flash, with 27 cases per
condition: 27 clean correct, 9 polluted, 27 source prune, 27 subgraph
prune, 26 recompute. Across the seven endpoints in
STATUS.md, pruning the whole contaminated subgraph repairs
every derailed case while pruning only the source leaves a residue at
depth two and three.
Every repair case carries a sixth condition,
source_prune_stale: the source pruned and the contaminated
descendants kept under the product's stale mark
(benchmark/DESIGN.md:87). It is wired end to end.
compile.mjs passes its stale_nodes to
buildContext (tools/lib.mjs:92-98,
tools/compile.mjs:20), and 30 compiled artifacts carry the
mark. validate.mjs requires it and checks that its stale
nodes are the contamination chain (:55-67). The capture
loop sends its compiled artifact like any static condition
(tools/run-capture.mjs:39-63), and the scorer loop scores
it with adopted_stale_answer.
No committed run holds a trace for it, and STATUS.md:5
says it has not been run. score.mjs reads a trace for every
condition a case lists with no guard (:13-14), so
re-scoring any committed run against the six-condition cases stops at
the first missing trace; generate_figures.py plots the
five.
No paper; CITATION.cff cites the software, and the arXiv
ids in the tree are example queries in the why-layer guide
(docs/guides/why-layer.md:117).
11. For Your Own Build
Steal
- One compiler for the preview and the request. If the user can see exactly what will be sent, the retrieval is reviewable by construction.
- Hash what you sent, at the moment you sent it,
after every late-inserted block. A
commitwith the request's SHA-256 makes what did the model see a lookup. - Cite the turn under every sentence of a summary. A dossier line carrying its source keys, validated against the excerpts the model was shown, is a summary a person can check.
- Consume only what the writer read. An update that names the pending facts it saw leaves a fact filed mid-write for the next update.
- Exclude with a pointer. A withheld tool output that leaves which files, contents not included gives the model somewhere to look.
- Define benchmark conditions as graph operations and keep the traces.
Avoid
- A cap on the audit log. A trim that keeps the newest entries turns the record of what was sent into the record of what was sent lately.
- Keys by position. A label or a coverage mark keyed on a turn's rank among live nodes moves to the neighbour when a node is archived.
- A provenance label the rewrite hands out. A line note that every changed line inherits from the admitted fact says stated about words the user did not state.
- Exclusion on some read paths and not the others. An
archived node that the context walk, the stale set and the index skip is
rerun by its own
autoRerun, and its dossier sentences reach recall.
Fit
For a researcher or a developer who wants to see and shape what a model is told — across their own conversations and the sessions their coding agents left behind — this is a legible instrument, and the benchmark behind its one rule is kept with rigour. It is a person's tool, and the memory that forms on its own is two short documents and a set of dossiers a person can read line by line. The recall layer reads everything on the machine and ships excerpts to a decision endpoint when one is configured; anyone with client work in their Claude Code history should read section 9 before switching it on. A team wanting per-project isolation, a refusal that survives re-assertion, a validity axis or a log that keeps everything will find the design has not built them.
12. Open Questions
- Does the stale mark change model behaviour? The
source_prune_stalecondition compiles the mark into every repair case; no run has scored it besidesource_pruneandrecompute_descendants. - Should archive reach a dossier? A sentence built from a turn later archived stays, with the turn's key in its sources.
- Should a canvas turn's key be its node id? The why layer's turn ids
are (
thoughtdag-canvas.ts:79); labels and coverage use the position. - What does the judge do with a fact a person undid or a line they deleted? It is shown the surviving lines; the same sentence can be proposed again.
- Is the agent-lane
commitmeant to be a hash of what the runtime received? It hashes the compiled messages and labels the laneproxy.
Appendix: File Index
| Path | Lines | What it holds |
|---|---|---|
src/store/context-builder.ts |
489 | buildContext, compileContext,
hashContext, upstreamFingerprint,
upstreamParts, the stale mark, the footprint pointer, the
execution record |
src/store/streaming.ts |
667 | Dispatch, the memory and recall blocks, the commit
hash, version append, the memory judge call, the plaque summary and its
map lines, the autoRerun pass |
src/store/slices/nodes.ts, llm.ts,
events.ts, evaluator.ts |
591, 702, 26, 161 | Mutations and their events, recomputeStaleness,
setArchived, the request paths, the event cap,
rerunNode, replayStale |
src/lib/memory.ts, profile.ts,
dossier.ts |
223, 145, 157 | The constitution, admissionCheck,
judgeMemory, the document merge and its line notes, the
inbox, dossier synthesis |
src/lib/judge.ts, stale-judge.ts,
topics.ts |
349, 69, 62 | The decision contract and providers, the typed decisions and bars, the stale verdicts, topic proposal and matching |
src/lib/recall.ts, why-bridge.ts,
canvas-record.ts |
440, 53, 92 | Recall into an ask and its reaches, the IPC and HTTP bridge, the canvas record the index reads |
src/lib/adapters/thoughtdag-canvas.ts |
237 | The canvas record's projection, archived nodes left out |
src/lib/canvas-chat.ts,
src/components/CanvasChat.tsx |
181, 199 | Ask the canvas and its per-canvas turns |
src/lib/ladder-context.ts,
src/components/ui/RecallChip.tsx |
44, 124 | The summariser's thread, the recall menu and the full-reach dialog |
src/components/focus-panel/{FollowUpInput,ContextChainSection,RecallSection}.tsx,
src/components/ui/MemoryPage.tsx |
— | The preview, the context chain, recall items, the memory page with line notes and auto-merge |
src/lib/agents/{agent-runtime,http-bridge}.ts,
runtime/agents/* |
585, 76, — | The agent lane, the guard, Pi and Codex |
src/lib/adapters/*, src/lib/atlas/*,
src/lib/events/* |
— | Runner readers, Session Atlas, the event contract |
src/types.ts |
470 | ThoughtData, RecallItem,
CanvasOp, CanvasEvent |
cli/src/lib.ts, cli/src/main.ts,
cli/test/ |
1,605, 307, 79 tests | The why layer, memory files, topics, dossiers,
findJson, turnsJson, and the suite |
dsh/lib/index.js |
1,689 | The harness plugin's bridge, writes and /why/*
routes |
desktop/main.js |
1,017 | Fenced session roots, the canvas record, the why:* IPC,
the agent runtimes |
server.mjs, functions/api/[[path]].js |
1,305, 661 | The proxy with /api/agents, /api/roots,
/api/judge, and its stateless twin |
benchmark/{DESIGN,README,STATUS}.md,
tools/, runs/ |
— | The design, the pipeline, traces, results, compiled conditions |
scripts/test-memory-judge.mjs,
memory-goldens.json, smoke.mjs,
test-undo.mjs, test-subagents.mjs |
— | The judge golden set, the persistence smoke test, the undo rules, the subagent fan-out |
docs/guides/memory.md,
docs/guides/why-layer.md,
docs/reference/feature-status.md |
— | The memory guide, the why-layer guide; current, experimental, not promised |
Recorded searches
Run at the tree root of the pinned checkout with
git grep -E; a pipe into /usr/bin/grep filters
git ls-files output only.
git grep -n -E 'EVENT_CAP' -- src # events.ts:16,:24 only: the one trim of the log
git grep -n -E 'logEvent\(' -- src/lib/memory.ts src/lib/profile.ts src/lib/dossier.ts src/lib/recall.ts src/lib/stale-judge.ts src/lib/canvas-chat.ts # none: no memory, dossier, recall, verdict or ask-the-canvas event
git grep -n -A10 -E 'export type CanvasOp' -- src/types.ts | /usr/bin/grep -i -E 'approval|recall|memory|chat' # none: no op for them
git grep -n -E 'get\(\)\.staleIds' -- src/store/slices # nodes.ts:287 only (the previous set): no request or replay reads the unfiltered set
git grep -n -E 'archived' -- src/store/slices/evaluator.ts # none: rerunNode does not skip archived nodes
git grep -n -E 'archived' -- src/store/streaming.ts # :92-93 only: triggerAutoReruns does not skip archived nodes
git grep -n -E 'find\([^)]*\{[^}]*cwd' -- src dsh desktop cli/src # none: no caller passes cwd
git grep -n -E 'cwdOnly|recallFolder' -- src # none: the folder row is gone
git grep -n -E 'findJson' -- cli/test # none: the filter has no test
git grep -n -E 'findJson\(' -- cli/src/main.ts # none: the CLI's find does not reach the filter
git grep -n -E 'archived' -- cli/src src/lib/dossier.ts src/lib/topics.ts # lib.ts:91 only: dossiers and labels keep archived turns
git grep -n -E 'archived' -- src/lib/canvas-record.ts # none: the record keeps archived nodes; the adapter drops them
git grep -n -E 'lineNote|\.notes\b' -- src ':!src/lib/profile.ts' # MemoryPage.tsx only: no read path filters on a line note
git grep -n -i -E 'stale' -- src/lib/canvas-chat.ts src/components/CanvasChat.tsx # none: ask the canvas sends no stale mark
git grep -n -E 'decideMemory|MEMORY_DURABLE_BAR' -- scripts # none: no golden set for the typed path
git grep -n -i -E 'tombstone|denylist|blocklist' -- src cli/src dsh/lib runtime desktop # none: no refused-value record
git grep -n -i -E 'valid_?(from|to|until)\b' -- src cli/src dsh/lib runtime desktop server.mjs # none: no validity interval
git grep -l -F '[Stale: this answer' -- benchmark/runs/compiled # 30 artifacts, all source_prune_stale
git ls-files benchmark/runs | /usr/bin/grep source_prune_stale | /usr/bin/grep -v -c '^benchmark/runs/compiled/' # 0: no captured trace
git grep -l -E 'agent-runtime|pi-guard|fs-diff|compileForAgent|fetchRecallItems|judgeStaleness|mergeIntoDoc|carryNotes|replayStale|ladderContextFor|collectMapLines|turnsJson|buildCanvasChat' -- cli/test scripts # none: lane, recall, stale judge, Replay, summariser, profile rewrite and ask the canvas untested
git grep -n -E 'confirmDialog' -- src/lib/recall.ts src/store/streaming.ts # none: an ask at the default full reach is not confirmed
git grep -n -i -E 'arxiv|doi' -- CITATION.cff README.md README_ZH.md # none: no paper of its own
git ls-files src | /usr/bin/grep -i -E 'test|spec' # none: no unit suite for the store
git grep -c -E '^[[:space:]]*(test|it)\(' -- 'cli/test/*.mjs'; git grep -c -E '^[[:space:]]*t\(' -- cli/test/dsh.test.mjs # 70, plus 9 under the alias
History
2026-10-21 — 7d9ba5e3…
— 68 commits on, releases 0.5.9 to 0.5.22. audit_log
withdrawn, wrong at the previous pin: logEvent has trimmed
the log past 10,000 events since it was written (section 9); the README's
arXiv example had also moved to the why-layer guide by then. Overtaken:
archived canvas nodes leave the index's projection, index version 14 (eabdb893…).
The profile rewrite drops credential-like lines, removes at most two and
labels each line's origin (section 7).
Every repair case carries source_prune_stale, compiled and
not run (section 10); a
folder row reached the cwd filter and was removed in fc753e6e…;
map lines and the stale set skip archived nodes. Found at both pins: the
autoRerun pass reruns archived nodes (section 1). Screened from a full clone:
no auto-run surface, four manifests inside the seven-day cooldown,
AGENTS.md treated as data; nothing installed, built or
run.
2026-09-30 — 850b89d1…
— 35 commits on, releases 0.5.4 to 0.5.8. The maintainer fixed two
findings in 2991553e…,
recorded on issue
#51: Replay snapshots the judge-filtered stale set its dialog
counts, and the summariser's thread skips archived steps (section 1, section 4). The summary's
fallback map lines and the stale set keep archived nodes. Added: recall
reaches, deep and full, which hand up to 2,000 snippets or every indexed
turn's opening to the decision endpoint (section 9); web and scholarly
search off by default; subagent runs mirrored as branches. The profile
rewrite, archived nodes in the recall index, the missing stale benchmark
condition and the uncalled working-directory filter are unchanged; no
mark moved. Corrected: server.mjs had 1,201 lines at the
previous pin, not 1,195. Screened from a full clone: no auto-run
surface, five manifests inside the seven-day cooldown,
AGENTS.md treated as data; nothing installed, built or
run.
2026-09-28 — af1f82a6…
— 14 commits on, releases 0.5.2 and 0.5.3. Every request path compiles
from the stale set the preview shows, the judge's dismissals removed (d345f050…),
which closes the finding that a dismissed mark reached the preview and
not the request (section 1); Replay
still counts the shown set and regenerates the whole one (section 3). Added: summary ladders written
with the thread's plaques in view, and the why layer's MCP server in the
MCP Registry. The profile rewrite, archived nodes in the recall index,
the benchmark's missing stale condition and the uncalled
working-directory filter are unchanged; no mark moved. Screened from a
full clone: no auto-run surface, four manifests inside the seven-day
cooldown, AGENTS.md treated as data; nothing installed,
built or run.
2026-09-26 — 6e98abf7…
— 33 commits on, releases 0.4.17 to 0.5.1. Memory became documents: the
fragment list and its 45-day project decay gave way to two profile
documents a model rewrites, topic dossiers, a decision-model judge,
recall into an ask, and a stale judge whose dismissal reaches the
preview and not the request (section
1). No mark moved; scope_enforced stays withheld on a
working-directory filter no caller passes (section 9). Corrected at the
previous pin: section 9 read "Human review — awarded" against
the withdrawn mark, and the test count missed seven harness cases behind
a t alias (61, not 54). Screened from a full clone: no
auto-run surface, four manifests inside the seven-day cooldown,
AGENTS.md treated as data; nothing installed, built or
run.
2026-09-19 — re-pinned to f05fc44d…,
two commits and eight files on. human_review is
withdrawn; two marks stand. Nothing in the range touches the
mechanisms, so this is a correction to the reading. The surfaces the
record named are all real and all keep their description: the canvas,
the will-send preview built by the same buildContext the
request uses, and the memory manager. None of them is a state a memory
waits in. judgeMemory — the only path where a machine
writes a memory — calls setMemories with the new entry and
then raises a toast with an eight-second Undo, so the entry is
live before anyone is told about it, and an update overwrites in place
with the same affordance. A person disconnecting a node or deleting an
entry afterwards is authoring, which the rubric separates from review by
name. The pendingApprovals surface is a genuine human gate
and gates tool calls, not memory content; it stays in section 9 without
a mark, the same call this atlas made for OpenExecutive's approval
ledger. Screened again first; nothing installed, built or run.
2026-09-16 — 3c57d429…
— re-read at a commit dated 15 September 2026, 42 commits past the
previous pin. All three marks re-tested and held, and the review surface
is repaired: pendingApproval was one slot, so a second tool
call arriving in parallel evicted the first question before anyone saw
it and left the turn waiting on a card that no longer existed. It is now
pendingApprovals, appended to and de-duplicated by request
id. The agentSession record is merged rather than replaced
for the same reason, and each stored version now carries the effort its
run used. Screened before reading, from a full clone: no auto-run
surface, no build-time execution point, three unpinned dependency
surfaces and two dependency files inside the seven-day cooldown; an
agent-addressed instruction file was recorded as data. Nothing was
installed, built or run.
2026-09-07 — 0c6d961c…
— re-pinned twenty commits on, all of one day, for the agent lane: Pi
and Codex as runtimes the model picker offers, a guard per working
directory, an approval card, and footprints excluded from context with a
pointer in their place; the context builder's one change is that
pointer. The benchmark, the CLI, the memory judge and the event contract
are byte-identical to the previous pin, and no mark moved. Corrected in
the body: the line ranges cited for buildContext, its
archived skips and the stale-mark prepend were wrong at the previous pin
(the function starts at line 183, not 74), memory.ts is 168
lines rather than 240, and the event slice is 26. Added, with its
search, the statement that no compiled benchmark condition carries the
stale mark. Screened first: no auto-run surface, five manifests inside
the seven-day cooldown (the app, desktop, plugin and CLI manifests and
the lockfile), AGENTS.md treated as data; nothing installed
or run, the read made from a full clone.
2026-09-06 — e658c280…
— first reading, at the head of main, the sixth commit of
that day. Screened first: no auto-run surface, no build-time execution,
three manifests with floating ranges behind lockfiles, five dependency
files inside the seven-day cooldown (the CLI, desktop and plugin
manifests changed that day), an AGENTS.md treated as data;
nothing installed or run. The repository is 177 MB, most of it
documentation gifs and a video project, and the read was made from a
full clone. Three marks; four withheld with their near misses. The
benchmark's per-condition counts were recomputed from a committed
results.json and match the status file.