M4b spec: gatewayd strips a leading mention of its own bot; bare commands are the operator's care

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 01:53:54 -07:00
co-authored by Claude Opus 5.5
parent 00a9b576b2
commit 3088dcc201
2 changed files with 22 additions and 6 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ Newest first. A decision that changes `docs/design.md` lands in the same commit
| Date | Decision | Reason |
|---|---|---|
| 2026-09-24 | M4b, changing the agreed outline (row of 2026-09-23): no reactions, an approval is answered only by `!approve <id>` or `!deny <id> [reason]` in its thread; an `[approvals] users` list in `gatewayd.toml`, which falls back to `allow.users` when empty; approvals are posted in the asking thread, channel threads included; on restart, `gatewayd` marks its old approval posts dead and `brokerd`'s courier posts pending approvals again. Spec `docs/specs/2026-09-24-m4b-approvals.md`. Brief change P16 proposed. | Owner: reactions are not used. Approvers are separate because an agent allowed to talk to Boxmaker should not be able to approve for it, but the default keeps one list for one person. Redacting secrets from arguments comes with the tools work. A restart must not leave posts that look answerable and are not. |
| 2026-09-24 | M4b, changing the agreed outline (row of 2026-09-23): no reactions, an approval is answered only by `!approve <id>` or `!deny <id> [reason]` in its thread; an `[approvals] users` list in `gatewayd.toml`, which falls back to `allow.users` when empty; approvals are posted in the asking thread, channel threads included; on restart, `gatewayd` marks its old approval posts dead and `brokerd`'s courier posts pending approvals again. Spec `docs/specs/2026-09-24-m4b-approvals.md`. Brief change P16 proposed. Also: `gatewayd` strips a leading mention of its own bot from every message before handling it; in a thread shared by several Boxmakers the operator addresses approval commands, and a bare one is taken by every Boxmaker with that id pending there. | Owner: reactions are not used. Approvers are separate because an agent allowed to talk to Boxmaker should not be able to approve for it, but the default keeps one list for one person. Redacting secrets from arguments comes with the tools work. A restart must not leave posts that look answerable and are not. The mention is how the operator chooses the bot; the frontend removes it so neither the commands nor the model see it. |
| 2026-09-23 | M4a spec approved with two changes: a secret read from a file prints a warning at startup (and still starts); and in allowed channels and group messages shared with other agents, Boxmaker answers a post only if it names this bot, or it is a reply in one of this Boxmaker's threads that names no other user (`@channel`, `@here`, `@all` name nobody). One Boxmaker per machine, each with its own bot account; nothing is shared between them. | Owner's notes. The owner runs several agents in channels, possibly one Boxmaker per machine. A post's `from_bot` property is user-settable in v11.11.0, so bots are told apart by the allowlist, not by that property. |
| 2026-09-23 | M4 is split: M4a is conversations over Mattermost (`gatewayd`, the secret store), M4b is approvals over Mattermost. Spec `docs/specs/2026-09-23-m4a-gateway.md`. | As with M2 and M3: the authority-bearing half is reviewed separately, and the open question about long approvals lands in M4b. |
| 2026-09-23 | `gatewayd` reaches Mattermost over TLS, by default at the tailnet name `https://straylight.scylla-hammerhead.ts.net`; plain HTTP stays possible by URL. TLS is `rustls` 0.23 with `ring` and `rustls-native-certs` (the host's trust store, plus an optional CA file); `deny.toml` gains ISC and BSD-3-Clause. | Owner: portability, and a bigger picture that favours the tailnet name over host loopback. The host's store keeps a privately installed CA trusted; `webpki-roots` would not, and `aws-lc-rs` is a larger C build for nothing needed. |
+21 -5
View File
@@ -6,7 +6,10 @@ path (`docs/specs/2026-09-18-m3a-decision-path.md`, section 6). Owner's decision
changing the outline: **no reactions**, answers are commands only; an `[approvals] users` list
that falls back to `allow.users` when empty; approvals are posted in the asking thread, channel
threads included (redacting secrets from tool arguments is later work, with the tools); a
`gatewayd` restart marks its old approval posts dead and `brokerd` posts them again.
`gatewayd` restart marks its old approval posts dead and `brokerd` posts them again; and
`gatewayd` strips a leading mention of its own bot from every message before it is handled, so
`@boxmaker0 !approve 42` is a command and `@boxmaker0 summarise the log` reaches the model as
`summarise the log`.
Facts this rests on, checked on 2026-09-24 against Mattermost's source and API reference at the
server's version, v11.11.0:
@@ -64,7 +67,7 @@ of grants, never reaches `admin.sock`, and cannot create, list or widen anything
| `gatewayd` | `approvals` | New: the pending approvals it has posted, the commands that answer them, the text of the posts. Pure. |
| `gatewayd` | `gateway` | New: the listener and one thread per `brokerd` connection. |
| `gatewayd` | `mm::rest` | `patch_post`, `username`. |
| `gatewayd` | `sessions` | `!approve` and `!deny` go to `approvals` instead of the M4a notice. |
| `gatewayd` | `sessions` | A leading mention of this bot is stripped first (section 7); `!approve` and `!deny` go to `approvals` instead of the M4a notice. |
| `gatewayd` | `state` | `approvals`: the posts of each open approval, for the restart. |
| `gatewayd` | `deliver`, `serve` | Typing paused while a turn waits for approval; the M4a "waiting for approval" line only when `brokerd` did not send it (section 7). |
| `bxctl` | `cli` | `deny` as another name for `refuse`. |
@@ -210,6 +213,15 @@ not retried.
## 7. `gatewayd`: answers, typing, and the old notice
**The leading mention.** Before commands are looked for, and before a message is queued for
`loopd`, `gatewayd` strips a leading mention of its own bot: `@<username>` at the very start of the
message, matched as M4a names are (case-insensitive, not followed by a name character), then any
spaces after it. If nothing would remain, the message is left as it was. This happens after M4a
has decided the post is for this Boxmaker (the mention still counts as naming it), and in every
kind of channel. So in a thread shared by two Boxmakers, `@boxmaker0 !approve 42` is a command for
`boxmaker0` and, since it names another user, is ignored by the other. A mention anywhere else in
the message stays.
**Answers.** M4a's routing is unchanged up to commands: only allowlisted users are heard, and in
channels only in this Boxmaker's threads. A command `!approve <id>` or `!deny <id> [reason]`
(exactly: one space, the id in decimal, then for `!deny` optionally one space and a reason of at
@@ -256,9 +268,10 @@ their connections close, so `gatewayd` marks the posts "no longer pending".
- Arguments are posted in full, in the thread, which in a channel is visible to its members,
other agents included. Redacting secrets is deferred to the tools work (owner's decision).
- Several Boxmakers in one channel have their own `brokerd`s, whose approval ids can coincide. A
command counts only in the thread where this `gatewayd` posted that id; two Boxmakers with the
same id pending in the same thread would both take it. This needs both named in one thread and
both asking at once; it is accepted, and noted in the runbook.
command counts only in the thread where this `gatewayd` posted that id. Where two Boxmakers
share a thread, the operator addresses the command, `@boxmaker0 !approve 42`, and only that bot
takes it; a bare `!approve 42` would be taken by every Boxmaker with approval 42 pending in that
thread. The owner's decision: that is the operator's care, not something the design prevents.
- A local process of the owner's user can connect to `gateway.sock` and have the bot post a fake
approval; its answer goes back to that process only. The socket is 0600 in a 0700 directory;
M7 keeps it out of `loopd`'s and the tools' reach.
@@ -281,6 +294,9 @@ Offline, as tasks with given tests:
(`Denied`); answered in `bxctl` meanwhile (the courier closes, frame 4 never sent); expired;
the fake closes before answering (the courier posts again); unreachable at first, reachable 5 s
later; a bad frame 3 (another id, bad `by`) answers nothing.
- **The leading mention**, pure: stripped in any case of letters and before a command; kept when
nothing would remain, when another name comes first, for a longer name (`@boxmaker0x`), and in
the middle of a message; a post naming another bot first is still ignored.
- **`gatewayd` approvals**, pure: every rule of section 7 in both directions; the text of one part,
of 3 parts with the hash, and of the refusal to show; fences around backtick runs; escaping of
controls, bidi overrides and zero-width characters; every outcome line.