docs: M6 spec approved

Consult device check uses a loopback dummy on straylight. Heartbeat stays
behind a subagent a live turn is waiting on. Section 11 is complete.
This commit is contained in:
2026-10-03 19:01:34 -07:00
parent b6022378de
commit a2979bd434
2 changed files with 23 additions and 63 deletions
+2 -2
View File
@@ -37,8 +37,8 @@ reviewed in `docs/implementer-log.md` and checked on straylight (spec 7); no fin
M5d-2 (heartbeat, cron, ToolSummary, live-slot isolation) is done and on master, reviewed in M5d-2 (heartbeat, cron, ToolSummary, live-slot isolation) is done and on master, reviewed in
`docs/implementer-log.md`; finding 1 closed by task 11; spec 7 device-checked on straylight `docs/implementer-log.md`; finding 1 closed by task 11; spec 7 device-checked on straylight
(heartbeat on slot 2 left a following slot-0 turn's `cache_n` at 1398 of 1399; `jobs/tier.toml` (heartbeat on slot 2 left a following slot-0 turn's `cache_n` at 1398 of 1399; `jobs/tier.toml`
promoted only a trusted note). M6 (subagents and cloud consult) is proposed in promoted only a trusted note). M6 (subagents and cloud consult) is approved
`docs/specs/2026-10-04-m6-subagents-consult.md`, awaiting the owner's answers in section 11. (`docs/specs/2026-10-04-m6-subagents-consult.md`); plans not yet written.
P22 (proposed, not applied) makes Boxmaker a fleet of P22 (proposed, not applied) makes Boxmaker a fleet of
independent agents, one per host, deployed with Nix in a last milestone, M8; `flake.nix` builds independent agents, one per host, deployed with Nix in a last milestone, M8; `flake.nix` builds
every binary, the tool image and a dev shell. Work happens on titan (macOS, no Nix); build and run every binary, the tool image and a dev shell. Work happens on titan (macOS, no Nix); build and run
+21 -61
View File
@@ -1,7 +1,7 @@
# M6 design: subagents and cloud consult # M6 design: subagents and cloud consult
Status: proposed. Section 11 records the owner's 2026-10-04 answers; questions 4 and 6 are Status: approved by the owner, 2026-10-04, with section 11's answers. The implementer is Ornith
still open. The implementer is Ornith (through `tools/run-plan.sh`), as for M5d. (through `tools/run-plan.sh`), as for M5d.
It rests on the brief (`docs/design.md`, Inference contract 5 and 6, Authority 6, Cloud consult), It rests on the brief (`docs/design.md`, Inference contract 5 and 6, Authority 6, Cloud consult),
on M5d's scheduler (`docs/specs/2026-10-03-m5d-scheduler.md`, out of scope: subagents join that on M5d's scheduler (`docs/specs/2026-10-03-m5d-scheduler.md`, out of scope: subagents join that
@@ -164,8 +164,9 @@ brief means the owner should not expect a heartbeat-speed reply, not a second ch
### 4.4 Egress ### 4.4 Egress
A new `docs/egress.md` row: run time, `brokerd`, the configured consult URL, the allowlisted M6 does not send consult traffic off the host. The device check's dummy is loopback. A live
JSON body. The secret is `bxcore::secrets`, same store as Mattermost and Honcho. URL later is a new `docs/egress.md` row, a grant, and a `bxcore::secrets` key, the same store
as Mattermost and Honcho.
## 5. Config ## 5. Config
@@ -229,9 +230,10 @@ Given tests, first, against the fake llama server and a fake `brokerd`:
- M6a: a `bxctl` session calls `subagent` with a short task; the child log is on slot 2; the - M6a: a `bxctl` session calls `subagent` with a short task; the child log is on slot 2; the
parent's next turn has `cache_n` near its previous total. parent's next turn has `cache_n` near its previous total.
- M6b: against a private OpenAI-compatible stub (or the configured consult URL if the owner - M6b: against a loopback OpenAI-compatible dummy on straylight (the check starts it; it is
names one): a clean session's payload in the audit log matches a captured request; a session not a standing service): a clean session's payload in the audit log matches a captured
that first recalled an untrusted note is refused. request; a session that first recalled an untrusted note is refused. No internet consult in
this milestone.
## 8. Runbook ## 8. Runbook
@@ -253,13 +255,14 @@ New entries, each with a pointer from the message:
## 10. Changes to the brief and decisions ## 10. Changes to the brief and decisions
With this spec, as their own commits, once 4 and 6 are settled: With this spec, as their own commits when the plans are written:
- `docs/decisions.md` open row "cloud-led sessions": not in M6; later, leaning only at - `docs/decisions.md` open row "cloud-led sessions": not in M6; later, leaning only at
`public` taint. Leave the row open until that later design. `public` taint. Leave the row open until that later design.
- `docs/egress.md`: the consult row in section 4.4, once the URL is chosen. - `docs/egress.md`: no internet consult row in M6. The M6b device check talks to a loopback
- Inference contract 5 already says subagents share the background slot; no wording change dummy. A live URL later is its own row, grant, and secret.
unless question 6 puts heartbeat on that slot ahead of a waiting live child. - Inference contract 5 already says subagents share the background slot. Queue order is
section 3.3; no brief wording change.
## 11. The owner's answers (2026-10-04) ## 11. The owner's answers (2026-10-04)
@@ -271,53 +274,10 @@ With this spec, as their own commits, once 4 and 6 are settled:
3. **Who waits.** The parent (or middle child) stays in the tool call. Completion arrives as a 3. **Who waits.** The parent (or middle child) stays in the tool call. Completion arrives as a
`ToolResult`: session id, completed/failed, and on success the capped summary. Same for `ToolResult`: session id, completed/failed, and on success the capped summary. Same for
`consult`. No new turn protocol. `consult`. No new turn protocol.
4. **Consult URL.** Open; discussion below. 4. **Consult URL.** Given tests use an in-process fake. The M6b device check uses a loopback
5. **Name.** `subagent`. No reason to pick `delegate`. OpenAI-compatible dummy on straylight (the check starts it; it need not be a standing
6. **Queue.** Open; discussion below. service). No live vendor in this milestone. A real URL is config and a grant later.
5. **Name.** `subagent`.
### Consult URL (question 4) 6. **Queue.** Heartbeat does not take precedence over a child a live turn is waiting on.
Compaction in flight, then that child, then cron, then heartbeat, then a tool-output
Three shapes, and they can combine. summary (section 3.3).
- **Given tests** always hit a fake HTTP server in-process. That does not need a network or a
secret.
- **Device check, stub.** A private OpenAI-compatible listener on straylight (loopback, one
shot) so M6b proves audit-body == wire bytes without sending owner text off the host. Enough
to close the milestone's "payload log matches what was sent".
- **Device check, live.** A real `[consult] url` and secret on straylight. The brief's
candidate is GLM-5.3-Flash over Z.ai pay-as-you-go (Singapore entity; they state they do not
store API content). That is the first new internet egress since `http_fetch`: it needs a
grant, an `egress.md` row, and a secret in the store. It also means the device check sends a
planted `question` to that host.
Recommendation: stub for the M6b device check (the audit match is a property of `brokerd`, not
of GLM). Add a live URL later, when you want Ornith to actually consult, as config and a grant,
without another milestone. A live check can still be a follow-up task if you want the first
real call witnessed.
If we do live in M6b, the planted question should be boring and public (`What is 2+2, one
word.`), and the grant `max_taint = "public"` so a private session cannot use it until you
raise that on purpose.
### Heartbeat vs subagent (question 6)
M5d's order on a contended background slot is compaction, then cron, then heartbeat, then a
tool-output summary. A subagent serving a **held live parent** is the owner's current turn
waiting on the slot. Heartbeat is the pulse for when the owner is not talking.
Heartbeat-first would mean a Mattermost turn that called `subagent` waits out a heartbeat
(often several tool calls; the M5d-2 check saw ~20 s of `recall`s) and, with `last_heartbeat`
stamped at tick start, a long pulse can come due again immediately. The live user pays that.
Subagent-first (the spec's current list) delays heartbeat until the child finishes. That is
the same as today when a compaction summary or a cron run holds the slot: heartbeat skips or
waits. Heartbeat is already written to skip when its own session is busy.
Cron is owner-scheduled and can be a long tiering turn. Putting cron ahead of a held live
child has the same stall. Compaction in flight should stay first: it is already holding a
session the way a child is, and it unblocks that session's next live turn.
Recommendation: keep compaction first, then a subagent whose parent is held, then cron, then
heartbeat, then tool-output summaries. Heartbeat should not take precedence over a child that
a live turn is waiting on. It should still beat a child whose parent is **not** held, if we
ever queue one; under answer 3 we do not, because the parent stays in the tool call.