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?) and close_werbos_chat_tool(sessionId). Stdlib urllib, no new deps. Pass the returned sessionId to 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 in 38208a1)
  • werbos.games.speedcode — second protocol. Same coding spec → both sides write Python → judge with pytest in isolated tempdirs. Smoke test on valid_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

MetricValue
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:8b66.7% optimal, mean regret 0.333
Per-game time (Ollama, M1 8GB)~22s
Bridge parse failures across all runs0
Bridge illegal-move retriesself-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>.jsonl and 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)

MetricValue
Local Claude Code corpus on disk958 MB
Projects36
Sessions (.jsonl files)2,341
Total events (lines)~148,634
One CryptoBlocks session: tool_use blocks674
Same session: Bash / Edit / Read / Grep tool calls193 / 182 / 132 / 127
Smoke import (CryptoBlocks only)11 sessions, 7.3 MB, 0 failures
Tool-call markers in one imported session687 <tool_use> + 670 <tool_result>
Hugo site main.css size8 KB
Hugo site full build time34 ms
Total Day 1 commits to werbos repo4 (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 code loops. 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>.jsonl layout) 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 public pk_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 ttt CLI. Thin wrapper on games.ttt_bench.run_bench() so the post-training comparison is a one-liner. (included in b663870)

Numbers worth keeping (Part III)

MetricValue
Sessions written (after bug fix)1,277 / 1,277 (0 failures)
Corpus on disk after import210 MB PFM (from 958 MB raw .jsonl)
SFT JSONL rows1,277
SFT JSONL size167 MB
Real secrets found in scan0 (60 false positives, all docs / public keys)
Adapter basemlx-community/Llama-3.2-3B-Instruct-4bit
Training iterations target2,000 (~1.5 epochs)
Per-iter wall time on M1 8GB~21s
Peak training memory4.59 GB (well under 8 GB ceiling)
Total Day 1 commits to werbos repo5 (38208a1, 2e00bd4, ecb0ef6, 9a536b1, b663870)

Val loss progression so far

IterVal lossΔ from baselineAvg P(correct token)
1 (baseline)1.605—20%
2501.178-26.5%31%
5001.132-29.4%32%
7500.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 &asymp; 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.