diff --git a/11-Features.md b/11-Features.md index 2519607..7d6f9e1 100644 --- a/11-Features.md +++ b/11-Features.md @@ -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: `, the context size at which a spawned +member compacts on its own. For a `claude-code` member it becomes `--autocompact ` at +launch. For an `opencode` member it becomes a per-model `provider.

.models..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..inbox` on a shared vhost. `fleet_send{coordId: , 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 diff --git a/7-Use-Cases.md b/7-Use-Cases.md index b64de36..890463a 100644 --- a/7-Use-Cases.md +++ b/7-Use-Cases.md @@ -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: , content}` — `fleet_list` → `leads` reports it. Coordination only, **never** a task | +| Message a **peer lead** on this host | `fleet_send{sessionId: , content}` — `fleet_list` → `leads` reports it. Coordination only, **never** a task | +| Message a **peer lead** on another daemon or host | `fleet_send{coordId: , 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}` |