No. 13 in the series

The Canon Is Firmware. The Thinking Learned to Search by Meaning.

or: How the Room's Memory Learned to Find the Note You Meant Instead of the Word You Typed

The Canon Is Firmware. The Thinking Learned to Search by Meaning.

You go looking for the note. The one where you told the system to stop letting the AI slide into motivational-speaker voice. You search “motivational speaker.” Nothing. You search “warm and fuzzy.” Nothing.

The note is there. You filed it under “register drift,” and the keyword index can only hand back the words you actually typed.

The AI has read every note in the room and still cannot find the one you mean.

That is not a small gap. The whole writing room runs on a memory the AI searches before it does anything else, and for two years that memory had exactly one way in. It could find what you named. It could not find what you meant. That is the half of retrieval nobody built, and this is the upgrade that builds it.

The Firmware and the Thinking

Two kinds of memory sit under the writing room, and they are not the same kind of thing.

The first is the canon. The cast of characters, the places they move through, the chapter records that track them: the fixed facts of the world, stored in a SQLite file the AI reads on every query and writes to never. That read-only posture is deliberate. canon.db is firmware. It gets reflashed only by me, through a separate Excel-to-database pipeline, the way you reflash a motherboard on purpose and never by accident. The running system reads it and never writes it.

The second is the thinking. Notes, constraints, the writing standards, the editorial record of every decision the room has made about itself. That layer is read-write working memory, and it is where the system reasons and accumulates. It also lives in SQLite, and it is still the source of truth for everything it holds.

Firmware does not change while the machine runs. The thinking changes constantly. Keep the two straight, because everything in this piece is an upgrade to the thinking. The firmware stays bolted down the entire time.

One Index Could Only Find What You Named

The thinking started with one way in: keyword search. SQLite ships a full-text engine called FTS5, which builds an inverted index, a lookup table from each word to every record that contains it. You hand it a word, it hands back every note carrying that word. Fast, exact, and completely literal.

Literal is the problem. FTS5 matches tokens, the actual character strings you typed. It has no idea that “register drift” and “motivational-speaker voice” are the same complaint written down on two different days. The note you want is filed under a phrase you no longer remember, and the keyword index has no concept of “close.” It has present, and it has absent.

For an exact identifier, a character name, a rare string, a tag, keyword search is exactly right and nothing beats it. For the note you remember by what it meant and not by what you called it, it returns nothing, and it returns nothing with total confidence. The miss is silent. The memory does not tell you it failed. It just hands you an empty result and lets you conclude the note was never written.

The Second Index Finds What You Meant

The fix is a second index that searches by meaning instead of spelling.

The mechanism is embeddings. An embedding model reads a piece of text and returns a long list of numbers, a vector, that encodes roughly what the text is about. Texts that mean similar things land near each other in that number space, even when they share no words. Simon Willison calls it “vibes-based search,” which is the most honest two-word description of the technique anyone has written.

This is old magic with new range. Back in 2013, word2vec, from Tomas Mikolov and colleagues at Google, showed that these vectors support arithmetic. Willison’s own demonstration: take the vector for “germany,” add “paris,” subtract “france,” and the closest vector is “berlin.” The version everyone quotes, king minus man plus woman lands on queen, is the one repeated in every talk on the subject; the germany-to-berlin one is the one I watched actually run. The point is the same either way. The geometry knows something about meaning.

Retrieval-augmented generation put that geometry to work. The 2020 paper that named RAG, from Patrick Lewis and colleagues at Facebook AI and UCL, combined a model’s trained-in “parametric” memory with a “non-parametric” index of documents reached by a neural retriever, and reported that the result “generate[s] more specific, diverse and factual language.” Point that retriever at your own corpus and the AI stops reaching for what it half-remembers from training and starts pulling what you actually wrote down.

Here is the same query run through both indexes, against the real memory corpus. Not a description of a demo. The demo.

I asked the memory for the room’s standing position that AI tools do not lower the quality bar, and I phrased the request so that not one of its content words appears in the note I was after. The keyword index returned nothing. The note never says “lower the quality bar.” It argues that output quality is a function of authorial standards, not model capability. Same idea, zero shared words, and FTS5 has no way to bridge the two.

The semantic index surfaced the standards cluster on the first try, similarity scores landing between 0.77 and 0.82. That range matters. In this corpus a genuine hit sits between roughly 0.72 and 0.82, and a query the memory has nothing real for tops out near 0.60. The gap between those bands is the whole game. It is how the system knows the difference between finding something and reaching, so it can say “I have nothing strong here” instead of serving you the closest weak guess with a straight face.

Neither index wins outright. Keyword nails the exact token and whiffs on the paraphrase. Semantic catches the paraphrase and can slide right past a precise literal string you needed matched character for character. The answer is not to pick one. It is to run both, merge the results, and judge. Hybrid retrieval, lexical and semantic together, each covering the other’s blind spot.

Why the Thinking Left SQLite, and the Canon Did Not

A vector index has to live somewhere, and where it had to live forced the next decision.

The searchable memory is moving onto a dedicated NAS, a small always-on box whose whole job is to hold the brain so more than one client can reach the same copy of it. The moment a database has to answer to more than one machine across a network, SQLite is out of its lane, and the SQLite project will tell you so itself.

It is unusually blunt about this. “SQLite does not compete with client/server databases. SQLite competes with fopen().” The project’s own “Appropriate Uses” checklist walks you straight to the answer: if the data is separated from the application by a network, choose a client/server engine. A single-file database is a brilliant way to open a local file and a poor way to share one over the wire. The NAS turned a local-file problem into a shared-file problem, and that is a different engine’s job.

