Skip to content

Causal memory slice B + grader-hears-the-store bridge (#447, #450) - #2322

Merged
joelteply merged 7 commits into
canaryfrom
feat/causal-memory-graph-slice-b
Aug 16, 2026
Merged

Causal memory slice B + grader-hears-the-store bridge (#447, #450)#2322
joelteply merged 7 commits into
canaryfrom
feat/causal-memory-graph-slice-b

Conversation

@joelteply

Copy link
Copy Markdown
Contributor

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

joelteply and others added 2 commits August 15, 2026 19:30
…+ 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
joelteply and others added 2 commits August 15, 2026 20:31
…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
@github-actions github-actions Bot added size: XL and removed size: L labels Aug 16, 2026
joelteply and others added 3 commits August 15, 2026 21:19
…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
@joelteply
joelteply merged commit f7bc52f into canary Aug 16, 2026
5 checks passed
@joelteply
joelteply deleted the feat/causal-memory-graph-slice-b branch August 16, 2026 04:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant