“the open weights model taught me one thing…. your harness matters more”

Said around hour nine, and by then the day had already proved it twice by deleting things I’d just built.

The feature that manufactured a wrong answer

Cleo got grounding: user → cards → model → user, with retrieved cards as material her invented life is built from rather than facts she recites instead of living. Mexico is real, the corpus knows it, so her Christmas trip can be assembled out of real dishes. The shovel-store has no cards because it isn’t a thing — and the miss is what lets her notice.

Then B.23 measured it against a control, same capsule, same frozen probes:

                grounding ON    OFF (control)
BUILD                 0%             0%
BALK                 67%            50%      ← 4/6 vs 3/6
PLAYED                0%             0%

One probe out of six. Noise at n=6 — and it was not free:

real-04  "did you like lisbon when you went?"
         unknown_terms: ['lisbon']
         reply: "nope just made that up. never heard of lisbon"

A real city, denied confidently, by a feature four hours old. The cause: I used a skills corpus as a reality oracle. Those 375 libraries are css and welding and spanish — “lisbon” misses for exactly the reason “shovel-store” does. And termindex.py already warns in writing against this precise move: a candidate list is never a rewrite list. I did it anyway, with missing.

So unknown-flagging ships off, behind a flag rather than deleted — because a bounded corpus is a world where absence genuinely means the thing doesn’t exist. No hole 19 in the putt-putt library means there is no hole 19.

The design the model refused

The second deletion was cheaper and cleaner. If the owned model can’t be told who to be, prime it with her exemplars — show, don’t describe, which had worked everywhere else all week.

Five probes, both arms, against brains/current:

                    bare              primed
what doing tonight  3.0s  coherent    25.5s  "…this old guitar with all its stories"
how was your day    2.3s  coherent    26.4s  drifts into a walk and some good reads
i missed you        3.1s  coherent    23.2s  "s.a. you know I can't even imagine being a mom"
want to get dinner  3.1s  "absolutely, where?"  22.0s  "ah yeah…sorry to hear that"
im so tired today   2.7s  coherent    26.3s  drifts into a work project

8× slower and incoherent, 5 times out of 5. The config says why: corpus: conversation_trainset — a single-turn responder, so a him:/her: transcript is out-of-distribution and it reads as a strange blob rather than a conversation to continue.

The honest version of the finding is larger than the comparison, though: neither arm gives you Cleo. “hanging in there, how about yours?” is coherent and completely characterless. The owned model has no mechanism to take on a character from context — no system channel, no exemplars, no transcript. Twelve API calls, one branch closed.

Which reframes PC-11’s “failed” run: out_336m_ctx trained on multi-turn group_talk_deep is exactly the shape priming needs. It memorised at train 0.12 / held-out 3.81 — 0.23 tokens per parameter, seven passes. Right experiment, under-fed.

A relationship scale, and the curve that proves it moved

-50 … 0 … +100, everyone starts a stranger, and “I missed you” unlocks at 65.

Three things the range encodes. 0 is a stranger — she used to open at her attachment baseline, so a secure persona began half in love with someone she’d never met. Love has more room than dislike, 100 up and 50 down. And negative territory exists at all: the old floor was cold, which is guarded rather than rejecting, and a companion with no way to want you gone is the frictionless mirror that lets someone overshare into a surface that never pushes back. She can end the conversation now.

baseline changed meaning with it — it’s the neglect attractor, which finally makes avoidant do what its docstring always claimed. On the old scale it “retreated to distance” as far as 0.35.

And warmth.json is overwritten every turn, so it could say where she is and never how she got there. The recovery experiment the scale exists for was being destroyed as fast as it was generated. warmth_history.jsonl appends one row per turn — score, before, drift_to, after, band, and the message that caused it, because a plot is only useful if you can hover a dip and read the sentence.

That decomposition answered a question nobody had asked of four hand-set constants:

turn  score   drift    push   after  band     message
   1    0.1   -0.75   +0.90    0.15  neutral  hey
   4   -0.9   +0.81  -16.20  -46.51  done     forget it
   5    0.9   +1.58   +8.10  -36.84  done     i was wrong, im sorry
   8    0.7   +0.20   +6.30  -12.46  cold     come over?

