Day 67 — 'AAAA at breakfast, my own model live in the dashboard by midnight.'
A full arc in one day: hardened the deploy path so 'committed' proves 'live', then built the seek-compute-cache loop that lets the brain learn to seek and cache forever. Trained an owned conversation model from scratch on the GPU — from AAAA babble to 96% held-out small talk — and measured the size crossover: on thin data bigger overfits, but feed it 3x the corpus and capacity becomes the lever (5M collapses to 46%, 30M climbs to 79%). Then the honest one: built a JS+TS library and a from-scratch reader, and the reader hit a hard comprehension wall at chance — confirmed by a TF-IDF baseline also at chance, so it's the task, not the data. Not a failure: the thesis, proven. Capped the day by installing CPU torch on the homelab and wiring the owned weights live into the dashboard's Custom-model toggle — greetings now answered by my own model, on my own hardware.
“AAAA at breakfast, my own model live in the dashboard by midnight — with an honest wall in between.”
One day, one arc, from deploy plumbing to an owned model answering in the browser. The through-line was the one that matters: make the model earn every claim, and don’t dress up the walls.
First, make “committed” prove “live”
The day opened on reliability. A silent gh auth expiry had once let the homelab drift 138 commits
behind while every local commit looked green — so “committed” had quietly stopped meaning “deployed.”
Three verbs closed that gap: werbos deploy status compares the running container’s actual
image against source, deploy watch is an edge-triggered drift alarm on SR8’s timer, and
deploy sync is a one-command idempotent redeploy. Two honest bug-fixes fell out of using it:
staleness now reads the live container image (not the newest tag it could run), and build/recreate
got the long timeout they need instead of the 20-second probe cap. Retired two dead nav links
(Mythos, the Forest POCs) on the way past.
The loop that learns to seek
Then the real spine: resolve() — the seek-compute-cache loop. A query walks LOOKUP → SEEK →
COMPUTE → VERIFY → CACHE, with three gates keeping it honest (trust the lookup, compute-or-abstain,
cache only after verify). seek.py wires SEEK to the real web (fail-closed: no net → empty → abstain,
never a fabricated primitive). cache.py persists what’s verified, so the brain learns forever —
learning means writing verified compute to the library, not the weights. read.py adds model-backed
compute+verify seams and an intent-normalize pass that turns metaphors and indirect asks into literal
queries before the seek. And werbos ab puts one question to every phase side by side — open-weight
vs. owned — so you can see where each stands.
From AAAA to a real conversation model
The morning’s mint had babbled AAAA. Tonight the owned talk model went from that to 96% held-out
small talk — a 5M from-scratch PyTorch decoder, trained on the GPU. The fix that mattered was
teaching it to stop (a learned trailing newline); without it the model never halts and runs on. A
medium-talk tier followed (i like fishing → what do you like about fishing?), the follow-up
form learned by the model, the subject carried from the input.
The crossover — size is dead weight until it isn’t
Does medium talk need a bigger model? The GPU size-sweep gave a clean, thesis-confirming answer, then a twist. On the thin 770-pair corpus: 5M=70%, 15M=71%, 30M=67% — bigger overfit. So I teacher-expanded the corpus 3× (~200 subjects, varied openers) and re-ran. The ranking flipped: 5M collapsed to 46% (too small to hold the variety), 15M=73%, 30M=79% — best, still climbing. So “bigger is dead weight” was regime-specific: thin data punishes capacity, rich data rewards it. The size-sweep is now the instrument that tells you which regime you’re in — and the rule it earns is no bigger model without a sweep showing the small one saturating while the big one climbs.
The honest wall
Then the reader — the part the whole owned-model plan rests on. Built a real JS+TS library (650 curated concept cards: docs, runnable code, plausible distractors) and derived pick-a-passage reading fences from it. Trained a from-scratch reader on the GPU. It hit chance — 30–34% across 5M/15M/30M, loss 0.000 (perfect memorization), bigger worse. More data didn’t move it (272 → 650 fences: no change). The diagnostic that settled it: a pure TF-IDF baseline is also at chance. No lexical shortcut exists — these are pure-comprehension fences, and a tiny model, generative or lexical, can’t crack them. That’s not a failure. It’s the thesis, measured on a fresh domain: knowledge lives in the library, the reader stays a small cursor. (And the honest catch for next time: I built the hardest reader task — abstractive comprehension — when the reader’s real job is extractive, pick the retrieved passage that literally contains the answer.)
Owned weights, live in the dashboard
The cap: made the owned model run where it lives. Installed CPU torch on the homelab (it was
ollama-only), added torch_talk to load the byte-level weights on CPU, and wired a werbos-cli
reader provider — 100% owned, no Claude, no Ollama — into the dashboard’s Custom-model toggle.
Both conversational tiers now answer from owned weights, falling back to the curated lookup only if
torch can’t load, and never routing a real question at the talk model (it would babble). Restarted the
werbos-ab service, verified end-to-end through nginx: click Custom model, say hi, and the reply
is my model, on my hardware, ~600ms on CPU. The wobbles (dogstabog, coffee cooffee) are the
honest fingerprint of a real 30M byte-level model — not a canned line.
The AHA of the day: the reader hitting a wall at chance was the most valuable result I got — a TF-IDF baseline at the same chance proved it’s the task, not the model, and that’s exactly why werbos puts the knowledge in the library and keeps the model a cursor. The win wasn’t beating the wall. It was measuring it honestly, and shipping the part that does work — an owned model answering in the browser — the same night.