What it is
Every bug diagnosed in a session leaves a typed record, written by the agent that hit it. A pipeline clusters those records by shared mechanism, distils each cluster into one lesson, and serves the survivors to a coding agent over MCP. Agents then report back what a lesson did, and that moves its ranking. The site is that distilled layer. Its recall page runs the real index in your browser.
Why I built it
I started the habit: write down every bug you diagnose. After a thousand-odd records the archive had gone write-only, and I kept hitting problems it already had the answer to.
Writing down every bug I diagnosed is part of how I work. After roughly a thousand records, most of them out of professional work, the archive had gone write-only. Comprehensive, never read, while the same class of mistake kept recurring. Handing the pile to a coding agent doesn't fix it either: agents already have repository search, git history and long context, and none of that lets them open a million words to find out they have hit this bug before. The gap is the knowledge nobody writes into a repository. We tried that before. That looks correct but breaks on native. That fix caused a regression last month. So the records stopped being the product and became a compiler's input. The agents write them now, and report back what each lesson did — which leaves the question I actually care about: what stops a corpus that writes itself from filling up with confident nonsense? I don't have a complete answer. The gates below are what I have so far.
In the app
Captured from the public site, which is the distilled layer only — every source record behind it stays on my machine. The client-work projects appear in these frames as counts with their names masked, which is the whole demonstration.





