fix(cognition): a citizen's own lived turns become curriculum — producer wired INTO the settle driver (#319) - #2337
fix(cognition): a citizen's own lived turns become curriculum — producer wired INTO the settle driver (#319)#2337joelteply wants to merge 2 commits into
Conversation
…become curriculum, not just graded cards (#319) `ExperienceRecord::from_lived_turn` had ZERO production callers tree-wide. The only live producer was `from_kanban_grade` (benchmark.rs:1989), so the experience stream — "the SPINE the salience→curriculum seam was missing", in `experience_stream_path`'s own words — was fed EXCLUSIVELY BY GRADES. A citizen could hold a thousand real conversations and her stream stayed empty. Nothing could learn from work nobody had scored. Everything needed already existed and was correct: the record shape, the two magic-number-free lived-salience signals (infra fault, non-convergence), `ErrorSalience` reading them with no new detector, and a doc block naming "the lived-turn settle site" as a producer. The CALL was missing. Same shape as #341 (boot called the unbounded sink; the rotating one had zero callers) and #362 (ProbeRouterLayer installed with its handle discarded) — a built component with a dead wire, which is why [[an-absence-is-an-unfinished-measurement]] keeps earning its place. WIRED at `service_loop.rs`'s live settle site, BEFORE `SettleStep::from_settled` consumes the outcome — the only point where a lived turn's settle verdict exists. The driver stays a driver per PERSONA-COGNITION-PIPELINE §3: one call, no policy. The behaviour lives in `experience::record_lived_turn` so it is unit-testable without booting a loop. BEST-EFFORT, and that is NOT a fallback ([[no-fallbacks-ever]] holds): nothing is substituted and no result is fabricated. Learning is a SIDE channel to being — a full disk must not make a citizen mute mid-sentence. The failure is WARNED with the path and the honest consequence ("this episode will not become curriculum"). COMPRESSION, because the new writer would otherwise have been the FIFTH hand-rolled spelling of the citizen storage layout. `citizens/peers/<uuid>` was written as `join("citizens/peers")` in modules/work.rs, `join("citizens").join("peers")` in commands/benchmark.rs, and in prose in persona_workspace.rs + persona_roster.rs. Now ONE resolver, `identity::citizen_peer_dir(root, peer)`, in the module whose own doc says "the whole crate imports identity from ONE home" and warns against exactly this re-invention. Keyed by `PeerId`, never a String — the directory name IS her identity. Pure path arithmetic, so a read-only caller never mints an empty citizen dir, which its test pins. Tests: 11/11 cognition::experience (the lived-turn test now asserts the PRODUCER — empty stream → record lands at the canonical path → a second turn appends rather than overwrites, i.e. the wire, not just the record's shape) + identity's one-spelling test. `cargo check -p continuum-core --features metal,accelerate` clean. LIVE PROOF OWED and NOT claimed: no live turn has run on this build. The falsifiable prediction is that a citizen's `citizens/peers/<her>/experience.jsonl` gains a `LivedTurn` record on her next real room turn, where today it holds only kanban grades or nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… not at one call site (#319) MEASURED, and it retracts my own claim from 200dab984. I wrote that the #319 producer was wired and that a LivedTurn record was owed on the next real turn. The wire was wrong, so the record could never come: 180 experience records across 5 citizens on this box, 100% `source: "Eval"`, ZERO `LivedTurn` — while the same core showed 47 persona.upstart.bind, 97 presence rows and 4 delib.context.render, i.e. citizens alive and deliberating. ROOT CAUSE. `record_lived_turn` was called from ONE of the THREE `drive_to_settle` sites in service_loop — the directed-message path (:1087). The self-tick path (:2628) and the held-work path (:1207) settle turns through the same driver and recorded nothing. Three callers, one remembering: a missing constraint expressed as three sites, not a bug at one ([[the-same-bug-at-two-sites-is-a-missing-constraint]]). FIX. The record moves INTO `drive_to_settle`, which is the one place a `SettleOutcome` is born. `drive_to_settle` is now a thin wrapper over a private `settle_to_outcome`, so the write happens ONCE around the driver rather than at its four return paths — a fifth return path added later inherits learning instead of silently opting out, and no call site carries learning policy. Gated on `WorkspaceCycle::acting()`, because that is where a citizen's identity lives: a cycle with no `ActingBody` is pure cognition (a faculty test, a replay) and is nobody's lived experience. Structural absence, not a skipped write. SECOND FIX, applying Joel's rule that a required thing should not be re-expressible: dropped the `stimulus: &str` parameter. It was a second, independent expression of "what she was responding to", free to disagree with what the turn actually perceived — and it already did, since the old call site passed only `msg.text` while the mind had settled over the whole rendered burst. The stimulus is now read off `SettleOutcome.world_state` (set on every return path), so the disagreement is unrepresentable rather than merely discouraged. The test asserts `task.prompt == settled.world_state` instead of a literal. GUARD, positive-controlled (not merely green): a source-walking test asserts `record_lived_turn` has EXACTLY ONE production caller and that it is settle.rs. Verified failing — injected a second call into service_loop and it reported `found 2: [settle.rs, service_loop.rs]` with the remediation in the message. Reverted. Comment-stripped so prose naming the function cannot read as a call (registry.rs's module-wiring audit is the house precedent). cargo check clean; cognition::experience 12/12, cognition::act_observe 38/38. LIVE PROOF STILL OWED and NOT claimed: no turn has run on this build yet. Baseline for falsifiability is recorded above — 180 records, 100% Eval, 0 LivedTurn. The prediction is that a hosted citizen's `citizens/peers/<her>/experience.jsonl` gains a LivedTurn record on her next settled turn from ANY of the three paths, where today it holds only kanban grades. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
Coverage audit: is
|
Correcting my own reading above: it was NOT latency. It is lane-admission starvation.I wrote "this reads as latency, not a blocked path" in the previous comment. That was The measurement. Over ~25 minutes on
Frozen counters, not slow ones. Latency would have moved them. Mechanism, read in-file ( sem.acquire_owned().await // unbounded parkWith 24 hosted citizens, one self-tick holds the single non-directed permit and the Why this matters beyond #319. The four self-ticks that DID perceive never reached any What I did about the measurement, per The single non-directed permit is now uncontended. Watcher still armed; still owed, still Lesson for me, and it is the same one twice today: a frozen counter and a slow counter |
Live proof: BLOCKED by box state, not by this change. Watcher stopped; nothing claimed.Quiescing 24 → 1 did not relieve the park. What is actually holding this box ( That is #440's frontier bring-up — a different in-flight lane, not persona serving. So the honest state of the proof: this box cannot currently produce an ambient settled Unit evidence stands (12/12 + 38/38, guard positive-controlled by injection). Live Cheapest route when someone picks this up, in order:
Roster left at 1 (Anon). Independent finding worth its own card (#446/#139 family): an unbounded |
What was broken
ExperienceRecord::from_lived_turnhad zero production callers tree-wide. The onlylive producer was
from_kanban_grade, so the experience stream — "the SPINE thesalience→curriculum seam was missing", per its own doc — was fed exclusively by
grades. A citizen could hold a thousand real conversations and her stream stayed
empty: the curriculum could only ever learn from work someone had scored.
Measured on this box, before: 180 experience records across 5 citizens, 100%
source: "Eval", ZEROLivedTurn.Two commits, because the first one was wrong
0a34ea083wired the producer at the service_loop call site.08ccd6139corrects it, and retracts my own claim from the first commit.
The first wiring called
record_lived_turnfrom one of the threedrive_to_settlesites inservice_loop— the directed-message path (:1087). Theself-tick path (
:2628) and the held-work path (:1207) settle turns throughthe same driver and recorded nothing. So the owed proof could never arrive: the same
core showed 47
persona.upstart.bind, 97 presence rows and 4delib.context.render— citizens alive and deliberating — with the record count still flat at zero.
Three callers, one of them remembering. That is a missing constraint expressed as three
sites, not a bug at one.
The fix
The write moves into
drive_to_settle, the one place aSettleOutcomeis born.drive_to_settlebecomes a thin wrapper over a privatesettle_to_outcome, so therecord happens once around the driver rather than at its four return paths — a
fifth return path added later inherits learning instead of silently opting out, and no
call site carries learning policy.
Gated on
WorkspaceCycle::acting(), because that is where a citizen's identity lives.A cycle with no
ActingBodyis pure cognition (a faculty test, a replay) — nobody'slived experience, so there is no stream it belongs in. Structural absence, not a
skipped write.
Second fix: a required thing should not be re-expressible
Dropped the
stimulus: &strparameter. It was a second, independent expression of"what she was responding to", free to disagree with what the turn actually perceived —
and it already did: the old call site passed only
msg.textwhile the mind had settledover the whole rendered burst. The stimulus is now read off
SettleOutcome.world_state(set on every return path), so the disagreement is unrepresentable rather than
merely discouraged. The test asserts
task.prompt == settled.world_stateinstead of aliteral.
Guard, positive-controlled
A source-walking test asserts
record_lived_turnhas exactly one production callerand that it is
settle.rs. Verified failing: injected a second call intoservice_loop and it reported
with the remediation in the message. Reverted. Comment-stripped so prose naming the
function cannot read as a call (
registry.rs's module-wiring audit is the houseprecedent). The walk also asserts it found files at all — a vacuously-green guard is
worse than a red one.
Validation
cargo check -p continuum-core --features metal,acceleratecleancognition::experience12/12,cognition::act_observe38/38✅ deploy verified: core is running build 08ccd6139 (== git HEAD)Live proof
Owed, and not claimed here. Falsifiable prediction with the baseline recorded
above: a hosted citizen's
citizens/peers/<her>/experience.jsonlgains aLivedTurnrecord on her next settled turn — from any of the three paths, which is what the
correction buys. A watcher is armed; the result gets posted to this PR either way.
Note the self-tick path matters for provability independent of #434: it settles on her
own metronome, so the proof does not depend on inbound chat delivery.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo