P24: M5b is hardening, M5c Honcho, M5d the scheduler

Owner's decision. The hardening half (sanitizer, tag neutralisation,
image links escaped, the invalid-arguments reason, and the secret store
available to every role) needs neither roncho nor Honcho and can be
specified now. Honcho waits on roncho's tagged release. Earlier
documents keep the old numbers; P24 says so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-25 23:53:12 -07:00
co-authored by Claude Opus 5.5
parent 0d6cff444b
commit cc04b3923b
3 changed files with 13 additions and 9 deletions
+5 -4
View File
@@ -6,6 +6,7 @@ Newest first. A decision that changes `docs/design.md` lands in the same commit
| Date | Decision | Reason |
|---|---|---|
| 2026-09-25 | P24, Milestones: M5b splits. M5b is hardening (the sanitizer, tag neutralisation in `loopd`, image links escaped in `gatewayd`, the invalid-arguments reason, and the secret store made available to every role, which the Honcho keys of M5c will need), which needs neither `roncho` nor Honcho and closes a gap open since M3. Honcho becomes M5c, planned once `roncho` has a tagged release. The scheduler and compaction move from M5c to M5d. Documents and decision rows written before this date use the old numbers. | Owner's decision, 2026-09-25. The hardening can be specified and run now; the Honcho half waits on `roncho` (`docs/specs/2026-09-25-roncho-requirements.md`). |
| 2026-09-25 | `memoryd` reaches Honcho through the owner's `roncho`, not a client of its own. `roncho` first gains a blocking client with a small dependency graph (no async runtime), no default server, no hidden environment settings or redirects, and request shapes checked against rift; the requirements are `docs/specs/2026-09-25-roncho-requirements.md`, handed to `roncho`'s developer. Boxmaker will allow that one git source in `deny.toml`, pinned by tag. | Owner's decision (M5b design). One client for the fleet. As it stands `roncho` is async only (tokio, reqwest), defaults to `api.honcho.dev`, and differs from rift in its search shapes, so it cannot be used unchanged. |
| 2026-09-24 | M5a's check on memory writes stays short and fixed: control characters, invisible characters, chat-template markup, six phrases. A research thread on prompt-injection defences feeds a sanitizer module for memory writes (and possibly other persisted text), built as hardening in M5b. Memory writes are not posted to the chat; `bxctl memory log` lists them. | Owner. The archive routing of untrusted sessions' writes is the main defence; a pattern list is defence in depth and should rest on research, not guesses. |
| 2026-09-24 | Memory writes need no approval, as in Hermes (the owner's `~/.hermes` has no `write_approval`). What a session that has seen untrusted content wants remembered becomes a note, labelled untrusted, not identity in the prompt (refined the same day: identity and experience are separate kinds of memory). Every write is scanned, journalled (listed by `bxctl memory log`, not posted anywhere) and can be undone; nothing is deleted. Brief change P21 proposed; the M5a spec is revised to match. Session-scoped approvals for other tools become an open decision. | Owner: an agent harness should act without a stream of questions, and reading and writing memory is what an agent is. The only risk the approval guarded is injected text reaching every future prompt, and that needs a session that read untrusted content; `brokerd` knows which sessions those are. |
@@ -70,9 +71,9 @@ Newest first. A decision that changes `docs/design.md` lands in the same commit
| How an approval whose arguments do not fit one Mattermost post is shown. Settled on 2026-09-18: what is being approved is never truncated, in any channel, because the unseen tail is where injected text would put its payload; `bxctl` prints arguments in full. Leading option for Mattermost: a thread of numbered chunks split on character boundaries of the escaped text, where only a reply or reaction on the last post counts and that post repeats the id, the chunk count and the hash of the whole; above some number of chunks, the post points to `bxctl approvals` and approval is local only. The post size limit and rate limits must be checked against the Mattermost API reference first. | M4 |
| Secret store: settled 2026-09-23 (systemd credentials, environment, owner-only file; P15 proposed). Where `brokerd` reads tool secrets from, and whether `SecretStore` moves out of `gatewayd` into a shared place, is decided when a tool first needs a secret. | when a tool needs a secret |
| Whether cloud-led sessions are ever allowed, and for which data classes. | M6 |
| Neutralizing chat-template tags in everything `loopd` sends (the owner's and other agents' messages, tool results, recalled text): measured 2026-09-24, a tag inside any message becomes Ornith's control token, so a fetched page can end its tool result and forge a system turn (`docs/research/2026-09-24-chat-template-tokens.md`). Proposed: replace each tag in a fixed list with an escaped look-alike when the text is first appended and logged (replay stays byte-identical), the list checked against the tokenizer by the self-test. Memory writes already refuse the tags (M5a). | before M5b; the exposure exists since M3 |
| Neutralizing chat-template tags in everything `loopd` sends (the owner's and other agents' messages, tool results, recalled text): measured 2026-09-24, a tag inside any message becomes Ornith's control token, so a fetched page can end its tool result and forge a system turn (`docs/research/2026-09-24-chat-template-tokens.md`). Proposed: replace each tag in a fixed list with an escaped look-alike when the text is first appended and logged (replay stays byte-identical), the list checked against the tokenizer by the self-test. Memory writes already refuse the tags (M5a). | M5b; the exposure exists since M3 |
| Session-scoped and standing approvals for tools other than memory: answering an approval with `!approve 42 session` (the same pattern for the rest of the session) or `!approve 42 always` (standing), words after the id as `!deny 42 <reason>` has (owner's syntax, 2026-09-24). To settle: what "session" covers (this call's tool and path or host, or the whole grant it matched); and where "always" writes, since `brokerd` cannot write grants (a proposed grant for the owner, or a separate directory of chat-approved grants). A temporary grant is new authority (the grants are the owner's files, and `brokerd` cannot write them), so it needs its own design. Owner's point, 2026-09-24: a harness should not ask a stream of questions. | after M5 |
| Projects in memory. Honcho can separate projects by a workspace per project (hard walls, shared naturally by every agent on the project, but Honcho's picture of the owner splits per workspace) or by scopes inside one workspace (one picture, recall filtered by project; semantics not yet read). Either way a project is a `memoryd` setting: its channels and threads, its workspace or scope, `write` and `trust`. A shared project workspace is written by several agents, so its trust is the owner's call per project. Test both on rift before choosing. Owner's idea, 2026-09-24. | M5b or a later M5d |
| Projects in memory. Honcho can separate projects by a workspace per project (hard walls, shared naturally by every agent on the project, but Honcho's picture of the owner splits per workspace) or by scopes inside one workspace (one picture, recall filtered by project; semantics not yet read). Either way a project is a `memoryd` setting: its channels and threads, its workspace or scope, `write` and `trust`. A shared project workspace is written by several agents, so its trust is the owner's call per project. Test both on rift before choosing. Owner's idea, 2026-09-24. | M5c or later |
## Applied to the design brief
@@ -112,5 +113,5 @@ Later changes to the brief:
| # | Change | Reason |
|---|---|---|
| P22 | Target environment, Roles and Milestones: Boxmaker becomes a fleet of independent agents, one per host. Each has its own identity (`soul.md`), memory, grants, audit log and credentials (its own Mattermost bot); they share only the fleet's memory, through Honcho (M5b). Each agent's inference is a configured endpoint that speaks llama-server's API: the host's own router, another host's, or a router such as crossbar. A new last milestone, M8, fleet deployment: `nixosModules.boxmaker` (`nix/module.nix`) for the owner's NixOS hosts, used from their system flake; `dist/` units, a config example and `make install` for other Linux hosts (an Omarchy machine); a host's inference measured, as M0 did on straylight, before an agent runs there. The flake itself (packages, the tool image, a dev shell) comes first, without the module. Open for M8: whether the roles run as systemd user units, as Hermes does on straylight (`linger`), or in M7's rootless containers; and what an endpoint must provide (`id_slot` pinning and queueing, `return_progress`, `timings_per_token`, `reasoning_control` and `/v1/chat/completions/control`, `/props`, `/slots`, prefix reuse on one slot), which the owner passes to crossbar's developer. Crossbar's answer (checked against its main, 4c64158, 2026-09-25): it cannot serve Boxmaker as deployed. It routes by the path's first segment or an `X-Crossbar-Route` header, never proxies `/slots` or `/tokenize`, may send `/control`, `/props` and `/slots` to a different host from the stream's, puts `/control` behind its limiter, and answers errors in its own shape. Until then an agent's `inferproxy` points straight at one host's llama-server. Crossbar's minimum for Boxmaker (a v2.3): a default route per listener, every call of a route on one host (a route per agent), side calls outside the limiter, and llama-server's error shape. Still open after that: a request queued in crossbar looks idle on the host's `/slots`, so Boxmaker retries it after 30 s; pinned routes should not queue in crossbar. Two agents on one host need different `[slots] main`. | Owner's direction, 2026-09-25: three agents run on three machines; boxmaker deploys to NixOS nodes from the system config and to an Omarchy machine; dixie gets an agent after its second GPU. |
| P23 | Memory 4 and 5, Milestones (M5b): each agent writes to two Honcho workspaces of its own, a trusted one and an untrusted one. A finished turn is written as two messages, the owner's (or other speaker's) from their peer and the agent's final reply from the agent's peer; tool calls, tool results and intermediate reasoning are not written. A turn is clean when the session is still clean at the turn's end, because the reply is made from the whole context: clean turns go to the trusted workspace, the rest to the untrusted one, which `recall` and `ask_memory` read as `memory-tainted`. The gate is `brokerd`'s session state, asked over a read-only socket query when `memoryd` learns the turn ended; a late answer can only file a clean turn as untrusted, never the reverse. `loopd`'s log labels are a second check. M5b's check "a turn from a tainted session never reaches Honcho" becomes "never reaches the trusted workspace". | Owner's direction and approval, 2026-09-25 (M5b design): Honcho holds untrusted memories too, labelled by workspace since conclusions carry no per-entry provenance. Reading `brokerd`'s state file instead was rejected: a wrong path reads as clean (fails open), its modes block other users (M7), and it would make a private file an interface. To be applied to the brief with the M5b spec. |
| P22 | Target environment, Roles and Milestones: Boxmaker becomes a fleet of independent agents, one per host. Each has its own identity (`soul.md`), memory, grants, audit log and credentials (its own Mattermost bot); they share only the fleet's memory, through Honcho (M5c). Each agent's inference is a configured endpoint that speaks llama-server's API: the host's own router, another host's, or a router such as crossbar. A new last milestone, M8, fleet deployment: `nixosModules.boxmaker` (`nix/module.nix`) for the owner's NixOS hosts, used from their system flake; `dist/` units, a config example and `make install` for other Linux hosts (an Omarchy machine); a host's inference measured, as M0 did on straylight, before an agent runs there. The flake itself (packages, the tool image, a dev shell) comes first, without the module. Open for M8: whether the roles run as systemd user units, as Hermes does on straylight (`linger`), or in M7's rootless containers; and what an endpoint must provide (`id_slot` pinning and queueing, `return_progress`, `timings_per_token`, `reasoning_control` and `/v1/chat/completions/control`, `/props`, `/slots`, prefix reuse on one slot), which the owner passes to crossbar's developer. Crossbar's answer (checked against its main, 4c64158, 2026-09-25): it cannot serve Boxmaker as deployed. It routes by the path's first segment or an `X-Crossbar-Route` header, never proxies `/slots` or `/tokenize`, may send `/control`, `/props` and `/slots` to a different host from the stream's, puts `/control` behind its limiter, and answers errors in its own shape. Until then an agent's `inferproxy` points straight at one host's llama-server. Crossbar's minimum for Boxmaker (a v2.3): a default route per listener, every call of a route on one host (a route per agent), side calls outside the limiter, and llama-server's error shape. Still open after that: a request queued in crossbar looks idle on the host's `/slots`, so Boxmaker retries it after 30 s; pinned routes should not queue in crossbar. Two agents on one host need different `[slots] main`. | Owner's direction, 2026-09-25: three agents run on three machines; boxmaker deploys to NixOS nodes from the system config and to an Omarchy machine; dixie gets an agent after its second GPU. |
| P23 | Memory 4 and 5, Milestones (M5c): each agent writes to two Honcho workspaces of its own, a trusted one and an untrusted one. A finished turn is written as two messages, the owner's (or other speaker's) from their peer and the agent's final reply from the agent's peer; tool calls, tool results and intermediate reasoning are not written. A turn is clean when the session is still clean at the turn's end, because the reply is made from the whole context: clean turns go to the trusted workspace, the rest to the untrusted one, which `recall` and `ask_memory` read as `memory-tainted`. The gate is `brokerd`'s session state, asked over a read-only socket query when `memoryd` learns the turn ended; a late answer can only file a clean turn as untrusted, never the reverse. `loopd`'s log labels are a second check. M5c's check "a turn from a tainted session never reaches Honcho" becomes "never reaches the trusted workspace". | Owner's direction and approval, 2026-09-25 (M5b design, now M5c): Honcho holds untrusted memories too, labelled by workspace since conclusions carry no per-entry provenance. Reading `brokerd`'s state file instead was rejected: a wrong path reads as clean (fails open), its modes block other users (M7), and it would make a private file an interface. To be applied to the brief with the M5b spec. |