Skip to content

Releases

v5.5.3 — The One Where Switching Stops Inventing Memories

Choose a tag to compare

@tpierrain tpierrain released this 27 Sep 18:58
2352292

What this release is about

When you change universe, your brain only tells you it still remembers what it actually read.

A universe is one sphere of your working life, such as a client or an employer. When you switch from one to another, your brain searches in the new one from that moment on. What it cannot do is forget what it already read earlier in the same conversation, so it says so: that one sentence is what keeps an answer from looking scoped to the new universe when it is not.

The sentence did not know what the conversation had read. It only knew which universe was active before, often left there by an earlier conversation. So a brand-new conversation whose very first message was /switch could be told "I still have everything I read in 'acme'" about a universe it had never touched. Nothing from that universe was loaded and nothing cost an extra token: the sentence itself was the whole mistake. It was also silent in one case where it should have spoken, when the universe read in was left two switches earlier.

This release makes the sentence true. Your brain now keeps a small private record of which universes each conversation actually read notes from, and the switch reads it.

What you get

Every time you switch universe (/switch)

  • 🤐 A conversation that has read nothing is told nothing. Start a fresh conversation, switch, and the confirmation no longer claims a memory it does not have.
  • 🧠 A conversation that has read something is told exactly what. The sentence names every universe whose notes you read that is now out of scope, even one you left two switches ago, and never names the one you are arriving in.
  • 🔍 Only what really came back counts. A search that found nothing read nothing, and a search across all your universes names the universes its answers came from, not all of them.
  • 🤔 When your brain cannot know, it says it conditionally. A conversation that was already open when you updated (even one you reopen after the restart) has no record yet, so until its next /clear or a new conversation it says "if I read anything in 'acme' earlier in this conversation, it is still in my memory", which is true either way.

What you have to do

Ask your brain to update its engine (/update-engine), then restart Claude once. Your notes, your keys and your constitution are untouched, as always.


Under the hood

