Files
boxmaker/docs/plans/M4a/13-gatewayd-deliver.md
T
kyleandClaude Opus 5.5 0339dc13b2 Plan M4a: gatewayd in 15 tasks, with skeletons and given tests
Each task's tests were run against a reference at its end state; the end states were replayed
from master in order with the gate at each step (650 to 762 tests); each skeleton compiles
against its tests and fails them. The reference is kept off this machine. Lessons T27 (every
wait in a test has a limit) and T28 (mutate the reference before hand-over) come from this work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 19:05:44 -07:00

3.4 KiB

M4a task 13: a turn on loop.sock, and its answer in the thread

Branch: m4a (run git switch m4a; git status --short must be empty, otherwise stop) Commit subject: gatewayd: deliver, turns on loop.sock and answers posted in their thread

Goal

A batch of messages (task 11) becomes one turn on loopd's socket, exactly as bxctl chat sends one (read crates/bxctl/src/chat.rs, run_turn): one frame {"kind": "turn", "body": {"session", "content", "resume"}} with id 1, then events until a final turn_done or error. What comes back is posted in the thread (spec section 8):

  • No thinking and no status reach Mattermost. The one event that is shown: approval_pending, posted once as waiting for approval <id>: approve or deny it with bxctl (Mattermost approvals arrive in M4b) (the skeleton's approval_text).
  • turn_done → its content, split into posts of at most 16,000 characters (a Mattermost post holds 16,383), at the last newline before the limit, or at the limit when there is none, in order. An empty answer is posted as (the answer was empty): Mattermost refuses an empty post.
  • An error frame → Error: <code>: <detail>, where the code is the snake_case name (no_such_session), and the detail carries loopd's runbook pointer when it has one.
  • loop.sock cannot be reached, or closes early, or sends a frame that does not belong → LOOP_DOWN, and the messages are not sent again: loopd may have run them.
  • A reply in a thread loopd does not know (no_such_session with resume: true) is sent once more with resume: false, creating the session, as bxctl chat --session does. Only then.

Files

  • Copy: crates/gatewayd/tests/deliver.rs, crates/gatewayd/tests/support/fake_loop.rs, and the skeleton crates/gatewayd/src/deliver.rs
  • Modify: crates/gatewayd/src/lib.rs (pub mod deliver;), docs/implementer-log.md

The skeleton

Written: MAX_POST (16,000), LOOP_DOWN, EMPTY_ANSWER, the trait Poster (somewhere to post: Mattermost's Client, or a test's record) and its impl for Client, Outcome (Answer, Refused, LoopDown(why)), approval_text.

To fill: error_text, split_answer, one_turn, run_turn, deliver. split_answer counts characters, not bytes, and never cuts inside one: find the byte index of the 16,000th character with char_indices().nth(MAX_POST).

Steps

  • 1. Copy. git switch m4a, then cp docs/plans/M4a/files/crates/gatewayd/tests/deliver.rs crates/gatewayd/tests/ && cp docs/plans/M4a/files/crates/gatewayd/tests/support/fake_loop.rs crates/gatewayd/tests/support/ && cp docs/plans/M4a/files/crates/gatewayd/src/deliver.rs crates/gatewayd/src/. Add pub mod deliver; to lib.rs.
  • 2. See it fail. cargo test -p gatewayd --test deliver. Expected: it compiles and 9 tests fail.
  • 3. Fill error_text, split_answer, one_turn, run_turn, deliver, cargo check -p gatewayd after each.
  • 4. See it pass. cargo test -p gatewayd --test deliver. Expected: 9 passed.
  • 5. Run the gate. cargo fmt --all, then make gate. Expected last line: gate: ok.
  • 6. Log and commit. git add crates/gatewayd docs/implementer-log.md Cargo.lock && git commit

Done when

  • cargo test -p gatewayd --test deliver passes; make gate prints gate: ok.

Stop and report if

  • A test seems to need a turn sent twice in any case other than the one above.