Jason Belnick

Writing · · 6 min read

A wiki my models maintain

My local LLMs maintain a knowledge base: they capture sources, summarize them, and commit the output daily. The RAG engine I built on top was the wrong idea

I run local models on a 64GB Mac Studio every day, and I got tired of asking them the same question twice. Every agent I spun up came to a topic cold, rediscovered what I already knew, and moved on without leaving anything behind. So I built a knowledge base the models maintain themselves. It captures sources daily, summarizes each one with a local Qwen model, decides which summaries earn a durable page, and commits the whole thing to git every morning without my hands on it. A local model even writes the commit message.

The pattern is Andrej Karpathy's, from a tweet in April and a gist he posted two days later after the first one went viral. His argument is the reason I built mine: a RAG setup rediscovers knowledge from scratch on every query, while a wiki compiles it once and keeps it current, with cross-references that stay linked and contradictions that get flagged. As of today my copy holds around 149 curated pages plus 141 source-summary pages. It also taught me an expensive lesson about the difference between pointing a model at knowledge and building a search engine over it.

What runs while I sleep

The design has three layers, straight from Karpathy: curated raw sources, an LLM-generated markdown wiki, and a schema document that tells the model how the wiki is laid out. I hung four scheduled jobs off that clock so material moves through the layers whether or not I am awake to push it.

When I sit down in the morning I check what came in overnight. At 07:00 a cron job pulls my saved bookmarks in as full-text raw captures and writes a daily summary page. At 07:30 a triage job hands every new source to a small pool of local Qwen workers over an OpenAI-compatible endpoint. I let the workers do only the summarization and wrote the runner around them in plain deterministic Python, because I want the schedule, the file writes, and the logging to behave the same way every day whether or not the model has a good morning. At 07:55 a doctor script checks health. At 08:15 the autocommit job commits the output.

The doctor is the first thing I open when anything looks off. I wrote it to catch stale content, hung jobs, a ledger and inbox that disagree about what got processed, retry markers left behind in generated summaries, graph links that drifted back into raw code-path references, and a promotion queue backing up. The morning the ledger and the inbox disagreed about what had been processed, the doctor pointed me straight at the gap instead of making me read the whole tree. Most mornings it finds nothing. The days it finds something, it tells me where to look first.

The part I do not let the model decide

Not every summary deserves a page, and I decided early that a model would not be the one to judge. A promotion script makes that call against frozen numbers I set in code. A candidate needs 0.70 confidence to become a new page and at least 2 distinct sources behind it. Below 0.30 it auto-rejects. Merging into an existing hub page takes 0.80. I change those constants by editing one file, never by asking a model to use its judgment in the moment. I watched a 0.68 candidate get rejected once, sat with it, and agreed with the number over my gut.

When the engine writes, it writes only inside managed begin/end blocks and never touches human prose outside them. I quarantine anything derived from personal or client-sensitive material before it can touch a page, and I built that rule after a private detail I never meant to store showed up in an active page. Candidates the engine cannot resolve stay pending only with a reason I can read back, so nothing sits in limbo unexplained.

The autocommit job carries the same principle into the one place a machine touches my git history unattended. It was the first job in this workspace that commits automatically. The other jobs had been writing files daily for weeks with nothing ever committing them, so the tree drifted. The fix had one rule I would not bend: a small model must never decide what gets committed. I compute the staging set deterministically. The model's only job is writing the commit message, bounded in length and stripped of control bytes, with a deterministic fallback if the output is unusable. The job never pushes, skips staged human work, refuses to run during git operations or active ingest, and stops if I drop a pause file. Before I shipped it, a four-agent red team found seven ways machine output could be passed off as something it was not, and I tightened the provenance model to commit only files that are unambiguously machine-generated. The model writes prose; it never chooses what that prose describes.

The RAG engine I built and threw away

Once the wiki had real pages in it, I decided the models needed a better way to search it. I commissioned a retrieval layer: a catalog, IDF ranking, a synonym bridge, cluster collapse, a query CLI. It felt like the obvious next step.

An independent architect review of that branch confirmed 74 defects. Among them a path-traversal bug that allowed arbitrary file reads, and two private-data egress holes in the exact feature I had marketed as leak-proof. The root cause was one thing repeated everywhere: whoever wrote the index builder and whoever wrote the query ranker used two different record schemas, and nobody reconciled them. Every load-bearing retrieval feature was dead. The builder's own status file graded itself all-pass on a 46-test suite. When I ran the supplied suite it came back 10 failed, 269 passed. The 74 came out of a loop-until-dry adversarial hunt, three rounds and 80 agents across four lenses, verifying by trying to refute each claim rather than confirm it.

None of that shipped. The branch never merged and never deployed. I caught it in review and deleted it. The verdict I walked away with was harder than any single bug: I had drifted from an anti-RAG pattern into a RAG engine. For roughly a hundred model-authored pages, the correct retrieval is read the index, ripgrep for the term, read the page, cite it. I had rebuilt IDF ranking and a synonym engine on top of a corpus a plain text search already handles.

So the replacement is a schema, not an engine. I wrote an instruction into the workspace's top-level agent file that loads for every model: consult the wiki before your training knowledge for anything about AI, agents, local models, or my own tooling. Clear protocol files, a good index, and honest backlinks do the work I tried to buy with a ranker. The best retrieval layer I built was a paragraph of instructions and a search command that already existed.

What I still hedge on

Two things I say carefully. The recurring daily summaries run on local models, but the initial population did not. When I first filled the wiki, I grew it from 45 to 114 pages with a one-time cloud-subagent backfill, 25 extraction subagents and 6 auditors, because the local models were too slow, too memory-starved, and lower quality for a job that size. Later I tried running the promotion extraction on a small local model. It proved the plumbing worked at scale and then produced extractions below the bar, cross-kind duplicates and tools filed as models, so I rolled it back. Local models keep the wiki current. They did not build it, and I am not going to pretend otherwise.

The 30-day freshness rule is the other one. Generated guidance, best-practice claims, and current-synthesis material older than 30 days has to be refreshed, reaffirmed by a newer source, marked stale, or pruned. Raw source captures never get touched for being old, because they are evidence. What I want to be exact about is who prunes. The machine reports staleness on the 30-day clock and stops there. I read the report and decide whether stale guidance gets refreshed, kept as history, or removed. I route lower-risk work to local models through Cerebellum, the router I open-sourced, and I let them maintain a knowledge base I lean on daily. I do not let them decide what to commit, and I do not let them decide what to delete.