diff --git a/11-Features.md b/11-Features.md index 444b190..4a63e08 100644 --- a/11-Features.md +++ b/11-Features.md @@ -28,6 +28,7 @@ six weeks, and the table alone will not carry it. | [Role pools decide the backend](#role-pools-decide-the-backend) | `fleet.developers:` etc. | CB-557 | `member/CompositePeerLauncher` | | [Tab labels name the role](#tab-labels-name-the-role) | `fleet.tabLabel:` | CB-557 | `member/HerdrPeerLauncher` | | [Launch a lead when none is live](#launch-a-lead-when-none-is-live) | `fleet.leaders..profile` + `instances` | CB-558 | `lead/LeadLauncher` | +| [Re-read the config without a restart](#re-read-the-config-without-a-restart) | `configReload.enabled: true` | CB-559 | `config/ConfigRef` | | [Leads talk to each other](#leads-talk-to-each-other) | (always on, two leads) | CB-532 | `auth/Principal` | | [A lead can be delivered to](#a-lead-can-be-delivered-to) | automatic | CB-534 | `Bridged.deliverableTo` | | [Leads are visible in bridge_list](#leads-are-visible-in-bridge_list) | automatic | CB-535 | `mcp/BridgeMcp.listFleet` | @@ -682,6 +683,42 @@ placed in one would never be found again and would be relaunched on every boot. --- +## Re-read the config without a restart + +**What.** `bridged` watches `bridged.yaml`'s modified time and re-reads the file when it changes. +Consumers read the live config at the point of use, so a change reaches the next spawn without +rebuilding anything. + +**On.** Add `configReload: {enabled: true}`. `intervalSeconds:` sets the poll period (default 10). +Absent the block nothing is constructed, so an upgraded daemon behaves exactly as before. + +**Why.** Tuning a fleet meant restarting the daemon, and a restart tears down every lead and worker +it owns. Changing one pool's `weight` cost the whole fleet's state, so in practice nobody changed it. + +**Gotcha — three classes of key, and the difference is what already exists at reload time.** + +| Class | Keys | What a reload does | +|---|---|---| +| **Hot** | `fleet:` (pools + `tabLabel`), `placement:`, an existing profile's `weight` / `maxLoad` / `model` / `tabLabel` | takes effect on the next spawn | +| **Deferred** | `lifecycle:`, `leadHeartbeat:`, `guard:`, `worktreeRoot:`, `spawnReadyTimeoutMs` / `spawnReadyPollMs`, **adding or removing** a profile | accepted into the new config, but the startup wiring keeps the old value; logged by name | +| **Cold** | `bind:`, `herdrSocket:`, `broker:`, `auth:` | **refuses the whole reload** | + +A changed cold key refuses everything, not just itself. Applying the hot half and warning about the +cold half would leave the daemon in a state matching no file on disk — the worst outcome for an +operator who is reading the file to work out what the daemon is doing. Refusing keeps one invariant: +the live config is always *some* version of the file. + +Adding a profile is deferred, not hot, because a new backend needs its own launcher and launchers are +built once at startup. Changing an existing profile's fields is hot, because those are read per spawn. + +A reload that fails to parse, or fails any of the four startup validators, is refused the same way +and the running config stays live. A config file being saved is sometimes read mid-write, and +degrading a working daemon over a half-written file would be a bad trade. The watcher stamps the +modified time **before** it reloads, so a refused file is not retried every tick — the next save +earns a fresh attempt. + +--- + ## Backfill status This page was started after the fact, so it is **not yet complete**. Entries above are written from