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>
81 lines
4.4 KiB
Markdown
81 lines
4.4 KiB
Markdown
# 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.
|