By the numbers
- source records compiled
- 1,111
- distilled lessons
- 105
- tokens an agent carries per session
- ~3.1K
- lines of pipeline, tooling and viewer
- 25,317
Counted against the source on 20 August 2026, not estimated.
Decisions that mattered
11 of them. Each opens onto what I did and the reasoning behind it.
The source records are never loaded — only what compiles out of them
What I did, and why
A deterministic pass clusters records by shared error signature, symbol or vocabulary. Each cluster is rewritten as one lesson stated from the mechanism, not the incident, and the cluster's size becomes its recurrence count. The raw records stay on disk as an audit trail, reachable only from the lesson they produced.
When the reader has a context window, storing more and searching better is the wrong axis. What counts is the ratio between what you know and what it costs to consult, and only a build step moves it. Rewriting from the mechanism is what lets a lesson transfer to a codebase that shares nothing with the one that produced it. The recurrence count does something narrower. It tells you whether the failure mode is real or whether you just had one bad afternoon.
BM25 over the distillate, and no embedding model
What I did, and why
Retrieval is keyword ranking over the lesson layer, with no vector store and no embedding step anywhere in the pipeline. The vocabulary gap embeddings would close is closed at query time instead: the recall tool accepts several phrasings of the same task and fuses their rankings.
Every lesson has to declare the situation in which it should resurface, so half the retrieval problem is solved at write time by the thing best placed to solve it. Semantic search exists to recover intent from text that was not written to be found; text that was written to be found does not need it. Skipping it removes a model, a service and an index that can go stale. The caller is itself a language model, so alternate phrasings are free, and rank fusion over them recovers most of what a vector store would have bought.
Client work is publishable as scale, never as content
What I did, and why
Records from employer or client work carry visibility: private. The public build reduces each to a stub before it can reach a page: opaque id, a title rebuilt from schema fields, no body, no tags, no links. Only the character count survives, and two denylist tiers enforce that at build time.
Everything identifying is dropped instead of masked. A mask claims you enumerated all the identifiers, and free-text tags had already leaked a person's name once. Keeping the counts is deliberate: the site can say how much professional work sits behind the lessons without any of it being readable. The denylist catches names, not context, so it gates the build and does not replace a human read.
A build-time preflight, because a bundler almost carried the corpus into a deploy
What I did, and why
The check that the corpus exists was moved out of the content layer into a preflight script that runs before the build. It also fails when the corpus is present but empty.
A filesystem call on a computed path at module scope defeats static analysis, so Turbopack conservatively traced the whole repository into the server output, and this repository contains the private client records. Nothing was actually bundled; the trace manifests came back with zero markdown. But the failure mode is silent and the blast radius is client confidentiality, so it does not get to stay by luck. The empty-corpus case is subtler: the build would otherwise succeed and publish a site whose every number is zero, looking intentional.
The system's own running cost is a failing test, not a promise
What I did, and why
The two files that load into every session — the bootstrap every adopting project imports, and the kernel served once per session — have token ceilings enforced in the verify suite. Exceeding one fails the build. Raising a ceiling is allowed, but only as a diff in that script.
The whole claim is that a million words of experience cost about five thousand tokens a session, and only half of it was enforced: the kernel had a cap, the bootstrap file did not, and it drifted from roughly 800 to 1,900 tokens across three rewrites without anyone deciding that. A knowledge system that is expensive to consult is worse than none, because it taxes every task whether or not it helps. A number stated in prose decays; the same number in a check that fails holds.
Retrieval quality is a benchmark, because ranking cannot be eyeballed
What I did, and why
Task-phrased queries with known-correct answers run in the verify suite, and the build fails below an 80% top-three hit rate. The queries deliberately avoid the vocabulary the lessons declare as their own triggers. A second set of clearly off-domain queries (Kubernetes, Postgres tuning, payroll) is scored the same way and must return nothing.
A tokenizer tweak that fixes the query in front of you silently breaks two you are not looking at, and the failure surfaces later as an agent giving a confident wrong answer with no sign that retrieval caused it. It earned itself immediately: the baseline was 9 of 12, and the fix was a change to word-stem folding that would have been indistinguishable from a regression without a score. The negative set was added after measuring the false-positive rate and finding it was not zero — a coverage score counting query words present anywhere in the corpus let an off-domain query reach 'strong confidence' on one coincidental word. Matching a lesson by the words it chose for itself proves nothing, which is why the positive queries avoid them.
The agents write the knowledge base, so nothing takes an agent's word for its own work
What I did, and why
Agents write the records, distil them into lessons and report what each lesson did. Every input to the ranking function is therefore something the writing agent cannot assert: how many independent records shared a mechanism, whether the claim names a form you can grep for, and what happened when it was used. The one self-graded field — a lesson rating its own confidence high, which multiplied its rank by up to two and a half — is now earned rather than claimed, capped at write time and audited again over every file already on disk.
A knowledge base that writes itself is only worth having if it cannot flatter itself, and a self-assessed confidence score is exactly the input an agent will always max out: write high on everything and you outrank a careful writer permanently, with nothing noticing. Measuring first mattered. 45 lessons claimed high against 17 medium, but only 4 of 77 claimed it off a single record with no checkable form, so the fix caps those instead of refusing them — refusing loses real lessons to solve a five percent problem. What earns high is not certainty, since an agent is always certain, but evidence that the mechanism transfers: it recurred across independent records, or it names something you can grep for. Two implementations on purpose, because a write-time gate never sees the files that already exist.
Knowledge does not expire on a timer
What I did, and why
Nothing decays a lesson by age. A staleness mechanism that demoted old lessons with no recent use was proposed and rejected the same day it was designed. Rot is detected from reported harm instead: a lesson that misled a session weighs twice as heavily against as one that helped weighs for.
Age cannot tell the two kinds of old apart. A lesson bound to something the world can change — a version, a vendor, an API shape — can quietly become false. A lesson that names a mechanism does not expire at all, and those are the most valuable things in the corpus. Decaying by clock hits the durable ones hardest, since they are precisely the ones that have sat longest without being touched. A stale lesson nobody retrieves costs one line in a capped budget; one that is retrieved and wrong gets reported the first time it misleads, which is also the first moment anyone could honestly know it was stale.
Browsing does not count as a recall
What I did, and why
Agents report back what a retrieved lesson actually did, and lessons that are retrieved but never used sink while ones that changed an outcome rise. Queries typed by a human at the CLI are excluded from those counters.
Feedback is the only thing separating this from a pile that just gets bigger. Without it, ranking is frozen at whatever the distiller guessed on day one, and a lesson that has misled three sessions keeps outranking one that quietly saved ten. Which is why the signal has to stay clean. Me poking around the corpus is not evidence a lesson was useful, and letting it inflate the counters would corrupt the only measurement the ranking has.
The site runs the retrieval engine instead of describing it
What I did, and why
The recall page ships the real BM25 index to the browser and ranks a visitor's query with the same modules the MCP server imports, copied verbatim by a generated-file step that the build checks for staleness. Nothing about the ranking is reproduced in the site's own code.
Everything else on a project site is an assertion that the thing works; a console lets someone try to break it. The copy exists only because the bundler will not resolve outside the project root, and widening that root would pull the private corpus into its scope. So the copy is generated and staleness-checked, since a hand-maintained second copy of a scoring function agrees on the day it is written and drifts silently afterwards, both sides still returning plausible numbers.
A new publishing surface does not inherit the old surface's filters
What I did, and why
Shipping the recall index to the browser would have served three client-derived lessons in full, plus their slugs as keys in the feedback map. Both are now filtered at the single layer that owns the publishing boundary, with the index's postings remapped and its length normaliser recomputed, not carried over.
The redaction rules had been correct for two years of pages and none of that transferred to a JSON payload, because the index was built for a trusted local consumer and only became published when a new page decided to send it. The feedback map was the subtler half: it is a bag of integers, and it does not look like content until you notice its keys are paths and a path names a client. Found by grepping the build output for the private identifiers, not by reasoning about the code.
Stack
- Node (ESM CLI pipeline)
- Model Context Protocol
- BM25 ranking
- Next.js 16 (App Router)
- React 19
- TypeScript (strict)
- Tailwind v4
- Vercel
- Model Context Protocolan MCP server exposing ten tools an agent consults per task
- Information retrievalBM25 ranking over the distilled layer, no embeddings
- Pipeline designcluster → distil → rank → recall → feedback, as CLI stages
- Next.js 16static render over a filesystem corpus; the recall page runs the ranking engine client-side
What is honest about it
- The corpus took months; the compiler was built in a fast, concentrated push. The lessons are real and hard-won, but the system that distils them has no track record yet.
- For most of one day the retrieval tools were installed and nothing was calling them: the file an adopting project loads still described the pre-rewrite design, so agents were told to grep records they are no longer allowed to read. Setup looked identical to working. Found by using it on real work, not by reading it.
- The feedback loop has its first real data through it, and outcome counters now decide which lessons earn space in the always-loaded kernel. What it lacks is longitudinal proof: I can show the mechanism promoting lessons, not yet that it promoted the right ones.
- Its own README drifted from the live site's figures within a day, and later the same drift turned up in the two files every agent loads at session start — where it misleads the system instead of a reader. Eighteen corpus figures across those three files, plus the benchmark result, are now gated in the verify suite, each verified by planting a wrong value. The prose around them is still unguarded: a check can tell you a count is wrong, not that a sentence has stopped being true. This project page is downstream of exactly that gap — it was thirteen lessons behind the live site until it was recounted by hand.
- The verify suite covers retrieval quality, standing token cost, frontmatter and the confidentiality gate. There is no unit-test suite over the distillation stages themselves, which for a build step an agent trusts is the gap I would close next.
- The before/after demonstration on the site is a simulation; no agent is driven by it. The knowledge in it is not: the failure shown is recorded in the corpus five times, and the retrieval beside it is a real query against the real index, run at build.
- The repository stays private permanently, because the source records live in it. The distilled layer is public. That split is the product itself; it isn't a gap in the demo I plan to close one day.
Want this kind of work on your codebase? Email me and tell me what you’re trying to build.