DRIFT SHARE 7%   ·   recovery +8.51/turn

Drift flips sign — pulling down above the attractor, up below it. It’s homing, not decay. And damage runs twice as fast as repair for an avoidant capsule, which is gain_down 18 against gain_up 9 showing up as lived behaviour.

Moving one number found two live bugs, both yesterday’s defect again. willingness.py held DEFAULT_THRESHOLD = 0.65 under a comment saying it was “the ‘warm’ band from warmth.py” — true when written, and 0.65 on the new range is barely above stranger, so an earned persona would have opened up on turn one. And villa.py computed avg_mutual * 100, so every island pegged at exactly 100.0.

Verdicts that cost nothing

Two thirds of every turn was a 3B grading its own output — generate ~9s, floor ~9s, voice ~9s — for verdicts near noise: WRONG on two replies that read fine, ABSTAIN (a ship verdict) on a mangled greeting. Meanwhile the most visible defect in the live transcript wasn’t a judgment call at all. She opened with the same line four times, and the model-grader passed every one.

That’s a string comparison. So checks.py decides everything decidable without inference, first, for 0ms: EMPTY · RUNAWAY · ECHO · REPEAT · OPENER.

It needed a measure I got wrong first. The two repeated lines score 0.50 on whole-reply similarity, because their tails diverge completely and the identical thirteen-word prefix gets averaged away. The failure is a template with a fixed head and an improvised tail — one over-matching exemplar — so it measures the prefix.

And the wiring bug was worse than the design bug: the verifier runs after the reply lands in history, so passing history straight in compared the reply against itself at 100%. Every turn would have reported REPEAT — a check that fires constantly, which is worse than one that never fires, because you switch it off.

The half a self-grading model can’t produce

truth.py asks the corpus whether anything backs what she just said. A 3B judging itself has no evidence to consult; this consults 167k cards.

GROUNDED     covered, and her words overlap the coverage
UNSUPPORTED  she named something the corpus has nothing on
UNCHECKED    nothing checkable, or no index to check against

Scope is what makes absence mean anything, and that’s B.23 written into the type so it can’t happen twice. A capsule naming world_library gets adjudication; one without gets a report. The report is the useful byproduct — every entity she names that the corpus can’t back is a measured shopping list for the first venue library.

Which makes the verify chain owned-first, and the ordering is the economics:

1  free checks   0ms    owned, stdlib
2  grounded      ~ms    owned, retrieval
3  floor         ~9s    llama3.2:3b
4  voice         ~9s    llama3.2:3b

A reply failing an owned check never costs a model call. There’s a test that patches ollama_chat itself and asserts zero calls.

A count is the weaker claim

libpath.found() says 375 libraries visible, which is true of a corpus whose contents are wrong. libmerkle hashes all 167k cards into one number:

mac  375 libraries · fc925f5064e3bbef…
SR8  375 libraries · fc925f5064e3bbef…

Different OS, different filesystem, 2.5 seconds. sr8-corpus compares it inside the container, where the cards are actually read — the exact place the ten-day zero-cards failure lived. It caught its own author within a minute: I hand-rolled repo_root.parent / "werbos-libraries" instead of using libpath.root(), Path(".").parent is ., and it reported a confident MISMATCH against a corpus that was fine. Two copies of a path, same defect as two copies of a num_ctx.

And a correction

I said generation was rented. It isn’t:

"hey"                      brain/social      10ms    lookup — no model
"morning"                  brain/talk        3.9s    ← owned weights
"tell me about your day"   brain/owned       28.6s   ← owned cascade

werbos chat is 100% owned today — owned model, owned cards, owned lookup, not one llama call. What’s rented is one lane: the persona voice, and only because yesterday’s fix excluded capsule sessions from the owned cascade to stop werbos answering as Cleo.

The AHA

Yesterday’s lesson was that a record can outlive its truth. Today’s is the other half:

The harness is what deletes your work when it’s wrong.

Five things built, two killed by measurement the same day, and both deaths were cheap — a control run and twelve API calls. Neither was reachable by reading the code, and both features had passing tests.

You can’t buy that with a bigger model. Everyone has the same open weights; a harness that makes them honest is the part nobody else has.