Merge CB-539/CB-542 docs: subscription profiles, and the env: exception

Adds the 'Run a worker on the subscription' entry, and — the part that mattered —
corrects the 'Give workers a toolchain' gotcha, which flatly claimed an env: entry
cannot repoint a worker past the SubscriptionGuard. CB-539 made that false for
subscription profiles, and CB-542 closed the hole; the doc now states the rule and
its one exception together, rather than a rule the code no longer honours.
Dai Ha
2026-08-13 15:32:13 +02:00
+29 -4
@@ -30,6 +30,7 @@ six weeks, and the table alone will not carry it.
| [Reply nudges follow the delegating lead](#reply-nudges-follow-the-delegating-lead) | automatic (retires `primary:`) | CB-532 | `mcp/PrimaryRegistry` |
| [Weighted worker placement](#weighted-worker-placement) | `placement: weighted` + `weight` / `maxLoad` | CB-518 | `placement/` |
| [Give workers a toolchain](#give-workers-a-toolchain) | per-profile `env:` | CB-511 | `worker/HerdrPeerLauncher` |
| [Run a worker on the subscription](#run-a-worker-on-the-subscription) | profile `subscription: true` | CB-539 | `worker/ClaudeCodeLauncher` |
| [Isolated worktree per worker](#isolated-worktree-per-worker) | `bridge_spawn{worktree, ticket}` | CB-301-ext | `session/GitWorktrees` |
| [Worker tool-surface isolation](#worker-tool-surface-isolation) | automatic | CB-525 | `session/GitWorktrees` |
| [Worker opens its own PR](#worker-opens-its-own-pr) | `gitTokenEnv:` / `gitHostEnv:` | CB-302 | `worker/HerdrPeerLauncher` |
@@ -109,6 +110,27 @@ onto one backend regardless of what it costs or how loaded it is.
governs *unqualified* spawns. Equal-weight candidates tie-break on **YAML definition order**, so
that order is load-bearing config, not cosmetics (CB-524).
## Run a worker on the subscription
**What.** Lets a claude-code worker run on the operator's Claude subscription, on purpose, when no
off-subscription endpoint exists for its family (e.g. `sonnet` on `ccs`). The launcher injects
neither `ANTHROPIC_BASE_URL` nor `ANTHROPIC_AUTH_TOKEN` and skips `SubscriptionGuard`'s base_url
requirement *for that profile only* — every other profile keeps the hard refusal.
**On.** Add `subscription: true` to a profile. The default (absent/`false`) keeps today's hard
boundary: a claude-code profile with no base_url may not spawn, because doing so would bill the
subscription.
**Why.** Some model families (e.g. `sonnet` on `ccs`) have no off-subscription endpoint to point a
worker at. Rather than leave those profiles unspawnable, this is an explicit, visible opt-in — a
spawn under it logs a WARN naming the profile, so billing the subscription is never an accident.
**Gotcha.** `subscription: true` contradicts a `baseUrl` (the two state opposite intents) and is
refused at spawn if both are set. The same contradiction is refused at config load for the `env:`
map: on the subscription path the guard is skipped and the adapter writes neither Anthropic key, so
an `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` in `env:` would reach the worker having passed no
guard at all (CB-542). A subscription profile may still carry `env:` — just not those two keys.
## Give workers a toolchain
**What.** Propagates the daemon's own `PATH` to every worker, plus a literal per-profile `env:` map.
@@ -120,10 +142,13 @@ env map and herdr merges it into *its own* process env — so before this, a wor
`PATH` the herdr server happened to be started with. On a long-lived herdr that can predate your
toolchain entirely, leaving workers unable to run `mvn` or `java` at all.
**Gotcha.** Adapter-owned variables win over `env:` — the `ANTHROPIC_*`/`CLAUDE_*` wiring is applied
after it, so an `env:` entry cannot repoint a worker past the `SubscriptionGuard`. Since the default
is the *daemon's* `PATH`, start the daemon with a good one (see the `PATH` lines in
`deploy/dev.ltms.bridged.plist` and `deploy/bridged.service`).
**Gotcha.** On an off-subscription profile, adapter-owned variables win over `env:` — the
`ANTHROPIC_*`/`CLAUDE_*` wiring is applied after it, so an `env:` entry cannot repoint a worker past
the `SubscriptionGuard`. A `subscription: true` profile is the exception: it skips the guard and
injects neither key, so an `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` in its `env:` would survive
unguarded — that configuration is refused at load (see [Run a worker on the subscription](#run-a-worker-on-the-subscription)).
Since the default <em>PATH</em> is the *daemon's* own, start the daemon with a good one (see the
`PATH` lines in `deploy/dev.ltms.bridged.plist` and `deploy/bridged.service`).
## Isolated worktree per worker