A recorder, not a guess. A new recorder answers two moments. When a conversation begins with an empty window (a new conversation, or /clear), it opens an empty entry under that conversation's session id. After every search_vault and get_document, it adds the universe of each note the tool actually returned, read from that note's own frontmatter (the indexer's rule), so an answer with no citation adds nothing and an all-universes search adds only what it found. It never speaks, never blocks, and always exits 0. Each conversation gets its own append-only file in the brain's gitignored .cache/conversation-reads/, one universe per line: two reads landing at once can at worst write the same line twice, never erase each other. The fifty most recent conversations are kept, nothing travels with the brain, a single-universe brain records nothing, and renaming a universe renames it in these records too.

The switch knows which conversation it is in. Claude Code exports the same session id to the shell a skill runs (CLAUDE_CODE_SESSION_ID) that it hands every hook, and it changes on /clear. The switch reads the entry for that id only; another conversation's reads are never counted.

The record only vouches for a window it saw begin. A conversation with an entry is known: the reminder names the named universes it read, minus the destination and the cross-cutting scope, or says nothing. A conversation without one is not: it started before the hook was active (the gap between an update and the restart), or it was resumed after its entry was pruned. A read there is not recorded either, since a partial list would name some universes and hide others. Those conversations, and a shell with no session id, get the conditional sentence, under the same rule as before: silent when leaving the cross-cutting scope, spoken when leaving a named universe.

It reaches existing brains through the usual door. Two entry points, one per event: scripts/session-reads.mjs opens the record at SessionStart, scripts/conversation-reads.mjs fills it after a read. Both are added to settings.json by the update's additive hook reconcile. They are two files rather than one script on two events because the update report is printed by the engine already in the brain, which names a script once per event: one script on two events would read "conversation-reads, conversation-reads". Rehearsed on copies of two real brains, one installed at v3.4.0 and one at v5.5.2 with 720 notes: both report the two hooks as newly wired, each named once, and the owner's territory stays byte-identical.

Left out on purpose. A vault note opened with the plain Read tool is not recorded, since the brain reads through its own search.

Proof. The reported case is a test that was red first: a fresh conversation, /switch, no search, no residue sentence, run end to end through the real command with a real session id. Beside it, the reads being named, the two-switches-ago case, an empty answer, an all-universes search, a note opened in another universe, a rename, and both fallbacks. The hook's trigger was checked against a live Claude Code: it fires on the vault tools' real names, which is what the first version of this fix got wrong before any release. Mutation pass on what the branch wrote, after the code review's redesign: 95.26 % (the pure core at 98.39 %, every changed hunk at 98–100 %); the twelve mutants left alive are equivalent, and the redundant guards the passes pointed at were deleted rather than tested.

Thanks to @chrisdeniaud for reporting it, with the exact sentence and the question that made the fix obvious: does this cost tokens? It did not, and now it does not say anything untrue either.

v5.5.2 — The One Where npm Stops Blocking Your Search Database

Choose a tag to compare

@tpierrain tpierrain released this 26 Sep 09:11

What this release is about

If your brain's search stopped working after an install, the update brings it back on its own.

Your brain finds your notes through a small database that lives next to them. Building it takes one step during installation, and the newest version of npm (the tool that installs the engine's parts) now skips that step unless it has been told the step is expected. Kenjaku did not tell it. So on a machine with that new npm, an install or an update could finish without errors and leave a brain that could no longer search its own notes.

This is recent and does not concern most brains. If yours is one of them, this release fixes the cause and repairs the damage: the engine now declares that step, and the update checks the database really opens. If it does not, the update rebuilds it once and tells you it did.

Two smaller things are fixed along the way. After an update you may have read a warning about a dozen known vulnerabilities, with advice to report it to the author: they were real, in third-party libraries, all with a fix already published, and they are fixed. And on two machines, /sync could tell you your other machine had pushed nothing when its work had simply arrived earlier, in the background.

What you get

When you update your brain (/update-engine)

  • 🩹 Search works again if it had stopped. The update checks that your brain's search database opens; if the new npm left it unbuilt, it rebuilds it once and says so. If it ever cannot, it shows you the one command that does it by hand.
  • 🔒 The next install cannot break it. The engine now tells npm which of its parts need a build step, so the new npm lets them build.
  • 🛡️ No more vulnerability warning. The libraries are at their fixed versions, and every future release is checked before it ships.

The rest of the time, when you type /sync

  • 🔄 It says what came from your other machine, even if it arrived earlier in the background. You see how many files, from which machine, and when.
  • 📅 Every date and weekday is computed, never written from memory. If it says Friday, it was Friday.

What you have to do

Ask your brain to update its engine (/update-engine), then restart Claude once. Your notes, your keys and your constitution are untouched, as always.


Under the hood

The dependency fix (#132). npm audit fix in rag/ and local-mirror/: lockfiles only, no direct range moved, no major bump. The two notable transitive jumps are sharp 0.34→0.35 and global-agent 3→4, both pulled by @huggingface/transformers 4.2→4.3. The in-process embedder's vectors are unchanged (EmbeddingGemma q8, cosine 1.000000 on three probe texts, 4.2 vs 4.3), so no reindex.

The check that never ran. A new Dependency audit CI job runs on every push and every PR and fails on any fixable finding at moderate or above, in both package directories. Run against v5.5.1's lockfiles it exits 1 with 11 + 6 findings. A test pins that the job carries no if:, so it cannot be scoped away in silence.

The "unapproved install scripts". Both package.json files now declare, in allowScripts, the packages expected to run an install script (all native bindings or their loaders). A test reads each lockfile's hasInstallScript entries and fails if one is not declared. This is also what npm 12 needs: it blocks undeclared install scripts, and better-sqlite3 then has no native binary.

Healing brains npm 12 already broke. Declaring the scripts is not enough for a brain installed with npm 12 before this release: neither npm install nor npm rebuild re-runs a script npm 12 blocked. After its npm install, the update now opens an in-memory database from rag/ in a child Node; on exactly "Could not locate the bindings file" it removes rag/node_modules/better-sqlite3 and reinstalls it, once. An ABI skew (a binary built for another Node) is left to the search server's existing start-time rebuild. A repair that fails prints the manual command and never fails the update. On a real update it runs in the auto-finalize child, i.e. the new engine's code, so it fires on the first update to v5.5.2 even though the old engine drives. Rehearsed with the real npm 12.1.0 on v5.5.1's dependencies: broken before, "Search database repaired", database opens, a second pass silent.

/sync (#131). scripts/sync-report.mjs (pure core scripts/lib/sync-report.mjs, English and French) writes the whole report; the skill's step 5 runs it and relays it verbatim. It reads remote-arrivals.json regardless of announcedAt (that stamp is right for the unprompted announcement and wrong for an explicit question), and names the last push the remote received with its committer, so two machines with different git identities read as "from your other machine". Three acceptance tests, red first, process-level against a throwaway brain with a real remote. On the way, the guard for "a script a shipped skill calls must be shipped" only scanned engine-skills/; widened to .claude/skills/**, it found sync-report.mjs and pick-folder.mjs (called by /import since June) undeclared. Both are now carried by the update.

The nightly mutation run finishes again. local-mirror died at mutant 349 on most nights: a mutant made sync re-enter itself through syncAll, an async recursion that ate memory until the machine was killed. Fixed at the root (syncAll calls syncOne) and braced with a 3 GB heap ceiling. The scripts package, 13 306 mutants at ~30 s each, could never fit in six hours; it leaves the nightly and is measured per change instead.

Versions. scripts 1.20.0, rag 1.5.1, local-mirror 0.3.1.

Proof. Mutation on every production file the branch writes: local-mirror.ts hunk 100 %, audit-gate.mjs 91.46 %, install-scripts.mjs 97.67 %, lib/sync-report.mjs 100 %, sync-report.mjs 92 %, native-binding-heal.mjs 97.56 % (the one survivor is equivalent). The heal's first pass surfaced one real defect before it shipped: its failure message would have quoted a file path instead of npm's error.

v5.5.1 — The One Where a Correction Stops Breaking Your Links

Choose a tag to compare

@tpierrain tpierrain released this 14 Sep 12:26

What this release is about

When your brain fixes a spelling for you, it no longer breaks the links in the note it is writing.

You can tell your brain a handful of things that are always true in a sphere of your working life: how a client's name is really spelled, and the versions of it you keep seeing in transcripts. From then on, when it writes a note for you, it puts the spelling right and says so. It deliberately leaves some passages alone — quoted words, code, the address inside a link — because correcting those would falsify them rather than tidy them.

The vault's own way of linking one note to another, [[like this]], was missing from that list. So if one of your note filenames happened to carry a spelling you had declared wrong, every link pointing at it was quietly rewritten, and the link then led nowhere. Worse, the message you saw said the spelling had been corrected: the breakage was announced as a success, in a note you had no reason to re-check.

It was reported from a real vault the day its owner declared their first spellings, and six links broke at once. Anyone who has declared spellings since v5.3.0 could be affected; anyone who has not declared any never was. And if you are in the first group, the repair costs you one sentence: ask your brain to tidy your notes and it puts those links back itself.

What you get

Every time your brain writes or edits a note

  • 🔗 Links are left alone, always. A [[link to another note]] is an address, like the address inside any other link, and your brain now treats it as one. The prose around it is still corrected, exactly as before.
  • 👀 It tells you what it left as written. Instead of claiming a correction it did not make, it says it left the passage alone, so you can decide whether it matters.

If you have already declared spellings, once after updating

  • 🩹 Ask your brain to tidy your notes (/lint) and it puts the broken links back, on its own. This release stops new ones from breaking; the ones already on disk are found by that check and repaired without a single question, because the note they should point at is right there and there is only one of it. Everything it writes is committed as it goes, so any repair is one undo away.

What you have to do

Ask your brain to update its engine (/update-engine), then restart Claude once. Your notes, your keys and your constitution are untouched, as always.


Under the hood

One entry in a list that could only ever subtract. The guard keeps a set of protected spans — frontmatter, fenced and inline code, a Markdown link's target, an autolink, blockquotes, quoted material in three typographies — and a declared spelling is never applied inside one. [[…]] now sits beside them. Every entry in that list removes text from rewriting and none can cause a rewrite, so the change cannot introduce a correction anywhere; it can only spare one.

The span is bounded to a single line, so a half-typed [[ cannot exempt every paragraph below it — a test says so, and it is the one that kills the mutant that would have removed that bound.

[[target|alias]] is spared whole, alias included, and that cost is deliberate. The second half of such a link is prose, and it stops being corrected. It is the reporter's own framing: a lost spelling costs a word, a rewritten target costs a link. Parsing the pipe to correct only the alias was rejected — it buys one word and adds a parser to a guard whose entire value is being dumb and total.

The truthful report needed no new surface. The write notice already had a sentence for what it deliberately left alone; the fix simply makes that sentence the one an owner sees in this case.

The exposure is three releases old, not one. It was reported on v5.5.0; the declared-spellings feature landed in v5.3.0, and every release since carried it.

Proof. Four tests on the guard — the reported repro, the alias, the prose on both sides of a link, an unclosed [[ — plus one end to end on the hook that rewrites the write, each of them red first for the right reason. Mutation pass on the changed file: 94.70 %, every mutant on the new span killed. The one killable survivor it surfaced was pre-existing and is now closed with no production change: nothing asserted that never : Axion, with the space before the colon that French typing puts there, yields a clean spelling.

v5.5 — The One Where It Stops Blaming You for an Edit You Never Made

Choose a tag to compare

@tpierrain tpierrain released this 13 Sep 20:35
24ca15d

What this release is about

The ready-made skills your brain came with start catching up again — and it stops telling you that you customized a file you never opened.

Your second brain ships with ready-made capabilities: tidying your notes, folding recent captures into your pages, moving between the different spheres of your working life. They are plain Markdown, yours to read and tweak, and when you update the engine the ones you never touched are brought up to date while the ones you tailored are left exactly as you wrote them.

On a brain that has crossed several releases, the first half of that quietly stopped happening. The newer version of a skill landed beside the old one as a .new file — so nothing was lost and nothing was hidden — but the report explained it with a sentence that was not true: "your customized lint skill was kept exactly as you wrote it", about a file you had never opened. From then on, every release arrived as homework: a list of files to take or ignore, for changes you never made.

The old design was the careful one, and it is worth saying why. To know whether a skill is still the one it shipped, your brain compared it against its own copy of what it had been given — which is exactly right for as long as the two move together. One update that advanced one copy and not the other made them disagree permanently, and from then on your brain genuinely could not tell "we shipped this" from "you wrote this". Faced with that doubt it did the protective thing, every single time. The judgement was sound; what it was judging against had drifted.

So this release gives it a better thing to judge against: what the engine really published, version by version, going back to v3.6.0. A skill nobody edited is recognised as ours and simply comes up to date — however many releases it sat out — and customized goes back to meaning what you would expect it to mean.

What you get

When you update your brain (/update-engine)

  • 🔄 The ready-made skills catch up, however far behind they are. A brain installed a year ago and updated since finds /lint, /consolidate, /switch and their siblings simply arriving at their current version, instead of being handed a pile of .new files to sort out by hand.
  • 🏷️ "Customized" goes back to meaning you. The update report only calls a file yours when your bytes are genuinely not ours — so the summary you read after an update describes your brain rather than a bookkeeping accident.
  • 🛡️ A skill you really did tailor is protected exactly as before. Your version stands, what shipped since is merged around your words where they do not collide, and where they do you are asked which of the three you want. Nothing about that changed, and it is now proven against the published record rather than against a copy that could drift.

When you ask your brain to tidy your notes (/lint)

  • 🩹 A note whose header your brain cannot read is put straight — and it says so. This is the one piece of untidiness that costs you answers rather than neatness: until it is fixed, that note keeps replying to your searches with what it said weeks ago. It is now repaired in the same batch as the other small tidy-ups, named in the summary rather than done silently.

When you rename one of your universes (/switch)

  • 🔁 It tells you it is rebuilding the search, and starts — instead of asking your permission first. It costs minutes of computing and puts no note at risk, so the useful thing to hand you is the chance to stop it, not another question to answer.

When the note-watching had stopped (/rag)

  • ⏱️ Same again when your searches need to catch up. Your brain says what it is about to re-read and how long it will take, then gets on with it. Saying nothing is a perfectly good answer.

What you have to do

Ask your brain to update its engine (/update-engine), then restart Claude once. Your notes, your keys, your constitution and the skills you genuinely tailored are left alone, as always.


Under the hood

The fix is a change of evidence, not of judgement. A delivered skill has no recorded provenance of its own; its stand-in was the brain's engine-skills/ copy, read at update time. That is a local artefact, and the two-pass update out of an old engine — the old-parent/new-child path no suite in this repo can observe (CONVENTIONS §10ter) — advances the staging copy without the installed one. After that they disagree for ever, and every release hands out another sidecar. The installed bytes are now recognised against scripts/lib/engine-fingerprints.json, the record of what each published version actually shipped, which the engine already used and already trusted for exactly this question.

🛑 The property that had to survive is that the proof is MEMBERSHIP, never arithmetic. A file may be marked as ours only because its bytes are recognised in that table — never because a checksum was computed from the bytes sitting on disk. The day that slips, a real edit reads as untouched and the next update pastes over it. Both poles are held by a test: a contaminated skill comes out as a refresh, and a genuinely edited one is still preserved with its sidecar with the table present, so the widening cannot quietly weaken the protection.

The table grew from 15 files and 104 byte-states to 24 and 129, and the reach is the point rather than the size: /lint now carries five published states back to v3.6.0, /consolidate six, /switch eight. Whichever version a brain is stuck on, the bytes it is holding are in a row — which is what makes the repair retroactive instead of merely correct from here on. A retired skill still cannot be healed back into recognition: the tombstone gate applies to both doors into the table, and that is why it was extracted rather than respelled.

The tidier design was rejected on purpose. Recording real provenance for delivered skills at install time is the cleaner mechanism and it heals nobody: a brain that has already diverged stays diverged, and diverged brains are the entire population this is about.

Three questions stopped being asked, and one of them had the wrong name in its own specification. The register and the issue both said "re-index after a universe is created or deleted". Read against the code, neither half exists: creating a universe re-indexes nothing and cannot (a universe one second old holds no notes), and deleting one already re-indexes behind a confirmation the owner types in their own terminal, which ADR 0045 freezes exactly where it is. Renaming is the operation the row actually describes, and it is the one that changed. Each of the three was also proven to land in the brain's own history — for three different reasons, which is why assuming it would have been wrong — and the end-of-turn sweep that catches the one running from a shell script turned out to be tested only on a brain wired to a remote. Push is opt-in, so that is most brains; it now has a test that fails if the sweep is put behind the remote.

The rehearsal, which is a release gate here (CONVENTIONS §10ter, mandatory because this release changes the update path by construction): run on a copy of a real brain sitting at v3.5.0 — the version the defect reproduces on — through the two-update sequence, with the contaminated state reproduced first and then repaired. A recent brain was used as the control, to prove the behaviour it must not change.

The figure this project published about itself is no longer owed. The v5.1.0 note told its readers, in as many words, that its mutation numbers were measured before a review whose fixes changed four of the files they covered. That debt is paid here, while the instrument was warm: the five files re-measured whole — 95.40 % to 99.43 % — plus lib/yaml-scalar.mjs, which that review added and which had never been measured at all, 80.39 % → 100.00 % on eleven new assertions and not one line of production changed. All ten of its survivors were the same shape: every test proved what gets quoted, none proved what does not — and quoting a value that needed no quotes rewrites the header of every note already written. Whole-file numbers answer a wider question than the ranges they replace, so the record says that rather than letting a lower number read as a regression.

The release's own quality gate — what this branch writes — is 103 mutants, 97.09 %, with the new recognition code and the line that wires it in at 100 %, no survivors; the three left are named equivalents, down to a regex anchor only a file path containing a line break could distinguish. It was paid wrongly the first time, and that is the more useful half of this paragraph. The gate asks for the score of what the branch wrote; it was recorded as paid on one file — the small one — because the list was written from the last commit instead of from the branch's own diff, so the five files that are the fix above had never been under a mutant. Re-run properly before the tag. Nothing had shipped and nothing was broken, but the shape is worth keeping: a gate that is skipped leaves nothing to notice, and this one left a green log saying it had run.

v5.4.0 — The One Where It Stops Talking Like a Robot

Choose a tag to compare

@tpierrain tpierrain released this 13 Sep 08:28
db5ab55

What this release is about

Your brain stops interrupting you to ask things you would never have wanted to be asked, and says the rest in words you can read.

Ask your brain to tidy your notes and, until now, it came back with a list of findings in its own vocabulary — dangling links, orphans, frontmatter issues — and then asked you, one prompt after another, whether it could fix each of them. Somebody who had asked for their notes to be tidied got five questions in a row about machinery they had never heard of.

That was a deliberate choice, and it was the safe one: nothing should rewrite your notes behind your back. What it missed is that the questions are not all the same question. "This link says capacity-managment and your note is called capacity-management — may I fix the spelling?" is not a decision, it is a chore. "Shall I create a page for this person?" really is yours, because the page becomes your notes' own answer to who exists.

So this release sorts them. What is obvious and undoable gets done. What is small and reversible is announced once, in a single message you can stop. What actually shapes what your notes mean is still a question — and it now says what is at stake before it lists the options. What makes the first two safe is not optimism: every change lands in your own history, so anything done without asking is one undo away.

What you get

When you ask your brain to tidy your notes (/lint)

  • 🧹 A link that is plainly a typo is simply repaired. [[capacity-managment]] when capacity-management is sitting right there: one candidate, no judgement, nothing to decide. If two notes could plausibly be meant, it does not guess — that one goes in the message below.
  • 📋 The small tidy-ups arrive as one message, and your move is to stop it. "I'm about to do 4 things: … Say stop if you'd rather I didn't — otherwise I'll go ahead." Not four prompts, not a question mark: saying nothing is a perfectly good answer, and it is the one most people want.
  • ❓ What is genuinely yours is still asked — and it says why it matters first. Creating a page for a person your notes mention, settling a contradiction between a note and a page: those stay questions, and the question now opens on what it costs you rather than on a list of options.
  • 🗣️ The report is written in your words. "Links pointing at a note that does not exist", "Notes nothing links to", "Pages your newer notes have moved past" — where it used to say dangling links, orphans and stale entity pages. And the one finding that costs you answers rather than tidiness now says so plainly: a note the engine can no longer read still answers your searches, with what it said months ago.

When you ask it to fold your recent notes into your pages (/consolidate)

  • 🧾 It proposes the whole batch at once, and one reply is enough. "I've drafted 4 pages from your recent captures — 2 new, 2 refreshes." Accept some, decline others, in a single answer; it does not come back candidate by candidate to re-confirm what you already said.

Every time a conversation starts

  • 👀 The opening screen stops showing you the notes your brain writes to itself. Instructions meant for the brain — how to phrase an offer, which command to run, which word never to use in front of you — were reaching your screen word for word. What is left is the handful of facts that are actually about your notes.
  • 🕰️ It stops telling you it is up to date when what it means is nothing was newer when I last looked. Your brain checks for a new engine once a day; a release published a few hours after that check used to read as "up to date (checked today)". It now says which it is, and gives the clock as well as the day.
  • ↩️ Nothing it does on its own is a one-way door. Every change it makes is saved into your own history the moment it happens, so undo is always available, including for the things it did not ask about.

What you have to do

Ask your brain to update its engine (/update-engine), then restart Claude once. Your notes, your keys, your constitution and the skills you tailored are left alone, as always.


Under the hood

The unit of this release is the gesture, not the string, and that is what gave it an end. "Rewrite every message" has no bottom: there are ~3 700 lines of delivered skills and ~1 400 of constitution, and nobody can say when they have been audited. A gesture — a point where the brain asks, announces or acts on a person — is countable. The audit produced maintainers/registers/gestures.md: 62 gestures across 9 surfaces, 13 to change, 46 already right. And the count said something the plan had not expected — ten of the thirteen live in two places, the opening screen and /lint, which are exactly the two surfaces the field report named. Three quarters of the product was already at the tier the doctrine would give it, so this is two repairs rather than a sweep.

The doctrine is ADR 0043, accepted in v5.2.0; this release is the application it explicitly opened and did not do. Its gate is reversibility first, then confidence, and the tier belongs to the gesture, never to the skill — which is why /lint now spans all three tiers on its own. The middle tier is built once, in the engine (lib/batched-announcement.mjs), rather than reinvented per skill, and two of its properties are pinned word for word: the message says silence proceeds and asks nothing (no question mark, no "shall I"), and a gesture whose write would escape auto-commit may never ride the batch. The net is what buys the tier.

The opening screen: the July diagnosis was inverted, and the wrong reason had a live cost. A field report last July said the leak was an echo of what the brain relays. Re-measured two independent ways — the host's own renderer, and a live probe on a throwaway folder — the truth is the other way round: the channel we believed was the safe one is precisely the one printed to the owner. The fix made at the time was right under either reading, so nobody re-opened it, and four more emitters kept routing directives into that channel afterwards. All six now carry an assertion that what a person reads contains no instruction to the model. A right fix built on a wrong reason does not generalise.

The guard, and what it deliberately cannot do. lib/owner-facing.mjs decides the one honest subset of a wording rule that a machine can decide: a banned literal in a string the engine prints. It states its three blindnesses in its own header — it sees printed literals only, never a sentence composed at runtime; it cannot tell whether the replacement is any clearer; it judges a string, never a channel. ADR 0043 is explicit that no guard can judge whether prose is plain, and this one does not pretend to. A sweep runs every printed report through it, over a fixture with every section filled, so a section added later is covered the day it is written.

It was wrong once, and that is the test that matters for a checker. It read [[people/ada-lovelace]] as an internal routing marker, because a line opening on a bracket is how the engine addresses its own dispatcher — but a [[link]] is the reader's own notation, the thing they type in their notes. A guard that is wrong about the most ordinary string in the product is a guard someone switches off. Doubled brackets are exempt, with tests in both directions so the carve-out cannot quietly empty the rule.

Mutation pass, run over everything the release wrote — the two new modules whole and the changed ranges of the six files altered: 98.11 % and 100 %, one survivor left and it is a named equivalent (details). The interesting half is not the number: nine of the nineteen first-pass survivors said the new checker was tested only on what it catches — its allow-list of ordinary acronyms was entirely unasserted. Six more were answered by deleting production that could not change an answer.

The update path was rehearsed on a copy of a real brain installed at v3.5.0, before the tag: it converged to this release, 530 engine files were swapped, and the owner's own files came back byte-identical. The rehearsal only ever reads an original — the copy is taken without its history, so a push home is not even expressible. It earned its place by finding something no test in the repo can see: on a brain that has crossed several releases, an engine skill can stop receiving updates and the report explains it as "your customized skill was kept exactly as you wrote it", about a file nobody opened. The new version does land beside it and nothing is lost. A brain installed normally is unaffected — verified on the same brain with its two copies made equal again. Filed as #115 with the reproduction rather than patched in a hurry the night before a release.

Two of these came from the owner reading his own screen, the morning of the tag, which is the only test this class of defect has. The first is the status line above: the measurement was correct and the sentence was not. It said "up to date" — a claim about the world, in the present tense — when all the brain holds is a claim about its own memory, and the date alone read as "checked today" when the probe had run five hours before the release existed. Both halves are wording, and both are fixed: the verb says it is a memory, the stamp carries the clock. UTC is named rather than converted, because a line that renders differently per machine cannot be asserted by a test.

The second is one thing with two names, which this release elsewhere spends its whole argument against. The product calls a retrieval scope a universe, in both local...

Read more

v5.3.0 — The One Where It Says Which Universe It Answered From

Choose a tag to compare

@tpierrain tpierrain released this 12 Sep 15:40

What this release is about

Your brain can answer from one part of your life while you believe it is answering from all of it. This release is about saying which one.

If you keep several universes — a past employer and a new one, a few clients, work and personal — your brain narrows its searches to the one you are working in, plus the handful of notes you keep cross-cutting. That is the whole point of them, and it works. What was missing is that it never said so.

A narrowed answer and a complete one look exactly alike. So an answer that covered one universe read as everything you know, a note could be filed under one universe while your searches served another, and switching universe left the conversation you were already in still holding everything it had read from the universe you had just left. None of it was a malfunction: each piece was doing its job, quietly, and quiet is precisely what made the scope impossible to check.

This release makes your brain say, out loud and unprompted, which universe it is working in and what it could not re-scope for you. And since a brain that stays silent about its own scope is the same habit as one that stays silent about a waiting update, the update offer is repaired in the same movement: it now comes back until you answer it, and tells you how long saying yes actually costs.

What you get

When you work in more than one universe (nothing here appears if you have only one)

  • 🧭 It says which universe it answered from. An answer that covered your Acme notes now says it covered your Acme notes — instead of leaving you to remember, days later, which hat it had on.
  • 🔀 Switching now tells you what the switch could not undo. Your next searches are scoped immediately; the conversation already on screen is not, because it still holds what it read before you switched. It says so, and offers you the clean slate if you want it.
  • 🗂️ A note about to be filed in another universe is named before it lands. Not blocked — filing a note elsewhere is a perfectly good thing to do. Just never again without your knowing: fourteen notes once went to one universe while every search served another, and it was noticed by chance.
  • ✍️ A universe name you spell wrong is corrected, not turned into a new universe. Ask for "acme corp" when your universe is acme-corp and it fixes the name, quietly, instead of quietly creating a second one beside it.

When an update is waiting for you (/update-engine)

  • 🔁 The offer comes back until you answer it. It used to arrive once, at the top of a session, in a line that was easy to miss entirely — and on Claude Desktop it did not arrive at all. An update you never heard about is not one you chose against.
  • 🙅 And no thanks really means no. Remind me later means tomorrow, for as long as you like. No thanks backs off three days, then five, then three weeks, then two months, then stops for that version altogether — and a brand-new release starts the conversation again from the top, because refusing one version says nothing about the next. You are asked once a day at most, whatever you do.
  • ⏱️ It says what saying yes costs you: between 10 seconds and under a minute. The only duration you used to read was the scary one, attached to the rare case where your notes have to be re-encoded. An unknown cost is a cost you postpone.
  • 🆕 A brand-new brain knows from its first conversation whether it was born behind. Brains are often installed from a copy of the project that is itself a release or two old.

What you have to do

Ask your brain to update its engine (/update-engine), click Run the update, then restart Claude once. Your notes, your keys, your constitution and the skills you tailored are left alone, as always.


Under the hood

One rule, four surfaces, and that is why they ship together rather than as four fixes. ADR 0044 (narrowing a scope discloses what stayed wide, PROPOSED — the owner's to ratify) is the doctrine: whenever the brain narrows a scope, it says what the narrowing did not reach. #68 is the conversation window left wide; #82 is a scope that is right and never stated; #72 is the filing path moving while the pointer did not; #66 is a fact scoped so hard it does not survive a /clear. Fixed one at a time, those are four mechanisms that end up disagreeing. The nearest precedent in the codebase already said one of the four out loud — the reminder that live connectors do not follow a universe switch — and it is the shape the first three copy.

#68 — the residue a switch leaves behind. runSwitchCli re-points the pointer and retrieval is re-scoped from the next search; nothing said the conversation still held everything read from the universe just left. So the answer looked scoped and was not, which is a trust defect rather than a token one. The new disclosure is appended by the core, beside the connectors one, so the skill only relays it — and it covers both switch paths, not just the fast one (create is a switch too). The /clear suggestion stays advice, in prose, conditional: it is the owner's call, not the machine's.

#82 — the gate the documentation promised and nothing printed. The issue asked to expose "the same marker" the core already prints. That marker did not exist: the switch skill told the agent to look for the literal words below / past the disclosure gate "printed with the offer", and nothing printed them. So the skill sent the agent hunting for a string that was never there, leaving it only the one thing that same paragraph forbids — counting universes. The fix made the documentation true rather than working around it: a gate subcommand now prints exactly those words, reading the registry rather than the pointer (a pointer aimed at an unregistered universe is a damaged brain, not a multiverse). current's output stays one bare slug, pinned twice, because /sync compares it on both sides of a rebase — which is why the gate is a new verb instead of extra output on an existing one.

#72 and #66 — one hook, because they turned out to be the same question asked of the same event. Both watch a note being written: one to say when its universe is not the one you are working in, the other to correct a declared wrong spelling. Two hooks would have been two extra processes in front of every note write, which is a latency budget the owner has already ruled on once. It runs on PreToolUse, and that is load-bearing rather than incidental: the correction rewrites the tool input before the bytes exist, so there is no wrong file to rename afterwards. It is made by the code, never by asking the model to rewrite — a measurement taken before the design was fixed showed why, and it changed the design. The drift notice is said once per session per universe (fourteen identical warnings is noise, and noise is how a net stops being read), while the correction itself is always applied. It never blocks, and auto-switching on a write was rejected outright: guessing intent from one file would move your scope under you.

#100 — an offer that rests on one decision: being asked is itself worth a day. The stamp is written when the offer is spoken, before any answer exists. Everything downstream — the question, your answer, the command that records it — is then an improvement on "ask again tomorrow" rather than a load-bearing part, which is what makes every failure harmless: an unanswered question, a permission dialog you decline, a read-only disk, a conversation abandoned mid-sentence. There is no path through the feature that nags. It rides the hook that already speaks on every prompt, on Desktop as well as the terminal, rather than a new one. While a restart is pending it stays silent, and is not even looked up: you are already being told to close and reopen, and two update instructions at once is how someone does the wrong one first.

#101 — measured, not estimated. The field rehearsal could not answer this one: it stubs npm install and the re-index, so its whole run takes under three seconds. The figure came from a real, un-stubbed update on a copy of a brain installed at v3.4.0 — 9 seconds with no re-index, 24 seconds with one. The sentence states that range rather than a rounded figure, and still leaves room for what the stopwatch could not see: that brain's dependencies were already installed, and a cold install on a slow line is what fills the rest of the minute. A duration you beat is a promise kept. Whether a given update will re-index is not knowable in advance — the answer lives in the launcher's own manifest, which the update reads only after cloning it — so the sentence hedges honestly rather than promising what the machine cannot check.

A fourth door onto an engine script existed, and nothing watched it. A brain runs an engine script through three doors the suite guards: a wired hook, a delivered skill, the constitution. This release opened a fourth — a directive the engine injects into the conversation. The command that records your answer was written, unit-tested, run as a real process by its own suite, and declared in no delivery regime: every test passed, and the feature would have reached you with that command missing, so every no thanks would have gone unrecorded for ever. The new guard asserts on the directive's rendered text rather than on the module's source, and fails if the directives stop rendering, so it cannot go blind in silence. A test that runs a script proves it works, never that a brain will ever have it.

The update path was rehearsed on a copy of a real brain installed at v3.4.0, before the tag: the update landed, 508 engine files were swapped, the retired skill was removed, and the owner's own files came back byte-identical. The rehearsal only ever reads an original — the copy is taken without its history, so a push home is not even expressib...

Read more

v5.2.0 — The One Where Done Really Means Done

Choose a tag to compare

@tpierrain tpierrain released this 12 Sep 08:35
11d3dba

What this release is about

Your second brain had a habit of telling you something was done when it was not. This release is about the word done.

Your brain writes notes for you, tidies them, and updates itself. Each of those ends with a small report: saved, all clear, up to date. That report is the only thing you ever see, so it has to be the truth — and in five different places it was not.

None of them lost your work. A note it said it had saved was on your disk, exactly where you expected, just not backed up in the way your brain implied. A tidy report that said all clear was reading a note it could no longer understand and counting it as fine. An update you thought you had launched had not started, because the question was one line at the bottom of a long message. And when it told you it had found nothing, it did not say where it had looked.

They were not sloppiness — each one was a reasonable design that turned out to cover less ground than it claimed. This release makes each report say exactly as much as it knows, and no more. It also settles, once and in writing, when your brain may act on its own, when it should tell you what it is about to do, and when it genuinely has to ask you.

What you get

When your brain saves a note for you

  • 💾 A note your brain writes is really backed up. When you ask it to tidy or file something, the note it writes is now versioned by the same gesture that wrote it, instead of waiting for a net that never saw it.
  • ⚠️ And when it cannot, it tells you instead of ticking the box. It names the note, says it is on your disk but not backed up, and gives you the one command that says why.

When your brain tidies your notes (/lint)

  • 📄 A note your brain can no longer read is now the first thing you are told about. Not buried in a tidying backlog: it comes first, and it says what it costs you — until you fix it, your searches answer from the version before the damage.

When you update your brain (/update-engine)

  • 🔘 The question is a choice you click. It used to be one line of text at the bottom of a long message, easy to scroll straight past — and at least one person believed their update had run when it never had.
  • 💻 Each of your computers gets the whole update. Some parts of your brain cannot travel through your backup repository, so a second machine used to end up with the new files and last week's wiring. It now notices and catches up on its own.

In ordinary conversations

  • 🔍 "I found nothing" now says where it looked. Nothing in your notes or in Slack is a fact you can act on; a bare nothing reads, three days later, as nowhere. Your mail is now part of what it looks through before it says so.
  • 🤝 It knows when to act and when to ask you. A settled rule, in writing: it does the undoable things quietly, tells you before the ones that would surprise you, and genuinely asks only where the answer is yours. Everything it writes is versioned, so almost nothing needs a permission slip.

What you have to do

Ask your brain to update its engine (/update-engine), click Run the update, then restart Claude once. Your notes, your keys, your constitution and the skills you tailored are left alone, as always.


Under the hood

The doctrine came first, deliberately, and it is ADR 0043. Group 3 of the tracker sweep held two very different things: a decision about how the brain speaks and when it may act, and a wide clean-up of every existing string. The decision ships here because this release emits new strings — writing them before the doctrine existed would have manufactured debt in the exact dimension the doctrine exists to clean. Three tiers (act silently · announce then act, with a veto · genuinely ask), gated on reversibility first, confidence second, with auto-commit named as what buys the autonomy: a brain that versions every write can afford to act, because acting is undoable. The tier belongs to the gesture, never to the skill. Prior art is named rather than reinvented — Sheridan & Verplank's levels of automation, SAE J3016, terraform plan → apply, undo-over-confirm and warning habituation, plainlanguage.gov / GOV.UK. The wide clean-up of existing strings is v5.4's subject, not this one's.

#77 — the writes the hook could not see. The auto-commit hook matches Write/Edit. /consolidate and /file-back route through scripts on purpose — that routing is what makes their notes conformant by construction — so the net had never seen them. Measured on a real brain: a consolidation pass refreshed five vault pages, printed ✓ Refreshed five times, and committed none. The two writer scripts now persist their own note by wrapping the hooks' own attemptCommit rather than spelling "commit the vault" a second time; two spellings of one gesture are two behaviours to keep in step. An unmerged tree is the one case where persistence steps aside, out loud: add . there would bury conflict markers inside your notes. The exit code stays 0 — the note is on disk, and a warning is not a failed write. The issue's secondary finding ("auto-push was on and nothing was pushed") is not a defect: auto-commit is commit-only by design and the push is debounced to the end of the turn.

#81 — a guard the harness steers around. The write guards match Write/Edit, and the harness actively steers towards Bash for file changes, so the path we recommend is the one nothing watches. Measured on a real vault: a malformed note landed two days after the guard shipped and answered searches from stale content for three weeks, its only trace one error line inside vault_stats. The direction was chosen before any code was written, and the obvious one was rejected: inspecting write-shaped Bash commands means reading shell, and a guard that half-reads shell is worse than none. The cheap pair catches the same defect after the bytes land, which is enough when the damage is unreadable rather than lost. A note that opens a frontmatter block and yields no keys is now its own finding, reported first, with no exemption, and it is the one frontmatter finding allowed past the session-start noise guardrail — on that guardrail's own criterion, a true regression that self-clears.

#96 — two kinds of promise, one of which git cannot keep. Files travel through git; machine-local wiring does not. .claude/settings.json, .mcp.json and rag/node_modules bake absolute paths or are build output, so they are gitignored by construction. The self-heal gate asked about skills and MCP servers only, so a release shipping a new hook or a new dependency converged on the machine that ran the update and nowhere else — silently, because a hook that was never wired runs never and therefore cannot report its own absence. It now asks four questions. For hooks the oracle is the reconciler itself, asked what it would add and told to write nothing, so anything reported has a remedy by construction; for dependencies it compares the names rag/package.json declares against what is on disk. The permission allowlist is deliberately not asked: an entry the engine never delivered and one you removed on purpose are the same absence, /permissions is a documented way back, and the reconciler has no allowlist remedy to run — a gate that reports a gap nothing can close is worse than one that stays quiet.

#98 — consent collected where nobody reads. The update quotes the release notes in full before asking, which means the better the notes, the further the only actionable sentence is pushed off screen. Both places the update asks — the go-ahead and the per-file three-way choice, plus its grouped variant — now use a clickable control, with the prose kept as the fallback where the host has no such tool. Three things deliberately did not change: consent is still required, an unanswered question still runs nothing, and "you are already on the latest release" still asks nothing at all.

#83 — an absence stated wider than the search behind it. "Exhaustive search in the vault and the chat tool" followed by "no trace" reads, three days later, as a general absence. It is the near-miss of #80, which shipped in v5.1.3 — same subject, different defect — and it is the one failure here whose consequence leaves the machine: a true, sourced statement was deleted from a message to an executive. The scope and the conclusion are now one sentence, and connected sources (mail above all) are part of what an absence check must cover. The retrieval ladder gained a rung for them, ahead of the open web. It ships as doctrine, carried by all six documents that hold this discipline in both languages, and pinned by a guard on each.

The update path was rehearsed twice on copies of real brains, before the tag rather than after it. A brain installed at v3.4.0 (508 engine files swapped, ten skills installed, nine automatic behaviours wired, one retired skill removed) and a brain one release behind holding 663 real notes (six skills refreshed, reindex incremental, nothing re-encoded). Both came out with the owner's own files byte-identical. The rehearsal only ever reads an original: the copy is taken without its history and every write goes to a temporary directory.

Mutation testing, scoped to what changed. Seven of eight targets at 100 %. The eighth is the entry worth reading: the self-heal wrapper scored 27 % on the first pass, because the gate is pure and was tested from every angle while the two functions that go and read the disk had no test at all — paths could be broken, both existence checks inverted and an or swapped for an and, with the file still green. It is the project's own convention word for word ("pure I/O" is not an exemption), and the second time that same seam has been the hole. Five tests later: 85.71 %, six named equivalents. Two other survivors died by asserting a *separato...

Read more

v5.1.3 — The One Where It Stops Crying Wolf

Choose a tag to compare

@tpierrain tpierrain released this 11 Sep 10:18

What this release is about

Your second brain was reporting problems you did not have, and staying quiet about one you did. This release fixes both ends.

Your brain checks your notes for you. It looks for links that point nowhere, and for notes that nothing
links to, so that a vault which grows to hundreds of notes does not quietly fall apart.

That check was too suspicious. A screenshot you pasted into a note, a link written inside a table, the
notes your brain keeps for its own work: it flagged all three as broken or abandoned, and none of them
were. A list of alarms that are mostly false is a list you stop reading, and the day you stop reading
it is the day a real broken link can hide in it.

At the other end, the same brain was too trusting. When it searches your mail, your chat or your
calendar and the connection is down, what comes back is not an error message, it is an empty result.
Your brain read that emptiness as good news and told you there was nothing new. Nothing new and
nothing reachable look identical from the inside, and only one of them is worth knowing.

There is a third, smaller and constant: when your brain wrote a note for you, it cut your paragraphs
into lines at a width nobody had asked for.

Two of these were reported by people running real vaults who took the time to write up what they saw:
thank you to @StefanPenndorf, and to the reader who reported the
silent source.

What you get

When your brain checks your notes

  • 🖼️ A pasted image stops counting as a broken link. Screenshots, PDFs, anything you dropped into a
    note: they are part of your vault, and the check now knows it.
  • 📊 A link written inside a table is read as a link. It was being misread at the first |, so a
    perfectly good link to Acme | Q3 review was reported as pointing nowhere.
  • 🗂️ The notes your brain keeps for its own work stop being listed as abandoned. Meeting prep,
    briefings, one-to-one notes, its backlog: it wrote them, it uses them, and you were being asked to
    clean up after it.

In ordinary conversations

  • 🔌 A source that cannot answer now says so. Before telling you there is nothing new in your mail
    or your chat, your brain runs one small check that it knows must come back with something. If that
    one comes back empty too, the connection is the problem, and you are told that instead of being
    told all is quiet.
  • 📝 Your paragraphs keep the shape you gave them. When your brain writes a note, it stops cutting
    your sentences into lines at a width nobody asked for. A paragraph stays one paragraph, whatever its
    length, and your lists, tables and code stay exactly as they were.

In your brain's own instructions

  • 🧹 A rule you may have written yourself is now built in. If your CLAUDE.md carries a line
    telling Claude not to break lines inside a paragraph, you can delete it: the engine holds that rule
    now, for every brain.

What you have to do

Ask your brain to update its engine (/update-engine), say yes, then restart Claude once. Your notes,
your keys, your constitution and the skills you tailored are left alone, as always.


Under the hood

The checker's three false alarms, and what they had in common. All three came from the same
resolver treating "a thing a link can point at" as "a Markdown note".

  • #71 — attachments. buildResolver was built from the note list alone, so every ![[screenshot.png]]
    resolved to nothing. readVaultAttachments is deliberately the complement of readVaultNotes
    (everything not ending in .md) rather than an extension allow-list, which would have gone stale on
    the first format nobody thought of.
  • #73 — escaped delimiters. A wiki link splits on | (alias) and # (heading), but Obsidian lets
    both be escaped. \| inside a link target was being treated as the alias separator, truncating the
    target to something that existed nowhere.
  • #74 — the engine's own work zones. meetings/, briefings/, prep-1-1/, coaching/ and
    backlog/ are written by the engine for the engine. They are not orphans; they were never meant to
    be linked from a person's own notes.

The fix for #71 was live on a second surface nobody had looked at. Chasing a surviving mutant on
buildResolver's default argument found consolidation-candidates.mjs calling it with one argument:
the same bug, proposing "create a page for screenshot.png" in the session-start nudge. Worse, the
line after it read frontmatter off whatever came back, so an attachment reaching it threw a
TypeError inside a fail-open hook — which would have made the entire nudge vanish silently
rather than fail loudly. Both are fixed, and the attachment list is now read once and handed to both
scans, because giving it to one and not the other only moves the false positive.

Measured on a real 663-note vault, and the measurement is honest about itself. Orphans went
87 → 85; dangling links went 18 → 18. That vault holds zero attachment embeds and zero escaped
pipes (verified by grep, not assumed), so two of the three fixes could not possibly have moved a
number there — they were reported from a different vault, and both are proven instead by running the
checker as a process against a tree built to each issue's own repro steps.

#80 ships doctrine, not code, and the design call is the whole content. A control query is only
worth anything if it (1) goes through the same route that answered empty — in the field report the
read route worked throughout, so a control on it would have cheerfully reported "healthy" about a
dead search route, (2) carries no keywords, so a genuine absence of matches cannot be mistaken for
a failure, and (3) is built so that zero is impossible on a live account. The sync-sources skill
now carries one named control per shipped connector, an explicit escape hatch for connectors that can
only offer a weaker one, and the consequence: a DOWN verdict raises an alert and disables every
negative claim that depended on that source
, never cached, with a paced fan-out. Guarded by
lib/source-liveness-discipline.test.mjs across four surfaces (both constitutions, both skills).

#95 needed a tool before it could be a rule. The issue assumed scripts/unwrap-markdown.mjs
already shipped with the engine; it did not. Writing the rule without it would have had the
constitution point at a command no brain has. The rewriter is pure and its every predicate is a
refusal to join: frontmatter, fenced blocks, tables, headings, horizontal rules, HTML, list items,
blockquote prefixes and both spellings of an explicit hard break are all left exactly where they are.
The default is to leave a line alone. A repair pass over an existing vault is available as
node scripts/unwrap-markdown.mjs [--check] <file|dir>, with --check exiting non-zero so it can gate.
Run against a real brain installed in June and never touched since, it reported 9 of its 30 notes
as carrying breaks nobody asked for — which is how common this was.

Rehearsed on a real brain before the tag (§10ter). A brain installed 2026-06-18 at v3.4.0, in
French, was copied and updated to this release: 504 engine files swapped, engineVersion.scripts at
1.18.0, and the owner's vault and hand-edited files came back byte-identical. The new script was
checked running inside that updated copy rather than trusted from the report, and its French
constitution came out byte-identical to templates/fr/CLAUDE.engine.md, both new rules included.

Test quality. Mutation score on the two new files: lib/unwrap-markdown.mjs 98.36 % (81.45 %
on the first pass — 16 further tests were written to pin the boundaries each survivor exposed; the two
remaining survivors are equivalent by construction), unwrap-markdown.mjs 100 %. One of those
tests exists only because a mutation that deleted a path separator instead of converting it
survived every local run: on macOS join() never produces a backslash, so the conversion is dead code
here and would have printed vaultpeoplejane-doe.md on Windows (CONVENTIONS §9 — a local green is a
POSIX green).

Verified. All 7 matrix cells (Node 22/24/26 × macOS/Windows) plus the Windows installer
end-to-end, on PR #97.

Closes #71, #73, #74, #80, #95.

v5.1.2 — The One Where the Restart Nudge Finally Shuts Up

Choose a tag to compare

@tpierrain tpierrain released this 09 Sep 14:36
5b16101

What this release is about

When your brain asked you to restart Claude, doing exactly what it asked did not make the request go away. This release ends that.

Your second brain updates itself in the background. When it does, the conversation you are in is still
running the older version, so it tells you: close Claude, reopen it, and come back to this same
conversation. That message is deliberate and it matters, because until you restart, your brain cannot
use what the update just installed.

The trouble was that it had no way of noticing you had obeyed. Coming back to a conversation does not
count as starting a session, and starting a session was the only moment the reminder was cleared. So
for some people the message came back on every single reply, in front of every answer, with no way out
short of deleting a hidden file. It was never wrong about the update, only unable to see that you had
already done your part.

Now it can. Your brain remembers which running copy of Claude asked for the restart, and stops the
moment it is talking to a different one.

What you get

In ordinary conversations, right away

  • 🤫 The restart reminder stops when you restart. Quit Claude, reopen it, return to the very same
    conversation, and it is simply gone. Nothing to delete, nothing to type.
  • 🔔 And it still speaks up when it should. A reminder that has not been acted on stays, message
    after message. Silence has to be earned by an actual restart, never assumed: being told nothing when
    your brain is running old code is the worse of the two failures.
  • 💬 Opening a fresh conversation is no longer the secret way out. It worked before, and nobody was
    told. Now the instruction you are given is the one that works.

When you update your brain

  • ⚙️ Nothing changes in how updates run. Same command, same report, same promise that your notes,
    your keys, your constitution and the skills you tailored are left alone.

What you have to do

Ask your brain to update its engine (/update-engine), say yes, then restart Claude once. That last
restart is the thing this release just made honest.


Under the hood

The mechanism. .cache/restart-needed is the marker that drives the reminder. It is armed by the
session self-heal, by the updater and by the startup pull, and it was read on every prompt by the
UserPromptSubmit hook. Its only eraser was a SessionStart self-heal that found no convergence gap
— and resuming a conversation runs no SessionStart. The directive's own wording ("come back to THIS
same conversation") therefore guaranteed the marker's survival.

Why the obvious fix was measured, and rejected. The first attempt asked the search server to stamp
a boot trace, on the theory that a full quit respawns it. A rehearsal in Claude Desktop on a throwaway
brain, on 2026-09-08, disproved it: opening a new conversation without quitting anything also
respawns a server and stamps an indistinguishable trace. A verdict built on that would have gone
silent on a reminder that was still true — a false alarm traded for a silent one, which is worse. The
instrument was write-only precisely so this could be found out safely, and it has since been removed
(7d8980b), because the fix that shipped never reads it.

What the same readings handed over. Across three readings exactly one thing discriminated: the
Claude application process. It changed on a real ⌘Q and held across a new conversation. The hook
that repeats the reminder runs inside the conversation process, whose ancestor is that application,
so it reads the identity itself — no trace file, no second writer, and no SessionStart needed.
scripts/lib/claude-app-identity.mjs walks up from process.ppid through one read of the process
table; the marker records the app that armed it; the prompt hook compares, stays silent when they
differ, and erases the stale marker so the rest of that conversation costs nothing.

The detail the whole fix hangs on. The per-conversation process lives at
…/claude-code/<version>/claude.app/Contents/MacOS/claude, which differs from the application's own
path only by case. The match is therefore case-sensitive and additionally refuses anything under
/claude-code/. Matching it would have called every new conversation a restart, which is exactly the
silent failure the measurement existed to prevent.

Failing towards the reminder, everywhere. No identity in the marker, none resolvable now (every
CLI session, every platform the match does not cover), a marker written by an older engine as prose,
an unreadable file: every one of those keeps today's behaviour. And only the marker half is ever
disarmed — a convergence gap is present tense, and no restart installs a skill still sitting on disk
uninstalled.

Verification. Scripts suite 3268 green, RAG suite 539 green, CI green on Linux, macOS and Windows.
A regression test replays the lived sequence through the hook's own entry function against a real brain
folder: marker armed, app restarted, no SessionStart, two prompts, silence. The field rehearsal
(CONVENTIONS §10ter) ran on copies of two real brains, one installed at v3.4.0 and one at
v5.1.1; both converged to v5.1.2 and both reported owner territory byte-identical.

Marketing surface re-read (CONVENTIONS §10). Nothing became false. One sentence became true: the
README's "it says so… until you close and reopen" was a half-truth until today, since coming back to
the same conversation never stopped it. No board copy affected, no re-render.

Issues (CONVENTIONS §10bis). Nothing closed by this tag, deliberately. #90
is the defect fixed here and stays open until the fix has run on a real owner's brain — its evidence is
field evidence, and a rehearsal brain, however faithful, is not the field. #92
(shipped in v5.1.1) stays open for the same reason. Every other open issue was read and none is covered
by this release.

Engine versions. scripts 1.16.0 → 1.17.0. The search engine and local-mirror are byte-identical
to v5.1.1 and their versions do not move.

v5.1.1 — The One Where Your Brain Stops Running Builds

Choose a tag to compare

@tpierrain tpierrain released this 07 Sep 20:25
c10e4b5

What this release is about

Every time you saved a note, your second brain started a software build in your own GitHub account. This release stops it, and removes the cause from brains that already have it.

If you gave your brain a private repository — for a backup, or so your notes follow you from one computer to another — it saves and pushes your notes as you work. That part was working exactly as intended.

What was not intended: every brain was also carrying the automated checks that the Kenjaku launcher runs on its own code. They were sitting in a folder the install should never have copied, and nobody noticed, because nothing in the product ever mentions builds. GitHub, for its part, took them at face value: each push started a full build on several machines, and each one failed — there is nothing to build in a folder of notes. On a real brain that meant roughly nineteen builds a day, against the free allowance of the owner's own account, and a failure mail each time.

This was an oversight, not a trade-off: no version of Kenjaku ever meant a brain to run anything. A brain stores and syncs your notes. It never builds.

Were you affected? Every brain created by v3.1.0 through v5.1.0 has the two files on disk — they have been travelling since 17 June 2026. But they only do anything if your brain also has a repository on GitHub and pushes to it (git config secondbrain.autopush true, which is what the backup and shared-brain setups ask for). A brain with no remote was never affected, and neither were your notes, in any case. You can check in one look: open your brain's repository on GitHub and click Actions — if there is a list of failed runs there, this release is for you, and one update makes it stop.

What you get

When you update your brain — ask it to update (/update-engine); a few minutes, and your notes are not touched.

  • 🏗️ The builds stop, and so does the failure mail. The two files that caused them are removed from your brain, and GitHub has nothing left to run.
  • 🧾 Your brain says so, in plain words, at the moment it does it. Not "2 files removed": that the builds have stopped, that the mail stops, and that the cost stops with it.
  • 🔒 A workflow you wrote yourself is never touched. Only the two files the launcher has ever shipped are removed, by name — and because your brain is a git repository, the removal is in your own history, readable and reversible.

When you create a new brain — nothing to do, it is true from this version on.

  • ✨ A new brain has never had any of this. The launcher's own machinery no longer travels into the brains it generates.

The rest of the time — the moment you set up a backup, or invite someone into a shared brain.

  • 📖 The documentation now says what your repository does, and what it does not. One sentence where you wire up a remote, and one where a second person joins: it stores and syncs, it never builds — so if you ever see builds running there again, you know it is a defect and not something you did.

What you have to do

Open your second brain and ask it to update. It does the rest, and tells you what changed.


Under the hood

Written so it can be read without knowing the codebase: each technical name is explained the first time it appears.

The cause. Kenjaku is itself a software project, and like any software project it keeps its own automated checks in a folder called .github/ — the tests that run on our code every time we change it. When the installer builds your brain, it copies the launcher's files across and drops the ones that are only useful to us; that drop-list is DEV_ONLY_PREFIXES (in scripts/lib/tracked-files.mjs), and .github/ was simply never on it. So filterCopyable, the function that does the dropping, handed our two check files — ci.yml and mutation-nightly.yml — to every brain it generated.

From there, two things run on their own. A brain that has been given a remote repository and told to push (git config secondbrain.autopush true, exactly what SETUP §7 recommends for a backup) pushes once per turn, and ci.yml starts on every push — so every saved note launched the full set of checks, on several operating systems at once. mutation-nightly.yml, for its part, is on a timer and needed no push at all. Every run failed, because there is nothing in a folder of notes for those checks to build. Measured on a brain in the field: about 19 runs a day, on the order of 720 GitHub Actions minutes billed against a free monthly allowance of 2,000. Reported as #92, which stays open until a real brain is seen losing the files at an update — the report was field evidence, and so is its closure.

The half that fixes new brains. .github/ joins that drop-list, next to maintainers/. The test behind it asks git for the real list of files the launcher tracks, rather than a made-up list written by hand, so a check file added next year is caught by the same assertion without anyone remembering. And it first verifies the launcher really does have .github/ files — otherwise an empty list would look like a clean one and the test would pass while proving nothing.

The half that rescues brains already out there is a small module of its own, scripts/lib/workflow-retreat.mjs. Kenjaku already had a mechanism for removing something from a brain, but it could not be reused here: that one only deletes a file when it can prove it delivered that exact file itself, byte for byte, from what the brain recorded at install. No brain in the field recorded anything for these two files — they arrived by accident, outside every category the engine keeps track of. Sent through that door, every brain would have answered "I can't prove it, so I keep it", and this release would have announced a fix that removed nothing at all.

So the proof used here is a different, deliberately blunt one: the two file names the launcher has ever shipped, written out literally. No pattern matching, no list read from a config file, no value taken from anywhere — this door has no input, which is exactly what makes it safe enough to ship in a hurry. Anything outside .github/workflows/ is refused, and so is a path that starts there and then climbs back out towards your notes. It runs only during an update (never during the silent repair your brain does at session start, where a deletion would happen with nobody told about it), it is harmless to run twice, and it says nothing at all when there is nothing to remove — which will be every update from now on.

What the rehearsal on a real brain caught, and no unit test could. Before shipping, the release is rehearsed against a copy of a real brain (originals read-only, copy taken without its git history). Result: both files gone, the deletion safely inside the update's own commit, and not one byte of the owner's notes touched — and the report said nothing about it. The reason is subtle and worth writing down: the summary you read after an update is printed by the engine that ran the update, which is the old one, and the old one has never heard of this release's new sentence. By the next update there is nothing left to remove. The sentence would therefore have appeared never, and the rescue would have been a silent deletion of two files inside somebody's repository. Kenjaku already had one voice for exactly this situation (announceWhatTheOldRecapCannot), and the sentence now goes through it, with the wording kept in a single shared place so the two voices that say it cannot drift apart. Four tests, written first and seen failing; rehearsal re-run afterwards to watch the sentence appear.

Checks. The scripts/lib/** suite: 2679 tests, all passing. Field rehearsal: passed, twice. Marketing surface re-read (CONVENTIONS §10): nothing became false, nothing new to sell, no board re-rendered. The heavier checks (mutation testing, waiting on the full cross-platform matrix) were deliberately waived for this hotfix, on the argument that the change has nothing variable to explore — the deletion names two literal file paths and reads no input.

Knowingly still open. Kenjaku only ever mentions a new version at the start of a session, and quietly. Making that mention more insistent — so that someone who never notices it still ends up updating — was considered and not done here: it changes a line every owner reads every single day, and it rescues nobody tonight who is not already updating. It waits for a release with room to weigh it properly. The other open issues were not covered by this release and stay open.