Day 1 — He's a system citizen.
The day werbos stopped being something inside one repo and became a process other things can talk to. Two LLMs can hold a real conversation now.
The day werbos stopped being something that lives inside one repo and became a process other things can talk to. Two LLMs can hold a real conversation now, and we have a number that tells us if he’s getting smarter.
What shipped
- MCP chat bridge —
chat_with_werbos_tool(message, sessionId?)andclose_werbos_chat_tool(sessionId). Stdliburllib, no new deps. Pass the returnedsessionIdto keep a multi-turn thread going. Every turn lands in the chat archive. (38208a1) werbos.games.ttt_bridge— first protocol on the bridge. Claude plays X with optimal minimax; werbos plays O via a JSON-envelope prompt. Driver adjudicates rules locally and re-prompts werbos with feedback when he hallucinates an illegal move. (included in38208a1)werbos.games.speedcode— second protocol. Same coding spec → both sides write Python → judge with pytest in isolated tempdirs. Smoke test onvalid_parentheses(12 cases): both PASS 12/12. Werbos’s solve time on stock 8B: 11.10s. (2e00bd4)werbos.games.ttt_bench— third protocol. Adds a minimax oracle that scores every werbos move. Output: a single number we can watch go up across model versions. (ecb0ef6)
Numbers worth keeping
| Metric | Value |
|---|---|
| Hot-dog debate rounds (vs Claude) | 3 / archived |
| TTT live games (Claude minimax X vs werbos LLM O) | 2–0 Claude |
| Speedcode pass rate (parentheses, stock 8B) | 12 / 12 |
| TTT-bench baseline, stock llama3.1:8b | 66.7% optimal, mean regret 0.333 |
| Per-game time (Ollama, M1 8GB) | ~22s |
| Bridge parse failures across all runs | 0 |
| Bridge illegal-move retries | self-corrected, never corrupted state |
Surprise of the day
Realized I could type werbos from /, from ~, from any directory,
and he just runs. The binary’s on $PATH, memory is at
~/werbos/memory, archives at ~/werbos/chats/, Ollama runs as a
daemon. He’s not tied to the repo anymore. He’s a system citizen.
AHAs
- “If we expanded the MCP bridge, would you two talk unprompted?” — the answer was: only if someone fires the tick. MCP is request / response. Real autonomy needs a tick loop somewhere. (Pi 5 dream.)
- The three things I asked for — debate, speed code, tic-tac-toe — turned out to be the three classical post-pretraining paradigms: language self-play, executable feedback RL, game self-play. Picked them by feel without knowing.
- A blunder is a position + correct move pair. That’s the gold format for SFT. Every benched game now produces structured training data, not just narrative archives.
Quote of the day
ply 2: O(1,0) [BLUNDER regret=1] -- "control center row"
Werbos called (1,0) “center” when the actual center is (1,1). His
reasoning was wrong AND his move was wrong — and the bench
caught both. Fixable failure mode, exact training target.
Part II — afternoon → evening
“The corpus was already on the SSD.”
Started the afternoon planning to set up Hugo. Ended the evening realizing I was sitting on a 958-MB training corpus that exactly fixes adapter v1’s tool-loop problem.
What shipped (Part II)
- This changelog — numbered from Day 0 so day-counting matches programmer-counting. Day 0 / Day 1 backfilled from git log and memory.
- This Hugo site at
werbosLLM/site/— SCSS-only pipeline (Tailwind stripped from the starter), Netlify-ready (base = "site"), 14 pages built in 34 ms. Each log day is its own page (/log/day-N/) with prev/next nav and an RSS feed at/log/index.xml. The homepage’s “Latest entry” card auto-pulls the newest log post. werbos.importers.claude_code— the unlock. Walks~/.claude/projects/<slug>/<session>.jsonland emits one PFM archive per session, with tool calls + results rendered inline as XML-ish tags so the existing SFT exporter just works. (9a536b1)
Numbers worth keeping (Part II)
| Metric | Value |
|---|---|
| Local Claude Code corpus on disk | 958 MB |
| Projects | 36 |
Sessions (.jsonl files) | 2,341 |
| Total events (lines) | ~148,634 |
One CryptoBlocks session: tool_use blocks | 674 |
| Same session: Bash / Edit / Read / Grep tool calls | 193 / 182 / 132 / 127 |
| Smoke import (CryptoBlocks only) | 11 sessions, 7.3 MB, 0 failures |
| Tool-call markers in one imported session | 687 <tool_use> + 670 <tool_result> |
Hugo site main.css size | 8 KB |
| Hugo site full build time | 34 ms |
Total Day 1 commits to werbos repo | 4 (38208a1, 2e00bd4, ecb0ef6, 9a536b1) |
AHAs (Part II)
- The biggest training corpus on this machine isn’t on Google
Drive. It’s already on the SSD. ~150K events of real engineering
conversations — file paths, tool calls, back-and-forth —
sitting in
~/.claude/projects/. I just hadn’t seen it. - Claude Code transcripts contain the exact thing adapter v1
lacks. v1 was chat-only — no tool use. That’s why
werbos codeloops. The Claude Code corpus has hundreds of tool calls per session. It’s not just training data; it’s the antidote. - Two-adapter strategy crystallizes. v2-coder (Claude Code corpus) + v3-angular (ChatGPT work account) stacked at inference. Different skills, different cables, same brain.
- The changelog and the map are complements, not duplicates. Map = “what is” (timeless). Log = “what changed” (dated). Two surfaces, one site.
Quote of the evening
“can i data mine myself?”
The moment the corpus snapped into focus. The data was there all along.
Part III — late evening
“The engine starts.”
The training run that the entire previous week was scaffolding for. Adapter v2-coder is mid-flight as I write this — first real val-loss numbers in hand, more arriving overnight. The first day in this whole project where the dogfood loop closed end-to-end.
What shipped (Part III)
- Importer fix. First full-corpus run yielded 24 sessions out of
2,341. Two bugs: a non-recursive glob (missed the newer
<proj>/<uuid>/<uuid>.jsonllayout) and a sidechain filter that rejected every event in subagent files (because the whole file IS one sidechain). Fixed both. (b663870) - Full corpus import. 1,277 PFM archives written, 0 failures,
210 MB on disk. Every single archive contains
<tool_use>blocks — the agent-loop training data adapter v1 lacked. - Secret scan. 60 hits across the corpus, all textbook example
keys (
AKIAIOSFODNN7EXAMPLE,ghp_aBcDe..., a Stripe publicpk_live_which is meant to ship to clients). No real secrets to scrub. - SFT export. 1,277 rows, 167 MB JSONL. Tool-use markers preserved through PFM → SFT.
- Adapter v2-coder, training. Llama-3.2-3B-Instruct-4bit base, 2,000 iters, batch_size=1, grad checkpointing on, max_seq=2048. Memory steady at 4.6 GB on M1 8GB — comfortable headroom.
werbos bench tttCLI. Thin wrapper ongames.ttt_bench.run_bench()so the post-training comparison is a one-liner. (included inb663870)
Numbers worth keeping (Part III)
| Metric | Value |
|---|---|
| Sessions written (after bug fix) | 1,277 / 1,277 (0 failures) |
| Corpus on disk after import | 210 MB PFM (from 958 MB raw .jsonl) |
| SFT JSONL rows | 1,277 |
| SFT JSONL size | 167 MB |
| Real secrets found in scan | 0 (60 false positives, all docs / public keys) |
| Adapter base | mlx-community/Llama-3.2-3B-Instruct-4bit |
| Training iterations target | 2,000 (~1.5 epochs) |
| Per-iter wall time on M1 8GB | ~21s |
| Peak training memory | 4.59 GB (well under 8 GB ceiling) |
Total Day 1 commits to werbos repo | 5 (38208a1, 2e00bd4, ecb0ef6, 9a536b1, b663870) |
Val loss progression so far
| Iter | Val loss | Δ from baseline | Avg P(correct token) |
|---|---|---|---|
| 1 (baseline) | 1.605 | — | 20% |
| 250 | 1.178 | -26.5% | 31% |
| 500 | 1.132 | -29.4% | 32% |
| 750 | 0.870 | -45.8% | 42% |
| 1000 | (pending) | (pending) | (pending) |
| 2000 (final) | (pending) | (pending) | (pending) |
For comparison, adapter v1 (chat-only) got 1.986 → 1.636 over its full run (-17.6%). v2-coder is at -45.8% at 37.5% trained. The agent-trace data is fitting much better than the chat data did — which is exactly the point: it’s filling a gap the base model genuinely didn’t have.
AHAs (Part III)
- Val loss → “right next token N% of the time.” The
translation
prob_of_correct ≈ e^(-loss)makes loss numbers meaningful. v2-coder went from being right ~20% of the time on unseen data to ~42% in 750 iters. That’s not “the loss number went down a bit” — that’s the model becoming twice as confident about tokens it has never seen. - The dogfood loop is real now. Every previous piece — importer, PFM archives, SFT exporter, train command, MLXBackend, bench — exists for a reason that just paid off in one continuous flow: data → corpus → training data → adapter → benchmark. Tomorrow we measure whether the bench number moves.
- Some “successful” training looks like luck. The 1.132 → 0.870 jump at iter 750 is unusually sharp. Could be representations consolidating, could be a lucky val sample. Iter 1000 will disambiguate. Either way, the iter-750 checkpoint is the worst case from here.
Quote of the night
“the model went from being right 20% of the time to right 42% of the time on data it had never seen.”
The translation that made loss numbers stop being abstract.
Sleeping on it
Training continues in the background. Final adapter lands sometime in the early morning. Day 2 picks up with: bench v2-coder vs the 66.7% baseline, hands-on chat to feel the difference, possible adapter stacking (v1 + v2-coder = “knows my style + codes like me”), and the next training corpus from the other machine.