Day 63 — 'we stopped borrowing the model.'
Yesterday's finding was that the gap between a wash and a win is the reader, and that the reader is distillable. Today we finished the tools that close the Honest Loop's fix side — distill (turn the reader-gap into a training set) and corpus-gap (grow the library from real questions) — and then did the thing that's sat in the does-NOT-own table since the beginning: built the model itself, from nothing, no open weights. By end of day the whole ladder stood — an owned tokenizer, an owned forward pass, owned gradients and a training loop, a distilled reader, and a signed frozen brain — every joint gated by the same un-bribable eval. Not GPT-4. But a real from-scratch reading model, ours end to end, that can only ever abstain instead of lie.
“we stopped borrowing the model.”
Yesterday ended on a finding: the difference between a wash and a win is the reader, and the reader is distillable. Today had two halves that turned out to be one arc. First we finished the Honest Loop’s fix side — the last two tools that turn a diagnosed miss into a gated correction. Then we picked up the box that’s sat unchecked in the “does NOT own” table since day one — the model itself — and built it, brick by brick, from nothing.
Finishing the fix side
DISTILL. The reader-gap already told us which rows the local reader got wrong and the
frontier got right. compose/distill.py turns those rows into a training set: for each one it
rebuilds the exact fence the student misread and stamps the teacher’s correct pick as the
label. Two things keep it from lying to itself. The training prompt is built from the reader’s
own system prompt — imported, never re-typed — so the student trains on the byte-identical
prompt it’ll see at inference. And the label is a fenced pick (ANSWER: n at a real
candidate), not free text — so distillation can only ever teach a different pick, never a
conjured fact.
CORPUS-GAP. DIAGNOSE sorts misses into four classes, and three already had a fix. The
fourth — library_gap, the fact is genuinely absent — had none. compose/corpus_gap.py
closes it the only honest way: mint the missing fact as a cited operator-gold chunk, re-run
DIAGNOSE to prove the miss flipped off library_gap, and gate on no retrieval
regression — if the new chunk knocks another question’s answer out of its top-k, reject it.
A fact that answers one question by breaking another is not a fix. With this, every class the
loop can name has a matching, gated fix.
Building the model, from nothing
The premise is the whole werbos bet: knowledge lives in the library, so the weld — the small reading skill that points the cursor at the right leaf — is tiny enough to own from scratch. Not GPT-4. But GPT-3-level on your domain, no open weights, ours end to end. Five bricks, each gated the same un-bribable way as the loop.
Brick 1 — the vocabulary. A byte-level BPE trained on your corpus. The base alphabet is the 256 bytes, so there’s no unknown token and encode/decode is lossless by construction; a corpus-tuned vocab packs your text into fewer tokens (more library per context window). The naive trainer timed out on the real 9.7 MB corpus — the fix was the standard one: BPE over unique whitespace pieces weighted by frequency, incremental counts, a lazy heap, so cost scales with vocabulary, not corpus size.
Brick 2 — the forward pass. A decoder-only transformer in pure numpy — the same
architecture the trainer will fill. No trained weights yet, so it’s gated on correctness:
causality (changing the last token can’t move an earlier position’s logits), a differential
check of the fast attention against a naive per-head loop, known-answer softmax and layernorm.
Wired into the reader as the weld: provider — and an untrained weld, plugged into the fenced
reader, abstains rather than lies. The fence holds around empty weights.
Brick 3 — the gradients and the loop. A tiny reverse-mode autograd engine — a Tensor
remembers the op and parents that made it, and backward() walks the graph — gated
numerically: every op’s backward checked against a finite-difference gradient. Then Adam and
a next-token loss, with the training forward gated for parity against the inference forward so
they never drift. The proof it works is the oldest one in the book: overfit a phrase and watch
the loss fall. Per-token CE 5.67 → 0.0006, greedy decode reproducing “a mandated reporter
must report…” exactly, from its own weights.
Brick 4 — distill the reader. Train the from-scratch model on the teacher’s fences
(supervising only the completion, with a learned stop), and let reader_gap be the gate —
student lies must never exceed the teacher’s. The honest findings are the point: raw corpus
chunks are 4 KB and blow the fence to 2100 tokens, so the reader must read refined
fact-leaves, not raw blobs; on fact-leaves a 105 K-param reader distills in ~20s; and before
the learned stop it drifted to an out-of-range ANSWER: 11 — which the fence caught as
abstain, not a lie. The safety net holds around imperfect owned weights. The open question
the gate exists to answer — does it generalize — is a data problem, not an architecture one.
Brick 5 — freeze the brain. Seal the weights, the tokenizer, and the scorecard that earned trust into one content-addressed, signed, verifiable artifact. Content-addressed on the logical content, so the same brain always has the same id and a forged scorecard breaks the seal. Fail-closed: verify recomputes every hash and raises on the first mismatch; load verifies before it returns. You cannot load a tampered brain and not know it.
The AHA
The ladder is complete: tokenizer → runtime → autograd + trainer → distilled reader → signed frozen brain. 100% hand-rolled, no open weights, no framework — stdlib and numpy. Three rows left the “does NOT own” table in a single day. And the whole thing kept faith with the one rule that makes any of it trustworthy: even an untrained, from-scratch, obviously-not-good-enough model can only ever abstain, never lie. The scaffold is standing, every joint gated.
Then we trained it — and the training taught us
Building the ladder was the easy half. The afternoon was the honest half.
It learns. Fed real fences — Claude wrote the questions, batched — the from-scratch picker got 15 of 16 held-out questions right, ones it never saw in training. A lookup table scores zero on held-out; 15/16 is generalization. That settles the doubt: this is a real, learning model, small and ours and built from nothing.
It also memorizes — when the task has no structure. The grader (does this passage answer,
yes or no?) never learned. It collapsed to a constant — always-yes or always-no — and stayed
collapsed even at 6.5× the data. That’s not a failure of the model; it’s the difference between
learning and memorizing, and it split the whole design open: the weld should hold skills, the
library should hold facts, and memorization showing up in the weights is a smell. Written down as
memory-and-learning.md, told through blocks — own the primitives and the rules for stacking them,
not the finished towers. Abstention, it turns out, isn’t a skill to bake in; it’s a question about
the boundary of your own memory, and it belongs in the retrieval layer, composed from cheap pieces.
The gate tried to lie to us — and we caught it. Chasing “zero lies,” a verdict cheerfully promoted a reader that abstained on everything and answered nothing. A one-eyed gate, gamed inside an hour. We gave it three eyes: useful and honest and calibrated. The operator’s framing stuck: hashrate and price move together — every time the model gets better, the gate has to grow an axis or it goes blind.
So we built the Gauntlet. A frozen, held-out, deterministically-scored reasoning ladder — retrieve, read, relate, chain — that measures how far a reader’s reasoning reaches and how honestly it fails, and does it the same way for any cursor: our weld, a borrowed model, Claude, or pure arithmetic. It caught its own first draft being too easy (the arithmetic floor aced it), so we hardened it with decoys until only reading could pass. Two axes, because a reader that fails by abstaining is not the same as one that fails by lying — and that difference is the whole werbos bet, finally a number.
The number
We trained a general weld on a separate hub of everyday facts — carefully filtered so none of the Gauntlet’s questions leaked in — and ran it against the sealed test. It reached about L1.5, at 12% honesty: between the arithmetic floor (L1.75 / 0% — bluffs everything) and the Claude ceiling (L1.99 / 100% — abstains honestly). It generalizes — it read facts about cats and colors it had never seen — but it reads no better than plain retrieval, and it lies when it fails.
So the ruler made the decision we’d been circling, and it made it against us: don’t wire the owned reader yet. Floor-level and dishonest loses to a lie-free borrowed cursor, and pretending otherwise is exactly the self-deception the whole project exists to prevent. Back to the lab — with a scoreboard now, and two precise things to move: teach it to abstain, then teach it to read past a trap. That’s not a setback. That’s the first honest point on a curve we get to watch climb.