“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.