Causal memory slice B + grader-hears-the-store bridge (#447, #450) - #2322
Merged
Conversation
…+ room-scoped archive + scope-first ledger (#447) Receipt heads now store the compact call form (name(args), 48-char arg cap) instead of full prose dragging its ~250-char because-clause — ~5x more act history in the same char budget. Heads carry an Option<room> tag; the StepsLedger renders scope-first (this room's acts at real depth, cross-room remainder as one honest line, unscoped heads never hidden at the upgrade boundary). VolatileSnapshot round-trips the room tags via a parallel receipt_head_rooms array (serde-default, old snapshots restore as unscoped). Also fixes a 100%-reproducible SELF-DEADLOCK found by the suite: snapshot() took receipt_heads.lock() twice inside one struct literal — the first guard (a literal temporary) lives until the whole expression ends, so the second lock waits on ourselves forever. Every deploy-pause snapshot would have wedged. One lock now builds both parallel arrays. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…eChanged onto the bus, single emitter (#450) Found positive-controlling the done→grade tail with Asha's real rle_roundtrip artifact: 'airc work state <card> closed' changed the board and reached every persona's stream, but benchmark_grade never fired — it subscribed to the internal bus event only continuum's work/state verb emitted. One fact, two disjoint event planes: a card closed by the operator CLI or a remote peer graded NOTHING. bridge_wire_work_event in modules/work.rs is now THE one emitter of work.card.state_changed: pure decode via airc-work's real codec (transcript_is_work_event → decode_transcript_work_event → CardStateChanged), once-per-event-id dedup (every resident persona's subscribe stream echoes the same wire event), publish through the existing WORK_EVENT_BUS. Called from the persona inbound raw-event loop — the only channel-complete receiver this core has (the daemon attach covers ONE room) — before the perceptual filter and the self-skip, so a citizen's own work/state echo counts. The verb-side emit is deleted; payload upgrades to the full hyphenated card UUID + snake_case CardState serde form (the exact is_terminal/parse_state vocabulary). Tests pin the decode contract against the real encoder, the non-state/non-work refusals, and the first-sighting dedup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…dict IN the run room, glass-box every step (#450) Live diagnosis after the bridge fired for Atlas's real balanced_brackets close: the grade task parked forever with zero receipts. grade_card read the board via work_board_complete — which airc-lib's own doc names as the #345 wrong-room trap: it resolves current_room(), i.e. whatever room the grading citizen sits in (academy), not the bench room the card lives in. The bridge payload now carries the event's room_id (the card's own room — the wire event knows). grade_card resolves that room from the citizen's subscription set (same pattern as claim_following_card_room), reads work_board_in(&room), and posts the verdict via publish_text_in_room into the RUN room the citizens are standing in — Joel's #329 slice-3 targeting, no more current-room dependency anywhere in the grade path. A missing/unresolvable room fails LOUD instead of grading a guessed board. Glass box per Joel ('this is why we have probes'): benchmark_grade.start, benchmark_grade.verdict (both arms), benchmark_grade.error — the tail can never again park silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… a dead dispatch path and never once fired (#450) Third and final dead link in the done→grade tail, proven live: two bridged publishes reached the bus, zero handle_event probes. Root cause is the runtime's own documented trap (runtime.rs:99): config().event_subscriptions installs synchronous-tier subscriptions the registry marks synchronous=false — which publish() filters OUT. 'The (currently unused) synchronous publish path.' Grade-on-done was born on it (#439) and has never fired in production. Fix is the runtime's own prescription: initialize() spawns a bus-receiver task (same shape as chat::spawn_persist_listener) filtering WORK_CARD_STATE_CHANGED; the reaction logic is extracted into on_card_state_changed, shared with handle_event (kept for the synchronous tier should it ever dispatch). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…anual flip becomes algorithm (#451) The first graded pass needed an operator flip: Atlas wrote his artifact but the grade only ran when a human closed his card. The sweeper encodes exactly that hand-move: benchmark_grade ticks every 180s (a periodic actuator — lease expiry is a time fact with no wire event) and closes any bench-titled card whose claim LAPSED (card_holder::hold_of, the one lease predicate) with an owner and a real artifact behind it (gym: non-empty solution_file; SWE: dirty workspace tree — an untouched clone graded would burn a fake capability-zero, the #384 class). The close is the whole action: its wire echo drives the proven bridge → bus → receiver → grade tail. ONE grade path; the sweeper only detects. A live claim is never preempted; a citizen's own done still beats the sweep; normal work cards are never touched; ≤3 closes/tick so a backlog trickles instead of storming the room. Provenance note posted before each close. sweep_ready is a pure truth table with a pinned test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… success, loud error probes (#451) First live tick: 21 sweep_close probes in 0.6s, sailing past the 3-close cap. The cap only counts SUCCESSES, so 21 probes means the closes themselves were refused — and the Err path had no probe, so the entire failure mode was invisible (my own glass-box rule, violated in my own module). Worse, the provenance note posted BEFORE the close attempt: a persistently-failing close would have spammed every old bench room once per tick. Reordered: change_work_card_state first; provenance note only on Ok (still lands before the verdict — grading takes seconds); sweep_close_failed probe with the refusal error on Err. Next tick's probes will name WHY airc refuses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
… — the WRITE half of #345 (#451) The instrumented tick named the refusal in one pass: 'work card X is not in current room …; switch to the card's room before mutating it' — airc's change_work_card_state guards against the author's CURRENT room, while the sweeper's targets live in per-run bench rooms. Reads grew room-scoped variants (work_board_in); mutates never did. Fixed at the native seam (contribute-up): airc #1363 adds change_work_card_state_in(room, req) — same guard strength, room as a parameter — merged to airc canary as 103fd898. Continuum: bump airc rev 574ce235 → 103fd898; sweeper closes via the room-scoped verb against the room whose board it just read. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two slices, one funnel
Slice B (#447): receipt heads store the compact call form (~5x more act history per char budget), room-scoped archive with scope-first StepsLedger rendering, snapshot round-trip of room tags. Also fixes a 100%-reproducible self-deadlock: snapshot() locked receipt_heads twice inside one struct literal — every deploy-pause snapshot would have wedged (caught as a permanent test hang).
Grader bridge (#450): benchmark_grade subscribed to a bus event only continuum's work/state verb emitted — a card closed by the airc CLI or a remote peer changed the board and graded nothing (found live with Asha's real rle_roundtrip artifact). bridge_wire_work_event is now THE single emitter of work.card.state_changed: decode the store's own wire echo via airc-work's codec, once-per-event-id dedup, publish through WORK_EVENT_BUS. Called from the persona inbound loop before the perceptual filter and self-skip. Verb-side emit deleted.
Tests: 16/16 work+benchmark_grade (decode contract vs the real encoder, non-state refusal, dedup), 66/66 working_memory+perception_facts+act_observe. Deployed to the M5 (deploy-verified e3e44f1); bridge live proof rides the next dispatch round (citizens spawned into the run room).
🤖 Generated with Claude Code
https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo