Files
boxmaker/docs/plans/M4a/11-gatewayd-sessions.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

4.4 KiB

M4a task 11: which posts become turns, and the queue per session

Branch: m4a (run git switch m4a; git status --short must be empty, otherwise stop) Commit subject: gatewayd: sessions, routing posts to sessions, commands and the queue

Goal

The decisions, as pure code: no network, no files. For each new post, in this order (spec section 7):

  1. Ignore it if it is the bot's own, or a system message (kind not empty).
  2. Ignore it, silently, unless the author is in allow.users.
  3. Is it for this Boxmaker? A direct message ("D"): always. A channel or group message ("O", "P", "G") whose id is in allow.channels: if it names this bot, or if it is a reply in a thread this Boxmaker already has a session for and names nobody else. Anything else: ignore. @channel, @here and @all name nobody: every agent would answer them.
  4. The session: its root is root_id, or the post's own id when that is empty; the session id is mm-<root>. A top-level post is a new session (resume: false); a reply resumes it.
  5. Commands: a message starting with !. !!… is not a command: one ! is removed and the rest goes on as a message. !approve … and !deny … are answered with M4B_COMMAND; any other ! with UNKNOWN_COMMAND. A command never reaches loopd.
  6. Otherwise the message joins its session's queue.

"Names" means @<username> in the message, case-insensitive, where the name is the longest run of a-z, 0-9, ., - and _ after the @, without trailing dots. The spec's examples, with this bot called boxmaker-straylight and another agent called Hermes in the channel (the first test checks every row):

Post For Boxmaker?
@boxmaker-straylight summarise the audit log (top level) yes: a new session rooted here
a reply in that thread: and the older files? yes: its thread, nobody else named
a reply in that thread: @hermes what do you think? no
@boxmaker-straylight @hermes compare notes yes (and Hermes answers too)
@boxmaker-straylightx hello no: a different name
@channel standup in five no

The queue. One turn at a time per session. A message for a session with no turn running starts one at once. Messages that arrive while it runs wait; when it ends, all waiting messages go together as the next turn, joined with a blank line ("\n\n"), in the order they came. A session with limit messages already waiting drops the next one, and the caller answers BUSY.

Files

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

The skeleton

Written: the three answer texts (M4B_COMMAND, UNKNOWN_COMMAND, BUSY), EVERYONE, Ignored (Own, System, NotAllowed, NotForUs), Thread { channel, root }, Message { session, thread, resume, text, joins_thread }, Route (Ignore, Reply { thread, text }, Queue), Router, Batch { session, thread, resume, text }, Pushed (Start(Batch), Waiting, Full(Thread)), Pending, Queues.

To fill: named, Router::new, Router::for_us, Router::route, Queues::new, push, finish, running, threads. route is a straight line of early returns in the order above; the known function it is given answers "does this Boxmaker have a session for this thread root?" (task 14 answers it from the state file). SessionId has no Ord, so Queues keys a HashMap.

Steps

  • 1. Copy. git switch m4a, then cp docs/plans/M4a/files/crates/gatewayd/tests/sessions.rs crates/gatewayd/tests/ && cp docs/plans/M4a/files/crates/gatewayd/src/sessions.rs crates/gatewayd/src/. Add pub mod sessions; to lib.rs.
  • 2. See it fail. cargo test -p gatewayd --test sessions. Expected: it compiles and 8 tests fail.
  • 3. Fill named, then the Router, then the Queues, cargo check -p gatewayd after each function.
  • 4. See it pass. cargo test -p gatewayd --test sessions. Expected: 8 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 sessions passes; make gate prints gate: ok.

Stop and report if

  • A row of the table above seems to need a different order of the steps.