So the thinking’s search layer moves to Postgres with the pgvector extension, which adds native vector similarity and an HNSW index, a graph structure that finds nearest neighbors in a huge vector space without comparing against every row. The embeddings are generated locally by an open model, nomic-embed-text, running on my own hardware, and exposed to the AI as one more retrieval tool sitting beside the FTS5 one.

Local is a deliberate choice, for three reasons. Privacy: a private manuscript’s canon and the editorial record around it never leave the house. Cost: no per-token billing on a memory you query thousands of times, once the hardware is paid for. And durability, which is Willison’s load-bearing point. OpenAI “shut down some of their older embeddings models,” which strands anyone who stored vectors from them, because the stored numbers only mean anything to the model that produced them. An open model on owned hardware removes that shutdown risk for a memory meant to outlive any one vendor.

One thing did not move. The canon stayed SQLite, stayed local, stayed firmware. The thinking’s own source of truth is still SQLite too; Postgres plus pgvector is a search replica layered over it, not a replacement for it. Nothing about the world’s facts migrated to a new engine. The engine change is entirely on the search path for the thinking, which is the only layer that needed to answer to the network.

The honest status: this is an upgrade mid-build. The vector layer is live and answering queries today. The NAS, the full structured cutover, and a fused ranker that blends keyword and semantic scores into a single ordered result are still ahead of me. I am not going to hand you a stopwatch number, because the felt speedup so far came from an unrelated fix and the real engine move is not finished. What I can claim is capability and headroom. The room can now do something it could not do last month, and the architecture has room left to grow into.

The obvious objection comes from Willison himself: you may not need a whole new database for this, because SQLite has sqlite-vss and Postgres has pgvector. He is right, on one machine, which is his context. sqlite-vss would have handed me vectors and left me with a single-file database I still could not safely share across the network. The new engine is not for the vectors. It is for the network. The vectors were always the easy part.

Two Agents, One Showrunner

Building this took two AIs and a referee, and the reason is a bug.

The showrunner setup: a CoWork instance runs as project manager and architect, holding the plan and the standards, and a Claude Code instance runs as the implementer that writes the actual bytes to disk. Why two? Because a community-filed issue on Anthropic’s public Claude Code tracker, #53940, reports the CoWork edit tools silently truncating files past a buffer cap. I am not telling you Anthropic documented it or signed off on it. I am telling you someone filed it where anyone can read it, and I have watched the same thing chew the tail off a file in my own working tree.

The specialist agent exists because the project-manager agent keeps dropping the last paragraph on the floor.

The mitigation is a ladder. The everyday rung is written into the project’s CLAUDE.md as a standing rule: Python or a bash heredoc is the default for every file write and edit, because both have a clean record where the managed edit tools do not. The heavy-duty rung is the division of labor itself, routing byte-critical implementation to the Code instance that does not share the failure mode. And the self-demonstration, since this is a publication that practices what it documents: every edit in the prep for this article was written through Python, for exactly the reason above.

The Decision Rule, Ready to Lift

Here is the whole thing as a working artifact. The decision rule for which index to reach for, and the four layers the memory now runs on. Drop it in your own notes.

WHEN TO REACH FOR WHICH INDEX

Keyword (FTS5)    -> you know the exact term: an ID, a name, a rare string, a
                     tag, a code. Or you need every record containing a token.
Semantic (vector) -> you remember the meaning but not the wording; you want
                     conceptually related items phrased differently than you ask.
Both (hybrid)     -> it matters that nothing relevant is missed: the lexical
                     index catches the exact token, the vector index catches
                     the paraphrase. Run both, merge, judge.

Confidence floor: treat vector similarity below ~0.62 (this corpus's observed
no-match ceiling) as "the memory probably has nothing strong here," and say so
rather than serving the closest weak hit.

THE FOUR LAYERS

Firmware    canon.db (SQLite, read-only, owner-reflashed via Excel export).
            The AI reads every character and location; it writes none.
Thinking    notes / constraints / standards / editorial records
  source    (SQLite, read-write working memory). Still the source of truth.
Thinking    Postgres + pgvector, embeddings via local Ollama
  brain     (nomic-embed-text), exposed as one MCP tool beside the FTS5 tool.
Hardware    a dedicated always-on NAS hosts the thinking so more than one
            client can reach it; the firmware stays local.

The peer tools solve the adjacent problem a different way, and the difference is the retrieval axis. NovelCrafter’s Codex pulls structured story-bible entries into context by detecting when you mention a name or a defined alias, then injects the matching record. Sudowrite’s Story Bible works a similar lane, surfacing cards when they are referenced directly or through defined other-names. Both are mention-driven, and mention-driven retrieval is genuinely useful: name the thing and the right record shows up. It is also the exact case where keyword search already wins. What neither published workflow describes is semantic recall over the same notes, the case where you do not name the thing because you no longer remember what you named it.

That is the gap the second index fills. Not a knock on the tools. A different axis.

The note about the motivational speaker is still filed under “register drift.” Nothing moved it, and nothing needed to. What changed is that the room can now find it by what it means, reach it in words the note never used, while the canon underneath stays exactly as fixed as firmware should be.

It could already find what you named. Now it can find what you meant.


You may also like: - Your Docs Are for You. Your Database Is for the AI. - Synthesis Layer, Why I Didn’t Build a Wiki - Bringing It All Together

All entries

All writing on this site contains elements of both human and AI produced material. This author uses all resources at his disposal.