The Map
A field guide to local LLMs as Jason understands them — written from the inside of the conceptual unlocks, not the textbook outside.
The map answers the what is questions: training, adapters, hardware, the four operations, the off-grid economic argument. It’s the companion to the log, which answers what just changed.
Big picture (one diagram)
+--------------------------------------------------------------+
| BASE MODEL |
| (e.g. Llama-3.2-3B-Instruct, weights frozen) |
+--------------------------------------------------------------+
| inference time
v
+--------------------------------------------------------------+
| +ADAPTER 1 (~30 MB LoRA delta, your data, your style) |
| +ADAPTER 2 (optional, stack as many as you like) |
+--------------------------------------------------------------+
|
v
+--------------------------------------------------------------+
| YOUR HARDWARE: M1 (dev/train, 3B max), |
| SR8 Ryzen (inference, 13B+), Pi 5 (always-on orchestrator) |
+--------------------------------------------------------------+
If you only memorize one diagram, that’s it. The brain is the base. The skills are the adapters. You stack them at inference. Like Neo plugging in “I know kung fu.”
The four operations
| Operation | What it does | Capacity change |
|---|---|---|
| TRAINING | Nudges weight values via gradient descent | Same number of slots, better-tuned values |
| MERGING | Averages two models’ weights cell-by-cell | Same size; new behavior is a blend |
| CONCATENATION | Adds new layers (Frankenmerge / depth-up) | Bigger model, more slots |
| DISTILLATION | Trains a smaller model to mimic a bigger one | Smaller model, ~most behavior |
Merging averages, doesn’t add. Merge a 10B + 10B and you get 10B, not 20B. The way to grow capacity is concatenation.
The five doors on one corpus (Day 105–106)
375 libraries and 167,493 graded cards are one artifact. Everything else is an access pattern over it — which is the whole “the library is the product” claim, made concrete:
| Door | Question it answers | Where |
|---|---|---|
| ANSWER | “what does abort() do?” → the one best card, cited | compose/library_lookup.py |
| READ | “what do we know about Spanish?” → every card, grouped | /knowledge · compose/knowledge_hub.py |
| TEACH | “teach me Spanish” → a syllabus, a sitting, a Leitner schedule | /teacher · compose/course.py |
| EXPORT | “put it in our LMS” → a SCORM 1.2 zip | werbos scorm export · compose/scorm.py |
| VERIFY | “does anything back what was just said?” → GROUNDED / UNSUPPORTED / UNCHECKED | personas/truth.py (Day 106) |
VERIFY is the door that points inward. The other four answer a person; this one judges a
reply, and it is the verdict a model grading its own output structurally cannot produce —
a 3B judging itself has no corpus to check against, so its verdict is an opinion about an
opinion. This one consults evidence, which is what makes it un-bribable. It convicts only inside
a scoped world (a capsule naming its world_library): in an open corpus absence means
nothing, which is how a persona came to insist Lisbon was invented. In a bounded one it means
everything — no hole 19 in the putt-putt library means there is no hole 19.
And the corpus now has a root. compose/libmerkle.py hashes 167k cards into one number that
is identical on two machines in 2.5 seconds. libpath.found() answers how many libraries are
visible, which is true of a corpus whose contents are wrong; a root can only agree if the bytes
agree. werbos deploy status compares it inside the container, where the cards are actually
read — the exact place the ten-day zero-cards failure lived.
None of the five adds content. Teaching was free three times over because the cards already
paid for it: correct is explanation-grade, every card carries exactly two distractors (so 3-way
multiple choice costs nothing), and Sonnet emits libraries in syllabus order rather than as a bag
— so file order is the curriculum and course.py never sorts a topic list.
Two doors make a diagnostic neither one is. library_lookup returns the single best card and
discards the ranking, so a near-miss and a genuine absence produce the identical None.
knowledge_hub is a second, independent reader with no 0.50 cosine gate, so asking both splits an
abstain into honest gap (mint cards) and unreachable (fix retrieval) — opposite fixes that
were indistinguishable until there were two readers (scripts/reachability.py).
EXPORT is the distribution wedge. The other three require someone to come to werbos; a SCORM zip rides in on infrastructure the customer already bought, on a budget line that already exists.
The intent hierarchy — default to play, descend on contextual confidence (Day 139). Routing used to
fire the heavy lanes (advice, empathy, crisis) on a per-prompt keyword, so it over-escalated: “my team
lost the game” got crisis-toned comfort, “what should I do in Hawaii” got a life-decision lecture, “I
can’t go on” fell to a random library card. The fix is an inverted-Maslow prior — a person poking a
chatbot lives at the top of the hierarchy (play, curiosity), rarely the bottom (crisis) — so routing
defaults to play, and each descent to a heavier lane must clear a confidence that is contextual:
a running distress trajectory over the conversation, not one message. The crisis gate runs before
retrieval and is graded — explicit self-harm → a 988 referral always; an ambiguous “i want to end
it” → the trajectory decides (a dark arc escalates to 988, a rage-quit “end this level” stays light, an
isolated phrase earns a soft check-in). One contextual signal (compose/intent_hierarchy.py) replaced a
pile of per-lane guards. docs/intent-hierarchy.md.
werbosOS — the honest tools (Day 111–112)
The chat app grew a second kind of surface: feature screens — small, client-side tools that take the engine’s one rule and turn it on everyday text. Measure what’s observable; refuse to guess what isn’t.
| Tool | What it measures | What it refuses |
|---|---|---|
| Prose Fingerprint | how text is written — caps, emoji, rhythm, vocabulary, the words you reach for | to say who wrote it, or that they’re anxious / creative / hiding something |
| Relationship Lens | a conversation’s communication regime over time — warmth, tension, distance, each from observable signals (emoji, message length, reply latency, who-initiates) | to score how anyone feels — it “records the tremor, doesn’t diagnose the fault” |
Prose Fingerprint is the front door of the persona engine — the same style.fingerprint that
writes a capsule, made visible. The Lens is a communication seismograph: a scrubbable month timeline
where silent months show as shaded gaps and a volume lane keeps a busy reunion from reading as “cold.”
Both run entirely in the browser — a private conversation never leaves the machine, which is the
local-first promise applied to the most sensitive data there is.
Each screen is the same six pieces (a View, a view-kind, App wiring, a Sidebar button + icon), so the next tool slots in without touching the last — the marketplace pattern, turned on the app itself.
Serving honestly — the launch stack + honest readers (Day 135)
Three tools shipped that all share one shape: do the thing correctly, or refuse out loud.
- The launch stack — prepping a password-gated public launch meant bounding the box. SR8 is
memory-bandwidth bound (~8 concurrent owned generations before the knee), so a concurrency gate
caps owned-gen there and queues honestly (“you’re #N in line”); a per-session rate limiter is the
guest flood valve (operator exempt); a live pressure gauge on
GET :8097/reports the fall-through rate — the master capacity dial. And the one that matters most: the owned-brain server can never fall through to the operator’s silo — a sid-less public request mints a fresh isolated silo, never lands in the operator’s chats. - The OCR reader (
werbos ocr) — a small ONNX engine reads text off an image with a confidence per box, so a region it can’t read abstains instead of fabricating. Same honesty the whole system runs on, applied to pixels. - Topic images (
topic_image) — fill a page’s image slot from a topic (real photo → generated → keep the placeholder), SSRF-guarded and inlined. “A website about dogs” gets actual dogs, or an honest empty box.
Why local-first wins economically
| Setup | Cost @ 24h continuous agent | Notes |
|---|---|---|
| Cloud LLM API | ~$50/day | Token-billed; you stop using when you sleep |
| Your laptop / SR8 | ~$0.03/day | Just electricity; runs while you sleep |
Three orders of magnitude. That’s the whole thesis behind werbos.
The MMM forest — change the library, not the model
The four operations above all change the model. The MMM (Micro-Model-Forest) direction is a fifth lever: leave the model fixed and change the library it walks. A forest is a content-addressed library of leaves — retrieved at the point of need and injected into a small model’s context, so a 7B uses the right fact instead of half-remembering it.
| leaf type | what it is | how it proves itself |
|---|---|---|
| noun | retrieved content (a wiki article, an API signature) | a Merkle hash — the content is exactly what it claims |
| verb | a gradeable operation (add-test, fix-type-error) | a free grader — compile, test, run; no grader → it’s a vibe |
The coding forest (mmm-poc) points this at code: 600+ real
JS/Node API signatures + operation capsules, routed outside the model, graded
by tools that can’t be sweet-talked. The benchmarks (B.1–B.6)
measure the one question that matters — does the library make a small model
beat its raw self? — and the answer holds: it wins where the answer is
underivable (your repo, a stale API, a footgun), ties where the model already
knows, and never loses once the retrieval is gated.
The dogfood loop makes the library learn. Every graded run is an experience;
the library grows (crystallize a blind-spot win into a new alias/skill) and
prunes (garbage-collect the unused) — both review-gated and reversible — while
passing runs bank certified training pairs. The newest of these loops, HAIRLy,
drives the eval through the product: Claude simulates a real user in the chat UI,
grades the reply, and reduces two judges to one verdict (the Y). It’s a self-fuzzer —
Claude paraphrases each seed into the messy ways real people ask, so phrasing-sensitivity
bugs surface on their own — and it runs multi-turn: a roster of user personas
(Danielle and five others — the ones asking, each a different typing voice, distinct from
werbos’s own cleo/trump/dickinson output voices) holds a whole conversation, graded on the
arc (context retention, consistency, boundary-over-time), not just single replies. Varied
voices are the point: the same question in a bubbly vs a terse register caught tone
hijacking the router. Being internal tooling, it runs on the subscription (claude -p),
never the metered API. Crucially its grades are labels and test cases,
never weights — a reject routes to a fix layer and becomes a new scenario, so the loop
finds bugs a robotic query never would (its first turn caught the teacher lane eating a
fact question, and fixing it unmasked a confidently-wrong poisoned card) without ever
training werbos to imitate the grader. The endgame is the north star:
move enough work from the model into the library that the generator can shrink
from a borrowed open-weight model to a small model we own, then drop the
borrowed weights entirely. Stated as a target you can hold us to: the day the
forest wraps a model we trained and still beats the open-weight baseline on
verifiable coding. The Benchmarks page is the scoreboard for that
migration, run in the open.
Composer-only — how small can the model get? (B.7). If the forest is the brain, the model is only the constructor — it doesn’t need to know how to code, just which verbs and leaves hold the rules, and then it stitches. So the inverse of the question above: fix the library, shrink the model, and find the floor. Swept 7B → 0.5B, the forest beats blind at every size and a 3B-in-forest ≈ 7B-in-forest — the library buys back the size — down to a minimum-composer floor around 3B. A knowledge-leak test (a fact only the library holds, library removed → the model must fail) stays SEALED, and on the chat side the answers are zero-UNSOUND: when right, they’re right from the leaves, not the weights. The consequence: the model commoditizes, the curated brain becomes the moat — and unlike weights, the library compounds every Tuesday.
The library grows itself — seek-compute-cache (Day 67, compose/resolve.py). The forest above
grounds against a fixed library; resolve() lets the brain grow one from the open web. A query
walks LOOKUP → SEEK → COMPUTE → VERIFY → CACHE behind three gates, and only verified results enter the
library — learning is a durable, cited cache write, never a weight nudge. On top sits the newest
evidence for “the model commoditizes, the library is the moat”: an owned byte-level conversation
model, trained from scratch on the GPU, now answers the dashboard’s Custom-model toggle live on the
homelab (CPU) — and a GPU size-sweep found the crossover (size is dead weight on thin data, the
lever once the corpus is rich: 5M 70%→46% as data tripled, 30M→79%). The honest counterweight: a
from-scratch reader over a fresh JS+TS library hit the comprehension wall at chance, and a TF-IDF
baseline at chance proved it’s the task, not the size. So the split holds by measurement — the owned
model carries conversation, the library carries knowledge.
The machine became fully owned (Day 68, compose/{codegen,execute,owned}.py). A creation tier
added code generation graded by EXECUTION (run it in a hardened sandbox, return only what ran green,
else abstain) with owned patterns tried first, and a domain-spec factory made new libraries a JSON
spec. Then the closer: an offline/lite mode giving all three rented seams owned, model-free
fallbacks — extractive read, deterministic grounding grader, library seek — so --net off runs the
whole loop with the web AND Claude unplugged. The milestone isn’t “owned and as good as the frontier”
(offline quality drops magnitudes, by design); it’s that everything still rented is rented by choice,
not by necessity — pull every external cord and the machine still answers, or honestly abstains. That
is the definition of owning it. The remaining gap is coverage, a minting problem, not a wall. Story:
Wrapper to Owner.
Reasoning — search against a verifier (v0). The forest is brain storage; it
doesn’t, alone, think. The reasoning brick turns thinking into search against
a validator, the one thing MMM is uniquely built for. Decomposed: REASON
proposes a step (generative, fallible), LOGIC validates it (external, sound —
a type checker, node --test, a property check), and THINK is the search over
both. It runs as proof-of-work mining — spawn N miners down different paths, a
sound chain is hard to find but cheap to validate, first to surface a
validator-passing block wins, else abstain. v0 (one ply, no search depth yet)
proved the shape on a 13-task verifiable benchmark, and surfaced the key move: a
miner can propose a program instead of a prose answer — executed and graded
like a verb leaf — so enumeration tasks a 7B can’t do in its head (count the
combinations) get computed and certified. That’s the reasoning↔forest bridge:
the implement-function verb pointed at thought. Airtight exactly where the
validator is hard-formal; honest floor where there’s no verifier (taste, open
leaps). Lives in mmm-poc; not yet wired into a live chat turn.
Library at scale — mmm-brain. The sibling
that varies the library instead of the model. Three findings, all frozen: the noise that
drowns a fact is a property of semantic retrieval, not library size (lexical shrugs off 160×
distractors); routing to a domain sub-tree never improves retrieval (it removes cross-domain
noise that never competed — the noise that hurts is same-domain look-alikes, Array.toSorted
vs Array.sort); and the lever that does move is leaf enrichment — a capable model,
working offline and blind to the eval, writes “what you’d ask for this” onto each leaf,
lifting a small stack’s recall from 3/10 to 9/10 with the online model unchanged. The
librarian is a frontier model run once, offline; the reader stays tiny and local; the gold
grades it, never the librarian.
The kernel — recursive completion. The generalization the whole forest turned out to be an
instance of. A query is a silhouette: every node opens into a subset of information it
implies but never stated — “a park” implies daylight, green, clothed people — and you
recurse into the library, inheriting that context downward, until no hole remains. That
completion is what makes an image, a story, a sentence real — real = fully grounded, no
unfilled implication left. One structure, different leaves: image, story, person (the
personas), code (the verb-forest), memory. That’s the domain-blind engine, proven by the shape
reappearing every time something works — and the bet it implies: realness scales with the
depth of the library and the recursion, not the size of the model. The tree was never a
search structure; it’s a completion structure — a generative grammar grounded in a
living, cited library. It is now running code: werbos/compose/ completes a silhouette
into a grounded tree and can re-resolve a single failed node without re-rolling the whole
artifact (the man isn’t smiling — fix the man, not the park), with an image and a story
falling out of the same engine. (Written up in full at
docs/vision/recursive-completion-kernel.md.)
The kernel gets a reader — and learns to think (Day 58). Every move the kernel made was
arithmetic over the library, so it was blind to negation and paraphrase and stamped
GROUNDED on whatever shared words. So it got the organ it lacked: a fenced reader — a
small local model that reads the retrieved candidates and picks the one cited leaf that
answers, or abstains (a made-up pick reads as abstain), with a separate grader that may
only ever downgrade a pass, never invent one (cursor, not knower, per
VERDICTS). Same corpus, swap only the reader: arithmetic baseline 45% / 6 lies → 7B
reader 55% / 0 lies — retrieval already held the answer 91% of the time, so the win is
reading, not retrieving. Then the reader learned to think: reason.py decomposes a query
into the sub-questions it implies, recurses, grounds each hole or names it as a gap — a
node is real only when every sub-hole is. This is the same search-against-a-verifier
move as the reasoning brick above, ported from formal code to prose: the “validator” is the
fenced grader plus honest-abstain instead of a type checker, and the answer is composed from
cited leaves or refused. Day 107 swapped who decomposes: a typed reasoning card states
its own sub-questions in needs, so the library supplies the split deterministically and the model
is only asked when no card matches — and each card carries a check that rejects its own output, so
a failed step becomes an abstain instead of an answer. On our pinned 336M that moved confident wrong
answers 32 → 1 while capability stayed put: capability is bought with parameters, honesty with a
harness. Driven to failure on the real 2342-chunk client corpus it grounds
confident garbage — and that is the point. The binding chain is corpus quality → retrieval
recall → reader → grader → loop, the loop at the end; the fence is a near-free honesty win
on open corpora but on a closed high-recall lookup an always-commit re-ranker leads on
accuracy. Refined thinking, pushed to its edge, points a finger at exactly where the real
constraint lives — the library — which is the whole bet, stated one more way: vary the
library, not the model.
Day 110 made reasoning a primitive — and gave it a forward gear. compose/reason_tree.py
content-addresses every step (Merkle), so the decomposition converges to base cases, a repeated
subgoal is proven once, and an unprovable branch is an honest ∅ gap. Its sequel, the synthesis
tier (compose/synthesize.py / synthesize_reasoned.py), inverts the same loop to build past
what’s stored: an intent decomposes to eternal primitives, composes upward, and is RUN — a
verified artifact (“leaderboard v99” from parts that exist) or an abstain, never a faked one.
Retrieval is what exists, abstain is what I don’t know, and this is the tier above — what
doesn’t exist yet, derived and verified. Same honest spine, pointed forward. Day 124 hardened this into the chat’s live path — the engine does the work, the model gets out of the way. compose/puzzle_solver.py handles finite constraint puzzles the way honesty demands: don’t generate a proof (a 1B hallucinated a gibberish one for 77 s), enumerate every arrangement and verify — reporting the unique solution, or “underdetermined” / “contradictory” when the puzzle itself is broken (it caught two such puzzles in milliseconds). compose/session_memory.py makes the running conversation its own retrievable card library — the user’s words about their world outrank any hub card, so a fact stated ten turns ago resurfaces grounded, constant-memory not context-window. And compose/grounding_guard.py gates every voiced answer on the fact’s anchors (numbers, proper names strict; framing words loose), so a looser mouth can paraphrase but can’t drift off the fact or ground on an off-topic card. All three are the same move as the reasoning brick — retrieve, verify, enumerate; never generate-and-hope — the shape of the 30B fight: out-structure, don’t out-generate. Day 143 extended the same spine to conversation itself: compose/topic_extract.py finds the topic in any phrasing not by matching frames (a losing game against gen-z, slang, typos) but by subtracting the finite wrapper — tag each token filler/intent/connector/stopword, the residue is the topic — then grounds the residue, so “yo vibe about 2jz fr fr” answers from cards while a bare share is never force-grounded; compose/var_memory.py lets a variable declared one turn be used the next and a place named one turn resolve later; and the whole session is now forwarded front-door → brain so every conversation’s memory is its own (a fresh chat inherits nothing), with a deterministic guard that refuses any riff claiming lived history werbos can’t have. And the whole stack
finally has a body plan: senses + tongue in the head, a four-container brain, a
pacemaker heart — the tongue in the head, not the brain, because speaking is not knowing.
And then the cursor became swappable (Day 60). With the loop refined, the last gap was
that the reader was welded to a weak local model. But the reader is a cursor, not a knower —
so its model is config, not code. make_reader_chat(spec) now drives the same library with
local Ollama, the Anthropic API, or the claude CLI (reusing Claude Code auth, no key);
the library never leaves the box, only the query + retrieved leaves do. That closes the loop
on the thesis: keep the curated, cited, local library as the moat, and turn the model into a
dial you set — as cheap and as sharp as you like — rather than a thing you’re stuck with. (A
deterministic corpus refiner raised the library into clean fact-leaves, and a frozen
reasoning bench now keeps the whole thing honest by number, not anecdote — it twice
overruled a plausible plan the week it shipped.)
And then the reader became a brain (Day 61). One reader over one corpus became a runtime
pipeline over many libraries: a reflex cache answers common turns with no model; a
model-free intent weld routes a question to the library it’s most characteristic of (or
abstains at the door); the fenced reader grounds it; and a fenced fluent renderer phrases
the facts naturally without letting fluency smuggle in a hallucination. Around it runs the
Honest Loop — use → judge → grow a living gold set → diagnose the miss by fix layer →
fix it (enrich the query from the leaves, or measure the local↔frontier reader gap and distill)
→ gate — the feedback loop that made a frontier model’s weights good, run cheaply over daily use
with the operator as the un-bribable grader. Its Month 2–3 finding: a better question only banks
once the reader can hold the answer — fused retrieval is a wash on the local reader and a win on
the frontier, and that gap is distillable. The framing underneath it: the brain is a thin wrapper, the sibling repos are its
organs, and the one part you can’t hand-roll — the reasoning weld — is a thing to shrink,
not to out-pretrain (docs/brain-nervous-system.md, docs/honest-loop-plan.md). Use it now:
werbos brain "<question>" --registry libs.json.
And then we stopped borrowing the model (Day 63). “A thing to shrink, not out-pretrain” is
an invitation — so we took it, and built the reader from nothing. werbos/weld/ is a hand-rolled
model stack with no open weights and no ML framework (stdlib + numpy): an owned byte-level
tokenizer trained on your corpus (lossless by construction), a numpy transformer runtime
(the weld: cursor at the same make_reader_chat seam), a reverse-mode autograd engine + Adam
trainer (every op checked against a finite-difference gradient), a distiller that trains the
model on the frontier teacher’s picks — gated by the reader-gap, student lies ≤ teacher — and a
freeze step that seals weights + tokenizer + the trust scorecard into one content-addressed,
signed, fail-closed brain. It kept the one rule that makes any of it trustworthy: even an
untrained, obviously-not-good-enough model can only ever abstain at the fence, never lie. Not
GPT-4 — but GPT-3-level on your domain, ours end to end, because the library carries the
knowledge and the weld only has to read. The ladder is built; what’s left is empirical — grow
the teachable set from real use and measure whether owned-local matches the teacher on-domain.
This is the werbos thesis reaching its end state: own the whole stack, keep the model small,
let the library be the moat.
And then anyone could mint one (Day 65). The whole ladder collapsed into the Factory —
werbos weld dashboard|mint, one call and one live page that streams a mint end to end (tokenizer →
reader-gap → grade → distill → gate → freeze), with a bring-your-own-corpus on-ramp that turns
your docx/pdf/xlsx/json into the feedstock. The thesis, made operable. And the honest measurement
landed the same day: a GPU-scaled sweep — 8k synthetic pairs, models 3.4M–57M, adversarial decoy
data — all sat at 3–6/19, half the 1M-param champion’s 12/19, proving the lever is data quality
(real teacher-picked fences over real leaves), not model size or data volume. The v2 ruler closes
two buried questions so a perfect 19 is honestly reachable; relate (opposites) remains the wall no
from-scratch reader clears — champion included.
And then we trained it — and built a ruler to keep ourselves honest (Day 63). Fed real
questions, the from-scratch reader learned — 15 of 16 held-out questions it had never seen, which
is generalization, not a lookup table. But the grader (does this passage answer, yes/no?) refused
to learn — it memorized and collapsed to a constant even at six times the data. That split is the
whole design constraint (docs/memory-and-learning.md): the weld should hold skills, the library
should hold facts, and memorization in the weights is a smell — abstention, it turns out, is a
memory-boundary judgment to compose from cheap retrieval pieces, not a skill to bake in. To measure
any of this without fooling ourselves, we built the Gauntlet — a frozen, held-out,
deterministically-scored reasoning ladder (retrieve → read → relate → chain) that grades any cursor
on two axes: how far it reaches, and — the werbos crown jewel as a number — whether it abstains or
lies when it fails. Its first verdict was against us: a general weld reached ~L1.5 at 12% honesty,
between the arithmetic floor (L1.75 / 0%) and the Claude ceiling (L1.99 / 100%) — it generalizes but
reads no better than retrieval and bluffs, so don’t wire it yet. That is the point of building the
un-bribable grader first: it tells you the truth even when the truth is “not good enough, and here’s
exactly why.” The loop — train on a separate hub, measure on the sealed vault, never cross the two —
is now the trust curve we get to watch climb.
And then it learned to never lie, and to improve itself (Day 64). Two organs closed the loop into
something you can leave running. First, honesty: a tiny model can’t learn to abstain, so we made the
system honest with an adversarial cascade — cheap local checks escalating to Claude reviewers in
separate contexts whose only job is to refute the pick (an idea straight off a Fireship video on
adversarial code review). The picker is biased to commit; the reviewers are biased to reject; the gap
is the honesty. On the sealed Gauntlet it took honesty from 12% to 100% — seven lies to zero — with
capability untouched. Second, self-improvement: with a gate that can’t be fooled, the old
stability-harness question — does self-modifying build up or tear down? — finally became safe to run.
The daemon does a cycle at a time: the teacher writes new questions on the weak levels, the model
distills them, and it’s adopted only if the sealed Gauntlet says it didn’t get worse. Live, it took
capability from 9 to 13, then threw out a regression on the next cycle while the champion held — up,
then up, then held; never down. So the owned stack now learns, never lies, and gets smarter each
day without being able to degrade — every claim certified by a ruler it never trained on. It still
doesn’t wake up conversational (that wall is a bigger model on a GPU, honestly deferred), but grounded,
honest, sovereign reading that improves on its own is no longer a plan — it’s running. Milestones:
docs/ACHIEVEMENTS.md.
The training-fuel factory — how the corpus gets made (Day 108)
The MMM forest is about using the library; this is about making it. One domain map (the 374
/graph domains) drives three data pipelines, all seeded from the same subjects, all governed by one
rule — cards are retrieval, prose is fluency, and neither leaks into the other:
| pipeline | what it produces | where it lands |
|---|---|---|
fluency corpus (wiki_fluency, stackexchange_fluency, gutenberg_fiction) | three registers of PROSE — expository, conversational-technical, narrative | the tongue’s base weights (babble, never retrieval) |
conversation mint (scenario_matrix + mint_grounded) | card-grounded dialogue in US-weighted attributed personas with regional vernacular | fine-tune pairs for the tongue |
card factory (mint_cards: expand / grow-topics / new-domains) | new concept cards — deepening topics, widening domains | the retrieval library (graded first, never weights) |
Each runs against any OpenAI-compatible teacher, so a burst-rented GPU (~$3–4/hr) mints at concurrency and tears down — the expensive-to-get data, gathered cheaply. The teacher is a factory tool that never ships; it distills into the owned model and the graded library. The moat line holds through all three: the conversations teach the tongue how to talk about a domain; the facts stay in retrieval. This factory is the 2.5 → 3.0 fuel.
The marketplace — compose your model from packs
“Change the library, not the model” becomes something the user does. If capability is
cursor × library, then the product is the model you assemble: your model = base + /skills + knowledges + models(others'). Everything is one primitive — a pack (a knowledge library, a
skill, or a whole saved recipe) with a small manifest (kind, visibility, verified).
- Compose —
werbos pack new/addsnaps packs onto the base cursor; a model’s paths are exactly whatresolve()answers from, so it genuinely knows its packs and abstains on the rest. Upgrading is just adding a pack — no retrain, non-destructive. - Publish + install — a serverless shared catalog (
catalog.json+ a source; filesystem or http tarball). Public listing is gated on verification — you can’t advertise a pack you haven’t graded, so the grader is the catalog’s bouncer. - Kinds of pack — knowledge (facts, graded by the cold-check grader) and skill / capability (things it can do, verified by execution or rules). The first capability pack is tic-tac-toe: a minimax engine that plays perfectly, verified by rules not a model, and pack-gated — a model can play only if it composed the pack.
- A third kind — speak packs (Day 83,
compose/speak_packs.py). A way of talking you snap on: pirate, lolcat, shakespeare, cockney, gen-z, corporate. Each is aStylePack(lexicon + tics + interjections + its own detector), applied to owned source text at a blend (0 → full) and gated by two graders — does it read as the style, and does it still mean the source (round-trip). New register = ~15 lines of data, engine untouched.capability = model × libraryfor voice. - Why it’s uncopyable — sovereignty is permission. Frontier labs win the head; werbos wins the
tail they’re forbidden to touch (your niche, your stance, your game). Local-first is the liability
shield: werbos is the compose layer, not the host. See
docs/ideas/the-marketplace.md.
And then the store grew a body (Day 70). The pack system got a Compose Workbench — load a
model, snap on packs, extend it, and talk to it live on one page wired to the real resolve(). The
offline reader grew a conscience (abstain when a question’s subject isn’t in the library, never echo
the question back — the honest-abstain moat, held on the offline path too). werbos scan <path>
learned to crawl a folder into a small grounded memory card of what it is — no ingest, no model, no
bluff — the first brick of a tree of little models, one per project folder (the original
“Merkel Mini Models” idea maturing). And the conversational wall from Day 64 started coming down the
honest way: a two-lane conversation-corpus engine — a GPU box bursting high-quality turns while
the homelab drips a disjoint topic set 24/7, both into one deduped file — minting the tens of
thousands of natural pairs a GPT-2-size talk model needs. Teacher models are factory tools, never
shipped. The blueprint for selling all this — Minting-as-a-Service (login → mint → download a
model that’s yours, small enough to run offline) — is mapped seam by seam in
docs/ideas/minting-as-a-service.md.
And then it got trained, and the ruler judged (Day 71). The compose→mint→talk experience went
live at /demo — pick from the real library or import a ~5-page document (in-browser
.docx, no claude), then chat with the composed model via a client-side honest-abstain reader that
answers from what you gave it and refuses the rest. And the owned talk model got trained for real: a
size sweep on the 77k-pair corpus (werbos convo pull gathers + grades both mint lanes into it) gave
an honest verdict against the obvious plan — the 30M won (held-out 1.183); 85M and 125M got
worse. Not overfitting (their train loss was higher too) but undertrained — a 135M model
starved of steps by a CPU-bound loader that made it take 2.36 hours for 6k steps. The lesson is pure
werbos: ship the 30M (smaller, faster, best), and fix the pipeline before scaling — measurement over
ambition, every time.
And then it learned what kind of question it was holding (Day 75). Everything above assumes the
input is a lookup — and a flood of real chat turns showed that assumption is where the lies come
from. Asked “what is in the bucket besides 2 apples?”, a retrieval brain keyword-matched a token
bucket networking card and said it with a straight face. Keyword overlap always returns something:
confidence is free, being right isn’t. So the chat gained a discrimination cascade — figurative →
speech-act → small talk → code → reasoning → resolve() — where each gate classifies the move
before anything reaches retrieval. The new brick is the intelligence layer
(compose/comprehension.py): a word problem is computed from the sentence, not stored in a card, so a
reasoning gate catches state/count/negation asks and hands the model a state-tracking scaffold
(decompose → track state → answer only what’s literally asked) that resists the “besides 2”
distractor — “3 apples in, one out, what’s in there besides 2 apples?” → “1 apple.” Same 3B, no
new weights: intelligence is a scaffold, not a weight — the “vary the library, not the model” thesis
extended one rung, from what it knows to how it thinks. And when the scaffold can’t crack it, it
abstains instead of falling through — falling through to retrieval is exactly how you get a token
bucket. The same day, tarot stopped being a rented freewheel (qwen2.5:14b improvising a spread) and
became what everything else here already is: 480 gradeable cards and a seeded, exact-hit composer
that runs offline. Honest boundary: brick 1 is a discriminator plus one scaffold — temporal,
self-referential, and multi-hop reasoning are still open.
The system, end to end
The diagrams above are about LLMs in general. This section is about werbos specifically — the actual piece of software that runs on your laptop and your SR8 and (eventually) your phone.
Five layers in one diagram
+--------------------------------------------------------------+
| CLIENTS |
| CLI | Dashboard SPA | Slack | MCP | API |
+--------------------------------------------------------------+
| HTTP / stdio
v
+--------------------------------------------------------------+
| SERVER (werbos serve -- Starlette + uvicorn) |
| /api/chats /api/agents /api/tasks /api/runs |
| /api/budget /api/activity /api/models /api/channels |
+--------------------------------------------------------------+
|
v
+--------------------------------------------------------------+
| SESSION |
| ChatSession -> Agent loop -> ContextBuilder |
| persona | memory | tools | mirror | leash | budget gate |
+--------------------------------------------------------------+
|
v
+--------------------------------------------------------------+
| AGENT LOOP (pitts) |
| think -> act (tools) -> observe -> repeat |
+--------------------------------------------------------------+
|
v
+--------------------------------------------------------------+
| BACKEND |
| Ollama (default) | MLX | Bellman streaming HTTP |
| -> base model + LoRA adapter (see diagram above) |
+--------------------------------------------------------------+
A request comes in at the top. Each layer narrows the job. The backend at the bottom is the only thing that touches GPU/CPU weights. Everything else is plumbing — deliberately, so the weights can be swapped (Llama / Qwen / Mistral / your fine-tune) without re-plumbing the rest.
The input + output layers (Sprints SE.5 + SE.6, shipped 2026-05-30)
Two more layers landed late in the arc, both about closing the gap between “local LLM is slow” and “local LLM feels fast for the operator.”
operator types anything
|
v
+--------------------------------------------------------+
| PARSETONGUE (SE.6 -- werbos.parsetongue) |
| parse + parseltongue |
| raw string -> structured Intent |
| fast path: "/joke ..." -> kind=skill, name=joke |
| slow path: "my back hurts" -> kind=skill, |
| name=stretch |
| low confidence -> kind=unclear + clarify options |
+--------------------------------------------------------+
|
v
+--------------------------------------------------------+
| PRECOOK CACHE (SE.5 -- werbos.precook) |
| SQLite key/value, TTL-evicted, opt-in per skill |
| cache hit -> serve in <50 ms (no LLM) |
| cache miss -> fall through to live inference, |
| write result back for next time |
+--------------------------------------------------------+
|
v
[ existing SERVER / SESSION / AGENT LOOP / BACKEND ]
Together they implement static-rendered AI: the chat-time path stops being where the model runs for the 80% of operator usage that repeats. Parsetongue eats the syntax barrier; precook eats the latency barrier. Live inference fires only on genuinely novel input.
Parsetongue is the input layer. Type “tell me a joke about
ssh tunnels” and it resolves to kind=skill, name=joke,
high confidence, with the rest as slots. Type “i have idea” and
it returns kind=unclear with three buttons. The slash commands
you already know are the fast path; the natural-language path is
the new ergonomics. SPEC-locked at parsetongue/1; full design at
/docs/parsetongue/.
Precook is the output cache. A skill opts in by setting
PromptSkill.precookable=True; the cache hooks slot into
run_prompt_skill before live inference. v1 starters:
summarize, eli5, quote — knowledge skills with the
right shape (same input → same response). Creative
(/tarot, /haiku, /joke) and wellness (/cbt-reframe) stay
live to preserve freshness.
Measured on SR8: eli5 "binary search" took 10.4 s live, then
0.4 s cached — ~25× faster, and most of the 0.4 s
is docker-exec overhead. The actual SQLite lookup is single-digit
milliseconds.
A small but lovely emergent property: the SE.1.1 skill-smoke daemon already runs the precookable skills every 30 minutes for format grading. The harness warms its own cache. After the first cycle, every subsequent harness run is a cache hit. The systems compose without anyone wiring them together.
Storage — what lives where
| Where | What | Why local |
|---|---|---|
PFM archive (~/werbos/chats/*.pfm) | Every chat turn, append-only | Your conversations, your file. Survives reboots. |
| tasks.db (SQLite) | Tasks, pending actions, budgets | Operational state for the agent plane. |
| agents.db (SQLite) | Agent definitions, channel routing | The cast of personas you talk to. |
memory dir (~/.claude/.../memory/) | Cross-conversation memory files | What I remember about you between sessions. |
user.md (~/werbos/user.md) | Your profile, hand-edited | Six-section template. Injected into every system prompt. |
| working memory (append-only JSONL) | Per-session conversation turns | Retrieval-as-memory: recall what scrolled out of the window instead of holding a 1M context. |
brains (~/werbos/brains/, or $WERBOS_BRAINS) | Every talk model we ever trained, plus a current symlink | The models are OURS, so they are an artifact you keep, not a download you can re-fetch. Kept deliberately even when superseded: each is a point on the benchmark timeline, and deleting one invalidates a published number. Listed on the dashboard’s Models page. |
corpus + vocab (train.bin, book_chat_bpe_*.json) | Tokenized pretraining stream and the BPE that made it | A vocab is load-bearing forever: it is not a preference but part of the model, and re-segmenting with a different one turns the weights into noise. The .bin is a flat uint16 array, so a corpus that grows is a file APPEND rather than a re-tokenize. |
Local-first means your data lives on your box. Inference can route to whatever model fits the agent (cloud is fine for the rented brain; the data stays home).
The sibling repos — one job each
werbos is a wrapper. It composes a dozen sibling libraries. Each is small, independently testable, and has one job.
| Repo | Role | Status |
|---|---|---|
| werbos | The orchestrator. CLI, server, sessions, scheduler, channels. | shipping |
| pitts | The agent loop primitive: think → act → observe. Backend-agnostic. | v0.0.2 |
| werbos-libraries | The content moat, decoupled 2026-08-01. 374 graded card libraries (163,563 cards) as their own private repo — the library versions on its own clock and pulls on its own. libpath resolves it as a sibling with no env var, and libpath.found() is the assertion a deploy should fail on: the brain container ran ten days with a mount pointing at the emptied pre-decoupling path, serving zero cards behind a passing health check. | building |
| werbos-personas | Persona capsules, split out 2026-08-25. profile.json + prompt.md + exemplars.jsonl + grader.md per character — a capsule is content, versioned and graded like a card library. Her memory of YOU is state and lives under rosterpath.state_root(), never in this repo and never in the work brain’s MemoryStore.default(). rosterpath is libpath’s twin: explicit config wins unconditionally so a failed mount is VISIBLY empty rather than quietly served from somewhere else. Day 108: capsules can now be derived from a corpus — personas/style.py fingerprints how someone writes (format-agnostic across chat and verse; confidence scales with corpus size) and capsule_from_corpus.py writes the four files, so a transcript or a book becomes a chattable persona. The roster grew 4→10: Emily Dickinson (public-domain verse) and Trump (public-style pastiche) shipped in-repo, and Day 111 added a hand-authored werbos house-voice capsule — the 12-year ride-or-die coworker who is the default when no style is named and the honest fallback when a named style has no capsule (invoked discovery-free via compose/style_select.py: “in the style of X”, no menu); a persona built from real personal texts deploys off-repo (untracked, gitignored) so its words never enter git. An optional nsfw.md sidecar mounts on an SFW base per-session — off can only remove permission, never a hard limit. | private |
| pfm | Personal Field Map — append-only at-rest archive for chats. | locked |
| mcculloch | Evaluation harness. Bench suites for code/debate/games. | locked |
| partyparrot-py | Live wire. Streaming primitives shared across sessions. | locked |
| observe | The see layer. Telemetry events with discriminated kinds. | v1.0.0 |
| watchtower | The summon layer. HTTP / shell / fn dispatch with allow-lists. | v1.0.0 |
| warden | The defend layer. Rate / pattern / predicate / allowlist rules + alerts. | v1.0.0 |
| beacon | The signal layer. Probes + transitions (edges, not levels). | v1.0.0 |
| watchtower-ui | The SPA front for the orchestration suite. | v0.1.0 |
| glasshouse | The inspect layer. Reads the trace log; renders per-trace timelines + per-day stats. | v0.1.0 |
| soma | The puzzle bin — a thin index that re-exports the loop’s primitives (from soma import capture, grade, fence). Owns no logic; maps each piece to its own small repo. | building |
| capture | The shared experience store. Append-only JSONL log + query, carved from autopilot’s ExperienceStore so every loop logs one shape. | ready |
| learn | The champion/challenger gate. gate(experiences, champion, train=, score=) — refine a prior from experience, never promote a worse one. Surface-agnostic. | ready |
The five-piece orchestration suite at the bottom (observe → watchtower → warden → beacon, plus the UI) shipped on 2026-05-08. They embed into werbos or run standalone — that’s the point of keeping them library-shaped.
glasshouse is the diagnostic surface, shipped on 2026-05-26 as
the eighth Phase-3 sibling. Pure stdlib reader for
~/werbos/traces/<YYYY-MM-DD>.jsonl; the writer side
(werbos.tracing) emits per-turn UUIDv7 trace_ids and instruments
slash dispatch, LLM calls, intent_compiler, and tool invocations.
Operator asks glasshouse trace <id> and sees the whole chain
join up. Trace JSONL files are self-describing (a _schema header
row declares the on-disk shape) so a future archaeologist can
parse them without the werbos source on hand.
soma + capture are the loop-as-library carve-out, started 2026-06-16.
Every loop (autopilot, mythos, dogfood) had re-implemented the same
primitives — an experience log, a grader, a fence. soma is a thin index
that re-exports them from one import surface while each stays its own small
repo (grade=mcculloch, fence=warden, capture=the new append-only store).
On Day 48 ExperienceStore became the first consumer: it adopts capture
as a soft dependency — the capture engine when the sibling is installed, a
byte-identical inline fallback otherwise — so the container deploy is unaffected
while the loops converge on one shape. The same day learn was carved (the
champion/challenger gate generalised out of the autopilot policy) and run_gate
soft-adopted it the same way. Five pieces are ready now
(grade/trigger/fence/capture/learn); gate and search are the last
planned ones, and asking soma for one names the repo to carve next.
Three places it runs
| Deploy mode | Audience | Shape |
|---|---|---|
CLI (pip install werbos) | The author + early users | werbos chat, werbos code, werbos serve. One process. |
Docker stack (orchestration-deploy/) | Self-hosted on your own box | 5 containers: werbos + watchtower + warden + beacon + nginx. |
| SR8 (always-on) | The author’s workstation | Tailscale Funnel → nginx → werbos serve on host net. |
All three speak the same HTTP API. The dashboard SPA, Slack adapter, and MCP server attach to whichever one is running.
Agent plane — what V1 actually shipped
The dashboard isn’t a chat window with a sidebar — it’s an agent control surface. Five things were stood up between 2026-05-09 and 2026-05-13:
- Agents (V1.4) — cast of personas, per-agent persona/voice, tag derivation, mirror slider per turn.
- Permissions (V1.5) — tool registry perm-check, verb classification, pending-action ask flow.
- Models (V1.6) — Test/Pull(SSE)/Remove for
/api/models/{id}, backend inferred from id. - Scheduler (V1.7) — in-process asyncio daemon, hand-rolled
cron, FakeClock for tests,
SessionRegistry.create(agent_id=...)wrap so scheduled runs use the right persona. - Channels (V1.8) —
channel_routingtable, in-band/bindslash command, AgentDetail “Connected channels” list. - Activity (V1.9) —
/api/activityaggregator across chats + tasks + runs, Home feed component.
V1 is shippable. Code-via-channels (CC.1) and multi-adapter Slack (SC.1) are queued for Phase 16.
Autopilot (2026-06-09) is the agent plane’s self-directed turn.
The dashboard configures background agents; the autopilot is werbos
acting on its own. It watches <inbox>/requests/ and runs a
predict → act → observe → error → correct loop over its skill
library, then a slow loop trains a routing policy from the
accumulated experience and promotes it only if it beats the champion
on held-out data (the same gate the model trainer uses). Fail-closed
(safe-skill allowlist + append-only revert), opt-in, and it reflects
on itself — what it knows, what it’s missing, what it would
improve. The deep idea: the prediction error is both the real-time
correction and the learning signal. Diagram on the flow page.
The library is the product (2026-06-09). The catalog the autopilot
draws from — and the same skills you reach via /slash or
werbos <name> — grew from 137 to ~294 prompt skills in a
single day: a hand-built developer block (error-decode, git-undo,
regex-build, …) plus 157 more authored by a multi-agent workflow across
18 categories, each adversarially verified. This is the curated-breadth
thesis in motion: werbos doesn’t get smarter by swapping the model; it
gets a new capability by adding to the library. Frontier models improve
every six months; werbos got a hundred and sixty-nine capabilities in an
afternoon.
The capability was in the decoder, not the weights (Day 77, compose/focus.py,
weld/torch_talk.py). Two results that sharpen the thesis from opposite
directions. First, a contrastive-focus resolver: “I never said he ate my
sandwich” is seven claims depending on which word is stressed, and the mapping is
mechanical — a lexicon and word position, no model at all. It scores 20/20 where
llama3.2:3b scores 4/20 (chance), at 2.5µs against 469ms. The 3B ignores the
accent entirely, because a fluent model has no reason to represent that relation.
Second, the owned talk model’s monotony turned out not to be a data problem: the
corpus sits at 96.6% of maximum opener entropy with its top opener at 0.26%,
while the model emitted its own top opener 10.4% of the time — a 40×
amplification caused by greedy decoding taking the mode every step. Seeded sampling
moved input-sensitivity 0.65 → 0.94, past the 3B, with no retraining. And the
85M was tested directly and is worse on every axis than the 30M. Both point the
same way: capability lives in the structure around the weights, and “make the model
bigger” has now lost three times here.
The ask gets parsed before anything looks it up (Day 80, compose/askparse.py). Every
tier was reading the raw sentence with its own regex, which is four opinions about one
question. Now one parse produces four slots — intent, action, topic, target —
and the tiers read the slots. “How would i set display none for li” is a SOLUTION about the
topic display none applied to li; “what is display none” shares every content word and
is a different request entirely. The opener decides the tier, and it is the cheapest
signal in the sentence. It also fixes retrieval: the topic is the query, not the sentence,
because six words of scaffolding is how a question about display finds a card about how.
And retrieval learned to weigh rather than grab. The ring ceiling existed because a loose
ring with first-hit-wins returned something arbitrary. Every candidate is now scored —
topic overlap, how much of the card is about the ask, and the card’s own topic field,
which every card carries and the index had been discarding. The abstain survives, but on a
better rule: a close second is only ambiguous if it disagrees. Sixty cards filed under
const scoring within 0.06 of each other is abundance, not uncertainty.
The third answer (Day 80, compose/jsshape.py). A JS construct is an object with slots,
so it renders at any level of fill and every stage runs. Between “here is the code” and an
abstain there is now “this much I verified, and here is where I stop” — which is what
a structure that is right in every part but one should have been saying all along, instead of
being shown to the user as if it were fully right.
The composer spine — understand → plan → gather → speak (Day 82–84)
The runtime spine reorders everything: the model speaks last, not first. A message is
UNDERSTOOD (interpret.py reads intent + emotion), PLANNED into the NEEDS it declares (plan.py —
empathy / code / facts / transform / …), GATHERED (retrieve exactly those), and only then SPOKEN.
The weights’ whole job shrinks to the last step; the knowing lives in the owned, deterministic
composer. Each lane is a fully-wired seam, and each carries its own verifier:
- Style transfer (
speak_packs.py) — verifiable, blendable register shifts (pirate → gen-z); the whole 31,095-verse Bible into pirate in 4s, meaning-graded. - The emotion lane (
empathy.py) — nine states (grief, health, rejection, setback, fear, guilt, confused, gratitude, pride, relief, hype, anger + crisis→988), each a composed stance a verifier enforces: no cheering at grief, no defending against anger, no wet-blanketing hype. Reads valence from words and events (“I broke my arm”), and tells “FUCK YES” (hype) from “FUCK YOU” (anger) by what follows. The composer-layer answer to B.18 — a 5M can’t hold valence in its weights, so it lives here. - The reasoning stack (
multistep.py→tools.py→planner.py) — a question decomposes into a recursive step-TREE; leaves EXECUTE (calc/code/sandbox), RETRIEVE (library), or ABSTAIN; state carries across steps and status propagates up, so a step werbos can’t run stays✗(honest partial — the “then hack the mainframe” branch never gets a checkmark). Reasoning by execution, not chain-of-thought narration — every step actually ran, or it’s honestly unchecked.
The through-line, and the whole bet stated one more way: the composer is where the mind lives — the weights only render what an owned, verifiable stage already decided.
Two more composition engines (Day 137): knowledge in, language out
The brick idea — typed pieces that interlock into a verified whole — isn’t just for computation. It runs both other sides of the system, mirror images of each other:
- Knowledge acquisition (
werbos/knowledge/). Not APIs-for-questions but adapters for domains: a capability index of ~100 free sources across two-dozen domains, each tagged by what it covers and how to reach it (hosted-offline / consumed-live / searched / owned-card). A universal, guarded HTTP caller sits under all of them; small static datasets (periodic table, countries) answer offline. The payoff is the acquisition loop: one question resolves an entity and harvests its whole neighbourhood into cards — “capital of Australia?” mints capital + currency + region + language — so the library grows itself from use and adjacent questions then answer offline. This isresolve()’s SEEK → CACHE at the domain scale; the API is the SEEK, minting the CACHE. The Wikidata consume-harvester unbounds this to any entity (single-entity/targeted — the precise regime — with an ambiguity abstain), emittingis-arelations as the layer a reasoner walks. Anddescribeis the interlock: harvested facts → rhetorical bricks → 1B voice, grounding-guarded, so the two engines meet on one seam. - Language composition (
compose/convo_bricks.py,rhetoric.py,tongue.py). A reply is a small vocabulary of rhetorical moves (observe · name · reframe · contrast · generalize · exemplify · recommend · riff · advice · close, plus the contract-carrying reflect/validate/normalize/plan/verify/ boundary) chained into a program, the moves interlocking exactly like the compute bricks. The program is testable without the model; the 1B is wired as voice only (surface realization, grounding-guarded — it strings the grounded beats, never invents), while the lanes fill the content. Programs are now tunable per prompt type:therapyends on a boundary,codingon a verify,life_riffnever abstains,small_talkstays minimal — the shape itself carries the contract.program_for(intent)picks the shape;respond(context, intent, think=, voice=)is the one call a lane makes. - Talk bricks (
compose/talk_bricks.py+talk_lexicon.py+talk_families/, Day 147 — on a branch, not yet in the live path). The other half of language composition: bricks that fill themselves from the user’s own words and cards, with no model at serve time — same line in, same reply out. A line is read into slots (act, hype, subject, kind, what you already said), each clause runs a pattern of bricks (Seq, andEachfor recursion — a decision’s breakdown is one brick per factor), and a thread carries the subject across turns. 35 situation-space families (invites as states, travel, work/school, relationships, holidays, media…), ~5,250 bricks. The floor is shared: crisis, harm and serious-health lines never get a brick. A clause no brick can answer becomes a lane — a hole the right engine fills (code that ran, a cited card, a counted web answer, or a labelled guess), so one reply mixes bricks with lookups and the thread survives the detour. - Madlib bricks + conversation shapes (
compose/madlib_bricks.py,plan_bricks.py,decision_bricks.py,convo_shapes.py, Day 147; merged to main Day 159, not yet deployed). The talk bricks above are pre-written strings and lost to production in front of the judge; these replace strings with shapes. A singular brick (observation, plan) reads a line into slots, evaluates the subject (WordNet → cards → web → an honest unknown), picks an affordance and fills ONE madlib frame — under ten frames, no word lists. That’s depth. Breadth is composites built from section bricks, four levels: arcs across turns (TEACH, SUPPORT, DECIDE) → long replies (IRSAF, COMPARE, EXPLAIN, IHDS, COMFORT) → short reactions (CELEBRATE, ICD, REPAIR, RECOMMEND) → the sections themselves, each with a contract (claims backed or dropped to a gap list; one follow-up; hype matched, never escalated; a pushback held only on two independent sources). OneConversationroutes: reactions → shapes → singular bricks underneath. Each level has its own fuzzer; the section contracts are a deterministic ratchet (section_audit.py). By Day 159 the stack also holds event / state / feeling / outcome bricks with tone tiers (a loss, bad news and good news each get their own register — never “oh nice” on a grief line), master templates (TRIP, WRITE, EDIT, TROUBLESHOOT, PLAN, CODE_DEBUG, TAROT), the owned compute lanes wired into chat (units, arithmetic, dates, an everyday lane, saved recipes) and a safety floor (poison, carbon monoxide, never a dose guess) that runs before any tongue. The tongue (convo_tongue.py) only writes what the bricks plan; when a factual line has no facts,card_rewrite.pylets it act as librarian — rewrite the question as keywords, judge whether a hub card fits — and the reply quotes the card. Snippet and troubleshooting content is library data (snippet_library/*.json,procedure_library/), never code. Measured by the structure ratchet (see /flow HL.4).
COMPUTE nums → filter → map → reduce → answer
KNOWLEDGE api → facts → cards → relationships → reasoning
LANGUAGE thought → semantic bricks → rhetorical bricks → voice → sentence
Three orthogonal dials on the language side — intent picks the shape, the lane fills the bricks, the tongue voices them. Same machinery on all three sides — compute, knowledge, language. The intelligence is the composition; the model is the tongue.
See also
- The mind — werbos’s mind drawn as a radial: the capability axes, what each measures, and the ghost spokes it can’t measure yet.
- The full original SPA — llm-map/index.html in the monorepo has the deeper sections (acronyms, gotchas, hardware fleet, repo map). Will be ported into per-page Hugo content as the site grows.
- The advancements graph — visual dependency map of every milestone shipped.
- The log for what’s actually happening day-to-day.