“fail, then grow.”

Yesterday the forest learned the JS/Node API and took its first loss. Today the loop that learns from losses got built — and closed.


Ship the win into the real path

The B.4/B.5 retrieval win was trapped in the benchmarks: forest.mjs’s actual fallback coder still generated blind. Fixed. The production coder now consults the API forest — live, “generate a uuid” emits crypto.randomUUID (built-in) instead of reaching for the uuid package. The proof left the lab.

While wiring it, a real gate bug surfaced: “make a for loop that logs test 10 times” injected eight junk signatures (RegExp.test, Math.LN10) — the exact B.5 noise-injection risk. The fix was a two-stage, principled gate: a top gate that asks “is there ANY confident answer?” (measured tops separate clean — a real API need peaks at ~0.70 cosine, a no-API request at ~0.52), and only then a relative cliff. Below the floor, the forest injects nothing and the model falls back to itself. The library learned to shut up when it isn’t sure — the same lesson the dream loop is teaching the mind side.

The spine: every result becomes an experience

The whole point of model + forest + graders + retry is that the result is training signal — but it was being printed and thrown away. Now logExperience() captures every graded run: the query, what the forest injected, attempts, pass/fail, the grader’s verdict, the final code. That JSONL is the thing both feedback channels read.

Two channels — and the forest one closes today

MODEL channel (the angler): passing runs distill into certified (prompt, code) pairs — verified-correct by an un-bribable grader — the training set for a model of our own. (The fine-tune is the next brick; the data is flowing now.)

FOREST channel: closes end-to-end, no GPU. It grows and it prunes, and — heeding the hardest-won lesson, self-improvement without a gate tears down — it only ever proposes:

  • crystallize — a run that passed while the forest stayed quiet, yet used a real corpus API, becomes a proposed alias (with evidence). The forest should have known that word; now it can.
  • garbage-collect — the counterweight. An unpruned library rots into the bloat the forest exists to avoid. Usage comes free from the experience log; inventory minus usage is the dead set. Propose-only, archive-not-delete (append-only revert), with a cold-start guard that refuses to act on sparse data — proven necessary the instant it ran: at four observations it flagged 48 perfectly-good aliases as “unused.” That guard is the difference between a library that breathes and one that eats itself.

Then I watched it grow

The demo I wanted to see. A real gap: “list all files recursively” mis-ranks fs.cp first (the word “recursively” pulls copy), burying the right answer fs/promises.readdir at #3. The loop caught it, crystallized the fix, re-embedded:

ROUND 1:  fs/promises.readdir  ranks #3   ❌ buried below the wrong fs.cp
CRYSTALLIZE → teach the forest, re-embed
ROUND 2:  fs/promises.readdir  ranks #1   ✅ displaced fs.cp — gap closed

AHA: fail → grow, on screen, deterministic. The forest got something wrong, the loop taught it the right word, and it’s permanently better — every future request for that now gets the right API. The library learned, and the learning is a reviewable one-line diff, not an inscrutable weight update. That legibility is the product.

Benchmarks held green through all of it: B.4 6/6, B.5 6/6 (both vs blind 5/6).

Afternoon: the mind grows a memory

The question was “how much context can we hold — can we get to Claude’s 1M?” The honest answer, measured on the actual 8 GB box: no. The model supports 128K, but context lives in the KV cache (~110 KB/token), and ~16K is the comfortable ceiling before it spills to CPU and crawls. A 1M window wants ~15 GB of RAM that doesn’t exist. You will not get the window locally.

But “1M memory” was the wrong target. The right one is recall, and that’s a retrieval problem — the thing the forest is built for. So we measured the write→find cycle and it’s almost free: writing a memory is ~25 microseconds (append a line), recalling it lexically is ~7 ms over 10,000 items with no model, and ~22 ms semantically. A recall from 10K memories costs the same as from 10. So werbos can remember by retrieving — effective recall unbounded, window small and fast.

AHA: you don’t match a 1M window, you match the behavior. Claude holds 1M tokens at linear RAM cost; werbos recalls from 10M tokens of history at constant window cost, because the library does the holding the GPU can’t.

So we built it. The WorkingMemory primitive — instant note(), fast lexical recall, lazy semantic (embeddings computed off the write path so a 25 µs write never becomes a 30 ms one). Then the part that made it sing: the mind portrait’s memory-retention spoke had always been a ghost — unmeasured. A recall-over- distance eval (plant facts among 200 distractors, recall via paraphrased queries) made it real: 0.88 lexical floor, and wiring the semantic arm caught the one pure-synonym miss → 1.00. A phantom limb became a scored axis, and the /mind sync rule we wrote this morning fired twice on its own to keep the page honest.

Then we switched it live. ChatSession now optionally syncs the conversation into a WorkingMemory each turn and recalls the turns relevant to the latest message back into the prompt — exactly at the seam where the ContextBuilder truncates old turns. So a fact that scrolled out of the window comes back when it matters. The test proves it literally: plant ZEBRA-9-TANGO, bury it under 60 turns, ask later → it returns. Wired into both werbos chat --recall and werbos serve --recall (the web chat too; forced off for Tom). Opt-in, best-effort, never takes down a turn — 1200+ tests across session/server/memory stayed green.

The honest frame: retention is the rare axis where a 3B beats its own mid-60s — 1.00 — because remembering, unlike reasoning, is something the library can simply do. Memory locally isn’t a speed problem. It’s a what-to-keep-and-retrieve problem — which is curation, which is the whole game.

Footnote: planning for claude -p

Anthropic paused the Agent-SDK / claude -p credit change — but we planned for it anyway. An audit found the blast radius is contained: only the grader/judge path shells out to claude -p; the runtime, dream loop, autopilot, and the whole MMM coding forest are already local. The contingency is the work we’re already doing — promote a local grader to default, keep Claude a slider not a switch, and lean into the local-grader/angler hedge that retires the borrowed judge entirely.

North star, sharpened

Said out loud today: build as hard as possible toward stable Opus-3-level coding, fully local. The honest reframe that makes it winnable — you don’t need a small model that is Opus 3.0; you need the system (model + forest + graders + retry loop) to produce Opus-3-quality outputs on tasks that can be checked. The grade-retry loop is the equalizer: a weaker generator reaches a stronger one’s results by iterating against an un-bribable grader. Reachable on verifiable coding; Claude stays the ceiling on the unverifiable. Same project as “remove the open weights,” from the quality angle instead of the ownership one.