v10.4.0 — Awareness
V10.3 made the tools honest. This release adds the one read that was missing — what does the brain already hold — and, in building it, found a class of content that had been quietly unfixable since the day it was written.
assembly()
"What do I already know here" had no cheap answer. brain() is an inventory — id, kind, status, parent, dates and relations for every note — which is the right shape for auditing or locating one note, and the wrong shape for orienting before deciding whether to look something up.
assembly() answers that question instead: every note by title, grouped under the surface it lives in, with each surface's purpose alongside it. Three choices follow from the question:
- Dated collections collapse to a count and a span. Sessions, diary entries and logs are all titled
[yyyy-mm-dd]and are the bulk of the brain by note count. Listing them is a wall of dates carrying none of their content —"47 entries, 2026-06-30 → 2026-08-16"is the informational content of that list. - Domains nest under their book. The real shape of what the brain knows. The flat listing is the shape that got a domain misread as empty and cost an 8,259-byte note.
- Threads group by status. The working set is the actionable part; a resolved thread is history.
area= zooms into one surface. Each surface's purpose is read from the container note, not hardcoded — a copy in the tool would silently disagree with the note the moment either changed, and the note is the one the user can see.
Container purposes can now be healed
Serving that text exposed something that had been invisible since bootstrap: container purposes are written once and are then unreachable. revise() refuses containers via isContainer(), so nothing in the tool surface could update one, and a purpose that went stale stayed stale for the life of the brain.
The live instance still described Insights as holding "per-day logs of how its own content changed" — written 30 July, and wrong from the moment Insights gained the rendered graph and the claims register. Nobody had a reason to read that note, so nobody did.
bootstrap()'s re-run path now re-engraves them. It compares each container against CONTAINER_PURPOSES — one exported map shared by creation and refresh, because two copies of those strings would reintroduce exactly the drift this fixes one level up — writes only genuinely different text, and reports every change in purposesRefreshed. It overwrites a note the user can see, so it is never a silent side effect of a call made for another reason.
Singleton leaf notes are deliberately excluded: their bodies are content the assistant maintains, and re-engraving one would destroy it.
The general lesson
Nothing about the Insights wording changed in this release. It had been wrong for weeks, sitting in a note nothing had reason to open. Building something that displayed it found it immediately — which generalises: the cheapest way to discover stale text is to give something a reason to read it.
Also in this release
CI now discovers test suites by glob rather than naming them. The explicit list meant a new suite sat outside CI until someone remembered to add it, and nothing failed when they didn't — it happened to oauth.test.ts for a whole release, and to kinds.test.ts the day it was written. Both times the omission looked exactly like success: green.
CLAUDE.md gains a full release checklist covering every distribution BrainLLM ships through — tag, GitHub release, npm, the MCP registry — and the instruction to verify each one from outside the repo. A push satisfies every local signal while leaving every registry a user installs from untouched, which had by then happened twice.
176 tests, tsc --noEmit clean, build clean. 42 core tools (32 universal verbs + 10 surface reads), 75 with full mode.