Features: auto-compact window (CB-636) + cross-host lead coordination (CB-637)

Add two Features entries and propagate the cross-host peer-lead coordId row
into the canonical CLAUDE.md block template (7-Use-Cases). The block stays
byte-identical with the copy in the fleetd repo's CLAUDE.md.
Dai Ha
2026-08-24 17:49:44 +02:00
parent 0f5d81d302
commit 9da08df530
2 changed files with 63 additions and 1 deletions
+61
@@ -48,6 +48,8 @@ six weeks, and the table alone will not carry it.
| [Keep a worktree that still holds work](#keep-a-worktree-that-still-holds-work) | automatic | CB-576 | `session/GitWorktrees` |
| [Fail a ticket when its member dies](#fail-a-ticket-when-its-member-dies) | `health.enabled: true` | CB-580 | `health/FleetHealthMonitor` |
| [Durable reply inbox](#durable-reply-inbox) | `broker:` | CB-307 | `msg/AmqpReplyInbox` |
| [Set a member's auto-compact window](#set-a-members-auto-compact-window) | per-profile `autoCompactWindow:` | CB-636 | `member/ClaudeCodeLauncher`, `member/OpenCodeLauncher` |
| [Leads on different hosts talk over a shared broker](#leads-on-different-hosts-talk-over-a-shared-broker) | `coordinator:` | CB-637 | `msg/LeadMailbox`, `msg/LeadCoordLoop` |
| [Broker password out of the config](#broker-password-out-of-the-config) | `broker.uriEnv:` | CB-635 | `config/FleetConfig` |
| [An unreachable broker does not stop the daemon](#an-unreachable-broker-does-not-stop-the-daemon) | (always on) | CB-635 | `Fleetd.selectReplyInbox` |
| [Reject overlapping rendezvous](#reject-overlapping-rendezvous) | automatic | CB-548 | `msg/Rendezvous` |
@@ -2045,6 +2047,65 @@ forge; they are deliberately local and invisible to `git branch`.
---
## Set a member's auto-compact window
**What.** A profile can set `autoCompactWindow: <tokens>`, the context size at which a spawned
member compacts on its own. For a `claude-code` member it becomes `--autocompact <tokens>` at
launch. For an `opencode` member it becomes a per-model `provider.<p>.models.<m>.limit.context` in
the generated config. Unset ⇒ the backend's own default (Claude Code's built-in auto-compact,
opencode's `compaction.auto`).
**On.** Per profile in `bridged.yaml`, e.g. `autoCompactWindow: 250000`. The value must be in
`[100000, 1000000]` — the band Claude Code's flag accepts — and the daemon refuses to start with a
profile outside it, naming the profile. For opencode the key only takes effect when `model:` is in
`provider/model` form (a warning is logged otherwise), because the limit is written under that exact
provider and model.
**Why.** A member that runs out of context dies mid-turn and loses its `fleet_reply`, so the report
is gone even though the work was done. The backends already auto-compact, but the *window* was not in
the operator's hands — a long delegation on a big model could compact too late. This makes the
trigger a per-profile choice, so an expensive profile can be told to compact earlier than a cheap one.
**Gotcha.** The two backends take the window by different means. Claude Code's `--autocompact` is an
absolute token count and outranks env and settings. opencode has no absolute "compact at N" knob at
all; the only lever is the model's `limit.context`, and the generated config also writes
`limit.output` (16384) because opencode's schema requires both. On a gateway profile that is a
*partial* override of the model's real limits — confirm the live member still answers after a spawn.
Related: [opencode members run with `--auto`](#opencode-members-run-with---auto-and-forced-auto-compaction).
---
## Leads on different hosts talk over a shared broker
**What.** Two leads that share no herdr session — including leads on different hosts — can message
each other by a stable coordination id. A `coordinator:` block gives this daemon one id and a durable
AMQP mailbox `lead.<id>.inbox` on a shared vhost. `fleet_send{coordId: <peer's id>, content}`
publishes to the peer's mailbox; the peer's daemon drains its own mailbox and types the message into
its local lead's pane. `fleet_list` reports this daemon's own coord-id, so an operator knows the
address to hand a peer.
**On.** Add a `coordinator:` block with `selfId:` (this daemon's globally-unique id) and either
`uri:` or `uriEnv:` (the shared broker, ideally a separate vhost from the worker reply inbox). With
no block, nothing opens and lead-to-lead stays the existing same-host pane path. If the block is
present but `selfId` is blank, the daemon logs a warning and skips the mailbox — it does not crash.
If the broker is unreachable at boot, the daemon warns and carries on with the feature off.
**Why.** [Leads talk to each other](#leads-talk-to-each-other) widened addressing, but only for leads
that share one herdr session — the address was a bare herdr terminal id, which means nothing on
another host. Cross-host coordination needs an address that means the same thing everywhere and a
transport that is not a local pane. The shared broker is the one channel both hosts already reach, so
a coord-id plus a per-lead mailbox is the smallest thing that carries a message across the gap.
**Gotcha.** A publish to a coord-id whose mailbox no daemon owns is reported as unroutable, not
silently dropped — the peer must be configured and running for the message to land. Delivery into the
local lead's pane is status-gated exactly like a worker reply: a message waits, unacked, until the
lead pane is idle, blocked or done, so it is never typed in mid-turn. A daemon with several leads
must *name* one lead after `coordinator.selfId`, or it cannot tell which pane a peer's message is for
and holds it. This is transport only — there is no peer discovery yet; a lead is addressed by a
coord-id it was already told.
---
## Backfill status
This page was started after the fact, so it is **not yet complete**. Entries above are written from
+2 -1
@@ -400,7 +400,8 @@ the merge — and merging on a reviewer's word is delegating it by proxy.
| Delegate (blocking) | `fleet_send{sessionId, content}` |
| Delegate (long task) | `fleet_send{sessionId, content, wait:false}` → ticket → `fleet_poll{ticket}` |
| Answer a member's `fleet_ask` | `fleet_send{turnId, content}` — **not** `sessionId` |
| Message a **peer lead** | `fleet_send{sessionId: <their terminal>, content}` — `fleet_list` → `leads` reports it. Coordination only, **never** a task |
| Message a **peer lead** on this host | `fleet_send{sessionId: <their terminal>, content}` — `fleet_list` → `leads` reports it. Coordination only, **never** a task |
| Message a **peer lead** on another daemon or host | `fleet_send{coordId: <their coord-id>, content}` — needs a `coordinator:` block; your own coord-id is in `fleet_list`. Coordination only, **never** a task |
| Answer a peer lead that messaged you | `fleet_reply{content}` — the one case a lead replies |
| Collect a held reply | `fleet_poll{target}` · then `fleet_ack{target, msgId}` |
| Tear down a member | `fleet_stop{paneId}` |