Persona: send chat_read for the newest placed event per room at the echo #28

Merged
eureka merged 1 commit from eureka/chat-read-receipt into main 2026-11-10 14:55:01 +00:00
Owner

Heimdall's Bifrost #35 (mouth read-through) waits on this: Huginn sends her read receipt when the model's input holds the event.

What it does

  • New document gamecult.persona.chat_read.v1, positional [persona, room, event_id, at_unix_ms], ChatRead beside ChatAck in crates/mind-body/src/chat_client.rs, Chat::read, sent by RudpChat::read over the same loopback RUDP endpoint as chat_ack (no answer awaited).
  • Sent from PersonaInbox::echoed (the seam: at a60e83f persona.rs echoed() 1593, held[room] insert 1596-1600, the one ack at 1661), i.e. when the CLI has taken the line. One report per room per echoed batch. Not sent at write, not from the ack path.
  • report_read keeps a per-room "reported through seq" so a retold or revised batch never repeats an event or moves a receipt back. It advances only on a report the mouth took.
  • A failed or refused report is dropped: the receipt is cosmetic, the ack stays the delivery guarantee, the session does not fail.

Decision: newest placed event per room, no ids on earlier lines

Only the newest event id per room is reported. Every delivery of the echoed batch is a message line of the input and the earlier lines come before it, so the newest message is the newest event the model saw; queued-unread deliveries are not in the batch and are never reported. Event ids are NOT added to earlier prompt lines: a receipt needs only the latest event the model saw, and per-line ids grow the prompt for no receipt benefit. (Same text is in the code comment on report_read.)

Room identifier: the delivered room id, not the alias

echoed() holds held[room_alias] = (seq, event_id), keyed by alias, because send addresses by alias. chat_ack carries no room at all (persona and seq only). The delivery also carries room_id, and the report sends that (second tuple slot; the field is named room, the wire is positional). Reason: the mouth must post m.read to the delivered room, and a room id names exactly one room with no lookup; a direct room has no alias (its alias is its id). Bifrost's read() at heimdall/mouth-read-through 0a44bf8 accepts either known.roomAlias or known.roomId for the event and posts to known.roomId, so the id is accepted there. The mouth and Bifrost are untouched.

Tests (cargo test -p mind-body, run under unshare -r because the control-store tests need root-owned temp dirs)

  • a_read_is_sent_only_at_the_echo_one_per_room_for_the_newest_event_that_went_into_her_input: fails if a report names an event whose text is not in the prompt (including a queued-unread one), sends before the echo, sends more than one per room, names the alias, or puts event ids on earlier lines.
  • a_line_told_and_not_echoed_reports_nothing_and_a_report_is_never_repeated_or_moved_back
  • a_read_the_mouth_does_not_take_is_dropped_without_ending_the_session_and_is_tried_again_with_the_next_batch
  • a_read_reaches_the_mouth_as_the_positional_tuple_persona_room_event_instant (RUDP, tests/chat_rudp.rs)
  • tests/public_surface.txt updated for the new trait method, struct and const.
    Hand mutants (cargo-mutants not installed; run once, not committed), each caught: newest->oldest per room, forward guard off, advance on failed report, alias instead of room id, no report at all at the echo, report at write instead of echo, failed report ends the session.

Deviations / open

  • Slot 2 carries a room id, where the follow-up named room_alias (wire position unchanged).
  • earlier-only events cannot be reported (ContextLine has no event id); out of scope by design.
  • Not run: the unsafe_installs integration test fails under unshare -r (uid 0 artefact of the namespace; it does not touch this change).

🤖 Generated with Claude Code

Heimdall's Bifrost #35 (mouth read-through) waits on this: Huginn sends her read receipt when the model's input holds the event. ## What it does - New document `gamecult.persona.chat_read.v1`, positional `[persona, room, event_id, at_unix_ms]`, `ChatRead` beside `ChatAck` in `crates/mind-body/src/chat_client.rs`, `Chat::read`, sent by `RudpChat::read` over the same loopback RUDP endpoint as `chat_ack` (no answer awaited). - Sent from `PersonaInbox::echoed` (the seam: at a60e83f `persona.rs` `echoed()` 1593, `held[room]` insert 1596-1600, the one ack at 1661), i.e. when the CLI has taken the line. One report per room per echoed batch. Not sent at write, not from the ack path. - `report_read` keeps a per-room "reported through seq" so a retold or revised batch never repeats an event or moves a receipt back. It advances only on a report the mouth took. - A failed or refused report is dropped: the receipt is cosmetic, the ack stays the delivery guarantee, the session does not fail. ## Decision: newest placed event per room, no ids on earlier lines Only the newest event id per room is reported. Every delivery of the echoed batch is a `message` line of the input and the `earlier` lines come before it, so the newest message is the newest event the model saw; queued-unread deliveries are not in the batch and are never reported. Event ids are NOT added to earlier prompt lines: a receipt needs only the latest event the model saw, and per-line ids grow the prompt for no receipt benefit. (Same text is in the code comment on `report_read`.) ## Room identifier: the delivered room id, not the alias `echoed()` holds `held[room_alias] = (seq, event_id)`, keyed by alias, because `send` addresses by alias. `chat_ack` carries no room at all (persona and seq only). The delivery also carries `room_id`, and the report sends that (second tuple slot; the field is named `room`, the wire is positional). Reason: the mouth must post `m.read` to the delivered room, and a room id names exactly one room with no lookup; a direct room has no alias (its alias is its id). Bifrost's `read()` at heimdall/mouth-read-through 0a44bf8 accepts either `known.roomAlias` or `known.roomId` for the event and posts to `known.roomId`, so the id is accepted there. The mouth and Bifrost are untouched. ## Tests (`cargo test -p mind-body`, run under `unshare -r` because the control-store tests need root-owned temp dirs) - `a_read_is_sent_only_at_the_echo_one_per_room_for_the_newest_event_that_went_into_her_input`: fails if a report names an event whose text is not in the prompt (including a queued-unread one), sends before the echo, sends more than one per room, names the alias, or puts event ids on earlier lines. - `a_line_told_and_not_echoed_reports_nothing_and_a_report_is_never_repeated_or_moved_back` - `a_read_the_mouth_does_not_take_is_dropped_without_ending_the_session_and_is_tried_again_with_the_next_batch` - `a_read_reaches_the_mouth_as_the_positional_tuple_persona_room_event_instant` (RUDP, `tests/chat_rudp.rs`) - `tests/public_surface.txt` updated for the new trait method, struct and const. Hand mutants (cargo-mutants not installed; run once, not committed), each caught: newest->oldest per room, forward guard off, advance on failed report, alias instead of room id, no report at all at the echo, report at write instead of echo, failed report ends the session. ## Deviations / open - Slot 2 carries a room id, where the follow-up named `room_alias` (wire position unchanged). - `earlier`-only events cannot be reported (`ContextLine` has no event id); out of scope by design. - Not run: the `unsafe_installs` integration test fails under `unshare -r` (uid 0 artefact of the namespace; it does not touch this change). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
One gamecult.persona.chat_read.v1 [persona, room, event_id, at_unix_ms] per room per echoed batch,
sent over the same RUDP endpoint as chat_ack, from PersonaInbox::echoed (the CLI has taken the line).
Newest placed event only; no event ids on earlier lines. Display-only: a failed report is dropped.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
eureka merged commit 1a04d0efd9 into main 2026-11-10 14:55:01 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/Huginn!28
No description provided.