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:
+1
-1
@@ -6,7 +6,7 @@ Newest first. A decision that changes `docs/design.md` lands in the same commit
|
|||||||
|
|
||||||
| Date | Decision | Reason |
|
| 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 | 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 | 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. |
|
| 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. |
|
||||||
|
|||||||
@@ -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
|
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
|
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
|
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
|
Facts this rests on, checked on 2026-09-24 against Mattermost's source and API reference at the
|
||||||
server's version, v11.11.0:
|
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` | `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` | `gateway` | New: the listener and one thread per `brokerd` connection. |
|
||||||
| `gatewayd` | `mm::rest` | `patch_post`, `username`. |
|
| `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` | `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). |
|
| `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`. |
|
| `bxctl` | `cli` | `deny` as another name for `refuse`. |
|
||||||
@@ -210,6 +213,15 @@ not retried.
|
|||||||
|
|
||||||
## 7. `gatewayd`: answers, typing, and the old notice
|
## 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
|
**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]`
|
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
|
(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,
|
- 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).
|
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
|
- 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
|
command counts only in the thread where this `gatewayd` posted that id. Where two Boxmakers
|
||||||
same id pending in the same thread would both take it. This needs both named in one thread and
|
share a thread, the operator addresses the command, `@boxmaker0 !approve 42`, and only that bot
|
||||||
both asking at once; it is accepted, and noted in the runbook.
|
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
|
- 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;
|
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.
|
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;
|
(`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
|
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.
|
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,
|
- **`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
|
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.
|
controls, bidi overrides and zero-width characters; every outcome line.
|
||||||
|
|||||||
Reference in New Issue
Block a user