Lane calibration — tiers, effort, and cost
If you are not a Claude session, read this as an inventory, not an identity. The model names below (Haiku, Sonnet, Opus, Fable) name LANES this operator can dispatch work to — they do not describe you, and a Claude-family fact stated here (effort equivalences, quota posture) is not a fact about your own model. Route only to what
/laneslists on this machine.AGENTS.mdis debranded for non-Claude harnesses (scripts/agents-md/debrand.json); this file is reference detail linked from it and is deliberately NOT rewritten, so the orientation is stated here instead.
Reference detail for the delegation policy. The directives (name an explicit
model, raise effort before tier, the env-capped spawn depth + concurrency,
Haiku does not spawn,
single-writer, the salus provider restriction) stay resident in
CLAUDE.md — a directive behind a lookup is a dead directive
(see context-architecture.md).
What lives here is the fact-shaped part: which lane suits which work, and how to
set effort.
The live per-machine inventory is /lanes (derived from
scripts/lanes/lanes.json + machine state). Never route to a lane
/lanes does not list. The tier semantics below are invariant; the inventory
is data.
Metered OpenRouter workers
headed-arm-worker.sh --lane openrouter (or WORKER_SESSION_LANE=openrouter) uses
scripts/claude-openrouter with a pinned Sonnet 5.5 default. A fresh session
start measured about 0.17 USD; even a short reply pays the initial prompt
cost. Fleet admission still applies, but native Claude and codex subscription
usage budgets do not. The backend refuses unknown credit or key-limit readings and
an effective balance below OPENROUTER_MIN_CREDIT_USD (default 3 USD):
the smaller of account credit and the key’s limit_remaining is available.
bank-preflight.sh selects this gate via CADENCE_USAGE_LANE=openrouter
(or WORKER_SESSION_LANE=openrouter), reusing the balance helper and the same floor:
PROCEED at/above the floor, SKIPPED-BANK below it, BANK-UNKNOWN when
unmeasurable. Tick’s bank=openrouter:<verdict> reports that lane verdict;
its separate or= reading remains informational. Native and claudex usage budget
paths are unchanged.
The launcher keeps the session pin (OPENROUTER_MODEL) separate from subagent
tiers: OPENROUTER_HAIKU defaults to anthropic/claude-haiku-5.5 (1M context, verified in the public catalog 2026-10-11),
OPENROUTER_SONNET to anthropic/claude-sonnet-5.5 (1M), and OPENROUTER_OPUS
to anthropic/claude-opus-5.5 (1M). Each independently overrides its
ANTHROPIC_DEFAULT_*_MODEL export, including in managed workers. Tier overrides
must exactly match the launcher’s offline synchronous Claude catalog snapshot, verified
against the public OpenRouter model list on 2026-10-02; unknown or malformed
slugs (including asynchronous :batch variants) refuse with exit 2 before
key resolution or any network access. Newly
listed slugs require refreshing both launcher twins’ allowlists; launch does
not fetch the model list. A session uses a family alias only when that alias
resolves to its exact pin, otherwise it retains the explicit session slug.
scripts/lanes/openrouter-cost.sh --since <launch.log> reads account metadata
only and prints the effective balance plus account-wide spend since launch
(not a per-session attribution). Credits metadata can lag: an immediate
--since delta can under-report spend; re-read it later. Tick’s or= labels the limiting source;
or=skip makes no OpenRouter read when no OpenRouter worker is live, and balance
changes never wake the chief waiter. OpenRouter sessions share their own
config namespace and can message peers there. Native chiefs use the
handover document/file inbox bridge, not cross-namespace SendMessage.
Plugin profiles: lane-impl is not the chief profile
operator is the no-override sentinel: it injects no plugin settings and
preserves the operator’s chief’s installed configuration. lane-impl instead
injects an explicit lean surface of handover@himmel, himmel-ops@himmel,
and qmd@himmel, plus lean-skills@himmel
(in registry base, so every profile inherits it unless it says
otherwise — bare carries no base at all, and chief-relay drops it
explicitly, a relay (the transport worker kind — see docs/glossary.md)
forwarding messages having no business brainstorming;
vendored workflow skills, not an “operator always-tier extra” the way
superpowers/mattpocock-skills used to be, both of which are dropped from the
catalog entirely); plannotator-effective-html stays operator-tier-only,
explicitly disabled here (it is gated behind the separate design profile).
Composed lists and consults. --profile a,b resolves to the
OR-union of the members’ enabledPlugins (the floor stays on; a member’s drop
never turns off another member’s enable), the de-duplicated permissions.allow
union, the union of the mcpServers allowlists, and ONE skill-listing pass over
the union of the members’ required ids. Role/non-additive profiles refuse as
members. headed-arm-worker.sh --consult is a judge-shaped, plugin-scoped session
with Edit/Write/NotebookEdit denied and a single append-results.sh Bash
allow on its own doc; Bash runs in Claude Code’s sandbox, writes confined to the doc file plus temp dirs, reads open; it launches with --setting-sources "" so no user/project/local scope’s write roots load, with their hooks, deny/ask and env carried instead. Each consult’s launch
line in launch-logs/<session>.log carries role=consult and console= (the chief that launched it); consult
cost is joined later from the session transcript, not written at launch.
worker-impl is the profile a worker gets from
headed-arm-worker.sh --profile worker-impl. There is exactly one worker profile, and
that is deliberate: it is lane-impl minus i-have-adhd (the operator reads console windows, not leg windows) — HIMMEL-4017
audit, 33650 to 31219 first-turn tokens — because the measured worker floor is schema-shaped, not roster-shaped (~40k of a
74.3k first-turn floor is tool and MCP schemas, ~9k the base system prompt), so
sibling profiles for docs or upstream work would either resolve to bare and
lose the floor’s guards and skills or duplicate worker-impl exactly. What
distinguishes it is the legacy contextBudget: 35000 target, not a
measured attainable floor. The context-budget audit below retains it for lack of
three attributable workers; measure a real worker with scripts/lanes/worker-burn.sh.
gateAllow is one registry-level list of validated Bash rules;
lane-impl, worker-impl, lane-review, and lane-content opt in with
gateAllow: true, adding permissions.allow to their resolved settings.
Matching allow rules are evaluated before the narrative-sensitive auto-mode
classifier, giving deterministic routing; the deny list still wins and is
untouched. operator, user, and bare keep byte-identical resolver output.
The list covers guarded merge/lock/inbox/automated review/CI scripts. Push rules are omitted:
a branch-suffix wildcard also admits trailing --no-verify, which skips the
git hooks those rules would rely on. Pushes stay on the classifier path until
a proven deny pair exists.
quiet-run.sh itself can execute arbitrary argv, so its rules pin the label,
separator, interpreter and suite directory: only the basename suffix after
test- is a wildcard, and
both plain and SUITE_LOCK_WAIT=60 forms are listed. This supersedes the
unsafe label/directory-glob proposal; each listed directory has tracked shell
suites, while the empty direct scripts/lanes/ set is omitted. Node suite
rules are omitted entirely and remain on the classifier path. A suite run
that wants the deterministic rule uses the label suite; any other label
simply falls back to today’s classifier path — no regression. Unlisted suite
directories also retain that path. The matcher’s handling of the env prefix
and of a tail glob is unproven until the chief’s first native
--profile worker-impl dispatch. These profile rules also reach claudex workers
through --lane claudex --profile worker-impl; operator-managed
user-scope rules remain preserved by sanitize_settings.
worker-impl’s mcpServers: ["qmd"] layers --mcp-config +
--strict-mcp-config onto the same profile, stripping USER-level MCP servers
(graphify, obsidian-vault, context7) a worker never calls. qmd is kept
(not []) because --strict-mcp-config drops any server absent from the
allowlist, plugin-provided ones included — measured by dropping the connector
tools entirely rather than degrading. This is lever 1 of the first-turn reduction work,
completed: first-turn moved 74.3k (bare) → 73.0k (#642, profile only) →
70.5k (this ticket, +MCP allowlist) — a 3.8k total saving, not the ≤35k this
lever was hoped to reach. The ~40k this doc estimated as “tool and MCP
schemas” below is not mostly MCP-server schemas: removing three full
user-level MCP servers bought only 2.5k, so the true floor is dominated by
built-in tool schemas and skill/agent listings — outside what a plugin or MCP
lever can touch. Treat that residual as a separate, harder problem, not a
follow-up to this lever.
telegram is the profile the bridge (scripts/telegram/poller.ts)
resolves for every cold claude spawn dispatched from a Telegram message,
via resolveProfileSettings (the same seam spawn-claudex.ts already uses)
plus a poller-local --mcp-config/--strict-mcp-config helper built from
mcpServersForProfile/collectMcpServerDefs. Its plugin set is
lane-content minus claude-obsidian (floor + obsidian-triage) and its mcpServers: ["qmd"] matches worker-impl’s
allowlist. Measured (profile-context-probe.mjs): telegram = 32541
first-turn tokens against contextBudget: 45000, below the unleaned
bare baseline of 33493 — so unlike worker-impl’s token-reduction win, this
profile’s value is deny-by-default correctness (no ambient full ~/.claude
plugin/MCP surface leaking into an unattended bridge run), not a token cut.
Only the bridge entry point is wired; scripts/telegram/auto-action.{ts,sh}
never spawns claude itself (it shells out to hermes), and the hermes
gateway has no plugin-profile seam at all — both are out of scope until one
exists to wire.
--profile also exports HIMMEL_LEAN_WORKER_SESSION=1, which silences the three warn-only
SessionStart hooks (graphify freshness, qmd staleness, where-are-we) for that
session only — a worker has one ticket and a chief to report to, and never acts
on a warn-only hook. --profile composes with --lane claudex through
scripts/lanes/worker-claude-launcher.sh and its WORKER_SESSION_CLAUDE_BIN exec target:
settings, worker system prompt and optional strict MCP config reach scripts/claude-codex
without changing native argv. Every --lane claudex launch also appends
worker-preface-claudex.md, concatenated
after the profile worker system prompt when both apply, making document-channel
coordination independent of a hand-pasted brief.
Until launcher wiring is reconciled, consumers invoking
plugin-profiles.mjs directly must run it with the child’s effective cwd and
CLAUDE_CONFIG_DIR. Library consumers must pass the live installed set from
readEnabledPluginIds(childHome, childCwd, childConfigDir) as
resolveProfile’s installed option; otherwise an uncatalogued enabled plugin
can inherit into the child.
Tier semantics
| Lane | Best for | Effort / notes |
|---|---|---|
| Haiku 5.5 | bulk mechanical; scoped read-only verification/extraction; mechanical edits that end in a real check (never delegates further) | low for bulk; medium (the model default; override the lane’s low) once a brief has more than a couple of steps — see Haiku 5.5 |
| Sonnet 5.5 | scoped research; default implementor for well-specified impl briefs | medium default (a native worker runs at lanes.json’s sonnet lane effort, medium; an explicit CLAUDE_CODE_EFFORT_LEVEL in the launching shell overrides it — e.g. CLAUDE_CODE_EFFORT_LEVEL=high for a multi-step or guard-grade worker); high for multi-file/long briefs — raise effort before reaching for Opus. Effort is recalibrated vs Sonnet 5: low can skip verifying a change, and at low/medium on long tasks it is likelier to stop and check in (Anthropic migration guide) |
| Opus 5.5 | multi-step reasoning; default parent | medium default (≈ Opus 5 high); low for scoped impl; high for heavy parenting; xhigh/max only where a gain was measured — see Effort calibration |
| Fable 5 | judgment, taste — hardest calls; escalation target | scale to the item (operator 2026-07-08, un-capped): medium default; high for substantial judgment work — not just the hardest; xhigh for the hardest |
Beyond the Claude tiers the fleet includes machine-specific impl/critic/bulk lanes (paid/optional — they exist only where the operator configured them). See Non-Claude lane calibration below.
Haiku 5.5
haiku is claude-haiku-5-5 (released 2026-10-07; the haiku alias resolves to
it on the Anthropic API, needs Claude Code 2.1.293+). Facts, sources and the
full hard-coded-site audit: the audit doc in the state repo’s
specs/research/. Window 1M (was 200k; auto-compacts near 967K), 128K
output, price per MTok $0.10 in / $0.50 out for a prompt up to 100K ($0.50 /
$2.50 above; Haiku 4.5 was $1 / $5), a tokenizer about 30 % heavier than 4.5, and
adaptive thinking that cannot be turned off. Anthropic’s own pointers: high
volume classification/extraction/routing is Haiku; agentic coding is Sonnet 5.5
or Opus 5.5 (Terminal-Bench 4.0: 39 % Haiku 5.5, 71 % Sonnet 5.5).
Eval (2026-10-07). scripts/eval/lane-quality/run.sh,
the 4 frozen tasks, 1 rep per cell, hidden acceptance, blind Opus judge, cost
is the agent’s total_cost_usd (API-price equivalent; the runs.jsonl rows carry
no token counts). n=1 per cell, so read it as a screen, not statistics.
| Cell | Accepted | Agent cost, 4 tasks | Wall s | Judge mean C/S/T/H |
|---|---|---|---|---|
| Haiku 5.5 low | 4/4 | $0.064 | 199 | 4.25 / 4.75 / 4.25 / 3.75 |
| Haiku 5.5 medium | 4/4 | $0.065 | 209 | 4.00 / 4.75 / 4.00 / 4.25 |
| Haiku 4.5 low | 1/4 | $0.706 | 432 | 3.00 / 4.50 / 2.00 / 3.00 |
| Sonnet 5.5 medium | 4/4 | $0.955 | 241 | 4.25 / 5.00 / 4.25 / 4.75 |
Cost hypothesis (“about 75 % cheaper than Haiku 4.5”): measured 91 % cheaper
on this task set (0.064 vs 0.706), and 15x cheaper than Sonnet 5.5 at the same
acceptance. Part of the gap is that Haiku 4.5 needed 14-20 tool calls on three
tasks where Haiku 5.5 needed 6-11; the per-token price ratio alone is 10x.
medium cost no more than low here. Judge honesty at low is lower on
doc-plus-code (2 against 5 at medium), consistent with the verification gap
Anthropic’s guide describes (the judge’s reason was not read).
Routing verdict per work type (changes only where the eval says ROUTE):
| Work type | Verdict | Condition |
|---|---|---|
| Bulk mechanical (rename, reformat, bulk edit) | ROUTE (unchanged) | brief names the exact check to run |
| Scoped read-only verification / extraction (finding-verify shaped) | ROUTE (new) | 4.0-5.0 judge, 7/7 accepted at both efforts; Sonnet remains the default for open-ended research |
| Mechanical edit + test (shell-red-green, doc-plus-code shaped) | ROUTE with medium | 4/4 accepted; add the verify paragraph below; escalate to Sonnet on the first miss |
| Well-specified implementation, multi-file | DEFER | stays Sonnet (default implementor); no multi-file or long-brief task in the set, and Anthropic points agentic coding at Sonnet/Opus |
| Multi-step reasoning, orchestration | DEFER | Opus |
| Trust paths, hooks, judge, vault, PHI | NEVER | invariant, not an eval result |
hook-refusal (a guardrail-reading task) was accepted 18/18 by Haiku 5.5, but that is evidence about the model, not a license: hooks and trust work stay off Haiku by invariant.
Prompting a Haiku 5.5 child (Anthropic’s prompting guide; existing 4.5 prompts work unchanged). Fold into the brief when it is more than one step:
- Say what “done” is and add: “Keep working until everything asked for is done;
stop to ask only when you cannot go on.” At
lowit otherwise stops early on long briefs (raising tomediumabout halved that and doubled output tokens). - Add: “Before reporting a change as done, run a real check that exercises it and quote the result.” At low/medium it reports changes done without running one.
- Give today’s date if the task searches; do not write blanket “always search”.
- Never put user text inside a tool result; thinking counts toward
max_tokens; no prefill and no non-defaulttemperature/top_p/top_kon the API. - Effort and the cache (https://code.claude.com/docs/en/prompt-caching#changing-effort-level):
on Opus 5.5, Sonnet 5.5, Haiku 5.5 and Fable 5.1 with an API key or a Claude
subscription, changing effort mid-session keeps the cache (Fable 5.1 needs
Claude Code v2.1.260+). On other models, and on Bedrock, Vertex, a Claude apps
gateway, with
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETASset or a HIPAA configuration, a change recomputes the whole request. The claudex lane routes through a gateway, so assume a change throws the cache away there.
Known stale rows. The [1m] suffix table below was measured on Haiku 4.5;
Haiku 5.5 is natively 1M and takes no suffix, and its rows are unmeasured. The
OpenRouter lane now defaults to anthropic/claude-haiku-5.5;
anthropic/claude-haiku-4.5 stays selectable via OPENROUTER_HAIKU. Haiku 4.5 retirement is “not sooner than 2026-10-15” (no notice yet).
Labels above mirror scripts/lanes/lanes.json, which is authoritative. When a
tier’s underlying model ships a new generation, update lanes.json first and
this table follows — never the reverse.
Lane-fit eval: Haiku 5.5 vs Sonnet 5.5 vs codex gpt-6.1-sol (2026-10-08)
Same harness, now 7 tasks (the 4 above plus cr-fix, class-sweep,
guard-cmd; the real himmel hooks are live in the worktree, so denials are
counted), medium effort, 2 reps per cell (14 runs each), rep 1 judged by the
blind Opus judge, rep 2 unjudged. Codex runs through the claudex launcher
(CLAUDEX_LANE_OK=1, now accepted by run.sh --lane claudex). Raw rows:
the state repo’s specs/research/ bundle.
| Cell | Accepted | Agent cost, 14 runs | Mean turns | Mean wall s | Hook denials | Judge mean C/S/T/H (rep 1) |
|---|---|---|---|---|---|---|
| Haiku 5.5 | 14/14 | $0.19 | 10.3 | 41 | 7 | 4.71 / 4.57 / 4.14 / 4.57 |
| Sonnet 5.5 | 14/14 | $2.98 | 7.2 | 49 | 9 | 4.43 / 4.86 / 3.86 / 4.29 |
| codex gpt-6.1-sol | 14/14 | not priceable (see below) | 11.8 | 112 | 10 | 4.43 / 4.86 / 3.71 / 4.00 |
- Pass rate does not discriminate. Every cell cleared the hidden checks on every task, so the set cannot rank the lanes on correctness. The separation is in cost, turns, wall time and the judge’s test-quality and honesty scores.
- Haiku 5.5 is about 15x cheaper than Sonnet 5.5 ($0.19 against $2.98) at equal acceptance, and needed more turns (10.3 against 7.2) to get there. Its judge scores matched or beat Sonnet’s except scope (4.57 against 4.86) on this set (n=7 judged, so a screen).
- Codex dollar figures are Claude Code’s guess: it does not price the
gpt-6.1-solslug, so itscost_usd($0.14-$0.96 per run) is not a bill. The rows carry token counts instead; codex wrote 0.5-5K output tokens per run against Haiku’s 0.9-13K. Its draw is on the codex weekly usage budget, which the preflight did not report (codex=?), so no usage budget delta was measured. Codex was the slowest (about 2.7x Haiku’s wall time) and the weakest on judge test quality (3.71) and honesty (4.00, with two 3s). - Harness note. The first codex
shell-red-greenrun tripped the per-task--max-usdcap on that mispriced figure (1 turn, 0/14) and was re-run; a codex--max-usdis a guard against the estimate, not against real spend. - Denials are not a lane signal. All three lanes were denied by the live
hooks on
class-sweep,doc-plus-codeandhook-refusal(7, 9 and 10 in 14 runs); the counts are too close and too noisy to rank lanes.
Routing update. The verdict table above stands; this eval adds:
| Work type | Verdict | Evidence |
|---|---|---|
Automated review-fix and sibling-sweep shaped (cr-fix, class-sweep) | ROUTE to Haiku 5.5 medium with a named check | 4/4 accepted across both reps; all six cr-fix runs (every lane) cleared acceptance and scope, so none acted on the R3 trap finding |
One guard-shaped command (guard-cmd) | ROUTE to Haiku 5.5 | 2/2 accepted, 0 hook denials, $0.009 per run |
| Codex as an implementor | Not preferred on quality | no pass-rate gain over Haiku, 2.7x slower, lowest judge test quality; use it for usage budget diversification, not for fit |
| Sonnet 5.5 | Keeps open-ended and multi-file work | fewest turns, best scope discipline; no pass-rate edge on this set |
Limits: n=2 per cell, small tasks, one reviewer model; a set where every lane passes cannot show a Haiku ceiling. Add a harder multi-file task before promoting Haiku past the rows above.
Non-Claude lane calibration
Every lane scripts/lanes/lanes.json registers gets a row here. The columns
are the invariant calibration facts (class, effort convention, context
ceiling, cost/quota posture, calibration status) — not the full description,
sanctioned-script invocation, or roster detail, which stay in lanes.json (data) and
tooling-catalog.md
(dispatch shape). Restating that prose here would create a second copy that
silently drifts (the doc-drift class) — this table is deliberately thin.
quota bank reflects the quota.bank field in lanes.json; a lane without
one is dashboard-omitted from the quota exporter — that’s a
registry fact, not a doc gap. cost-optimal context window is exactly that —
NOT a hard ceiling: larger inputs remain possible past it at higher cost.
The codex-family figures below are all 900000 as of 2026-08-17 (operator
ruling), but the EVIDENCE behind that number is deliberately NOT
uniform per lane — same weekly usage budget, different real backends, different
verification status. A blended story would misrepresent at least one of them.
lanes.json’s $context-note carries the full per-lane reasoning (measured
vs. ruled, and the gpt-5.5 conflict on three of the five); this table only
summarizes.
Invoke implementation lanes through scripts/telegram/dispatch-lane.sh with
the Bash tool’s run_in_background: true. Its command and flag mapping comes
from each lane’s structured dispatch entry; the parent receives one bounded
final report instead of spending a Claude wrapper agent on polling.
v1 lane scope (operator ruling 34, 2026-09-01): the
himmelctl install wizard ships Claude tiers as the ONLY implementation
lanes; codex/hermes are offered ONLY as cross-model automated review lanes for
/pr-check, never as implementation lanes. ollama-local and copilot-cli
are dormant in v1 (below, dormant.optInEnv) — the wizard no longer offers,
selects, or probes either; an operator who wants them sets the registry’s
optInEnv directly rather than through himmelctl. This is a wizard/installer
scoping decision, not a registry deletion: both rows stay fully live in
/lanes and this table, on the operator’s own explicit opt-in, “based on user
requests” (the ruling’s own words) — the same dormant shape the other dormant
rows below use. claudex is the exception: a live implementation lane with no
dormant entry and no opt-in.
Lane (lanes.json id) | Class | Default effort convention | Cost-optimal context window | Quota | Calibration status |
|---|---|---|---|---|---|
glm — GLM lane (spawn-glm.ts) | impl | small-context discipline — chunk big plans | 1M | glm (flat-rate overflow, not per-token) | dormant (HIMMEL-1967) |
claudex — claudex lane (claude-codex over CLIProxyAPI) | impl | model gpt-6.1-sol; launcher default medium; a chief-dispatched worker runs xhigh through its brief’s effort key; never ultra/max | 900000 — declared window raised by operator ruling (2026-08-17) alongside hermes-oneshot; the CLIProxyAPI path itself is UNMEASURED at 900k (see below) — the launcher’s CODEX_CONTEXT_WINDOW default stays 272000 (its own code warns past the ~372k backend ceiling it has evidence for), so a caller opts UP toward 900k per-dispatch and accepts the risk | codex | live — calibrated; dispatch-lane.sh accepts it once 5 consecutive verified passes exist (readiness.passesRequired) |
hermes-critics — hermes free critics (scripts/cr/hermes-critic.sh) | critic | — (critic pass, not effort-tiered) | unverified/varies | none (dashboard-omitted) | calibrated — model is PINNED to gpt-6.1-sol via critics.json (re-pinned 2026-09-29, operator moved codex lanes to GPT-6.1 Sol; gpt-6-sol before it, re-pinned 2026-09-23, operator adopted GPT-6 Sol for codex lanes; gpt-6-astra before it, re-pinned 2026-09-05, operator switched the codex CLI default; the pin-via-critics.json mechanism itself dates to operator ruling 2026-08-23, which pinned gpt-5.6-sol), not the himmel_agent profile default (ox-alpha stays reserved for hermes-oneshot/dispatch-trusted); not yet wired into the /pr-check gate |
codex (paid, via hermes) | critic | — (critic pass) | 900000 — raised by operator ruling (2026-08-17); pins gpt-6.1-sol via critics.json (2026-09-29; gpt-6-sol before it, 2026-09-23; gpt-6-astra before it, 2026-09-05) — the window figure predates and is independent of the model pin; least-verified of the four raised lanes | none (dashboard-omitted); opt-in CR_PROFILE=paid | calibrated — automated review escalation / second opinions only |
copilot-cli — GitHub Copilot CLI (free tier) | bulk | small tasks; model tier is mini-class unless the caller overrides the auto pin | unverified/varies | none (dashboard-omitted); 2,000 completions/mo | not calibrated yet — worker-spawn-matrix row UNVERIFIED (live smoke pending eval); route only for free chores / second opinions, and only through the dispatch-copilot.sh sanctioned script |
hermes-oneshot — hermes one-shot dispatch | impl | invoke.sh wall-clock timebox (default 1800s) + Nth-identical-deny abort | 900000 — re-confirmed on ox-alpha (2026-08-22: 300K/600K/900K probes all accepted); previously operator-verified inside hermes v0.20.2 (2026.8.16), upstream commit bab7be3c (2026-08-17), superseding 350000 (hermes-agent commit 522997543, 2026-08-16) | none (dashboard-omitted) — free while ox-alpha lasts | calibrated — live-proven spawn path; see ox-alpha |
codex-exec — codex CLI sandbox | impl | well-scoped chunks; job registry is per-workspace | 900000 — raised by operator ruling (2026-08-17) despite the 360K rejection hermes measured on the gpt-5.5 pin it carried then (codex-exec now follows the codex critic’s model, gpt-6.1-sol (gpt-6-sol before it; gpt-6-astra before that); that probe was not repeated there); least-verified of the four raised lanes | codex | calibrated |
antigravity-cli — Antigravity CLI (Google AI Plus) | bulk | simple tasks; --output-format drift seen on Windows builds | unverified/varies | none (dashboard-omitted); free AI-Plus quota | not calibrated yet — roster + quota shape TO VERIFY at eval; parity/guards UNVERIFIED (permission flags only, no hook surface); egress-DENIED for vault corpora, himmel-code only; route only for free-bank chores / second opinions |
ollama-local — ollama (local models) | bulk | slow, small tasks | unverified/varies | none (dashboard-omitted); free, local wall-clock | calibrated — zero-egress guarantee is structural; the only salus-eligible backend |
openrouter-free — OpenRouter free models | bulk | simple tasks only; rate-limited | unverified/varies | none (dashboard-omitted) | not calibrated yet — parity/guards UNVERIFIED pending eval; probe live availability before routing, route only for simple free-tier chores |
ollama-cloud — Ollama Cloud (free tier) | bulk | simple tasks; free-tier usage caps | unverified/varies | none (dashboard-omitted) | not calibrated yet — parity/guards UNVERIFIED pending eval; egresses to ollama.com, an undeclared egress-matrix provider — vault corpora default-DENY (himmel-code only) until the operator declares a cell; route only for free-bank chores on non-vault corpora |
claudex chief reachability: a claudex worker has no
ListAgents/SendMessage path to a native session, so a chief ruling
reaches it through a per-worker file inbox instead — inbox-send.sh appends a
bullet, the worker’s own next PostToolUse (or a SessionStart resume) delivers any
undelivered bullets as additionalContext, no operator paste required. See
docs/internals/retask-channel.md’s
“Claudex file inbox” note for the RETASK-token equivalence this relies on, and
scripts/hooks/claudex-inbox-hook.sh for the delivery hook itself.
Chief wake-up budget: the tick no longer runs as a Monitor arm capped at 30 min — it runs inside chief-wait.sh, started once with Bash run_in_background, which samples tick.sh every 180 seconds and wakes the chief only on a change (or a failure streak); a message or a Telegram line wakes it too, but a full-context turn fires only on an actual event, never on the sample itself, so the idle chief costs zero turns. The one-shot bank-monitor.sh instead runs from a Bash polling loop every 300 seconds so its 30-minute ring accumulates samples; that sampling interval is distinct from the chief tick’s sample cadence and adds no scheduler. An unchanged cache emits state=stale once, preserving the last measured projections until sampling resumes; each usage window resets its own history independently. The chief batches rulings into one message per decision point, never one per thought. ar-watch, pubci, and vault monitors stay in Bash; their cost is the EMIT, so filters emit terminal-state changes only. Report at MILESTONES only (LIVE, FINDING, READY, BLOCKED, HALTED, WRAPPED); no progress chatter; acks to a rotation are one line.
ox-alpha — the current hermes himmel_agent default
The hermes-oneshot lane takes its model from the himmel_agent hermes
profile’s default rather than from a pin in this repo. Hermes-routed critics do
NOT ask for that profile by name — hermes-critic.sh passes no -p, so it
gets whichever profile hermes has ACTIVE. The two land on the same model today
only because ~/.hermes/active_profile reads himmel_agent; that
coupling is a real gap, not a guarantee (last two notes below). The
himmel_agent default is stealth/ox-alpha (provider nous,
https://inference-api.nousresearch.com/v1) as of 2026-08-22 — free,
front-tier, and TEMPORARY. It was gpt-5.6-sol. Per the lane-adoption rule
(profile + tailor per lane; a codex-tuned 240s budget once starved the GLM
lane), it is measured here rather than assumed.
Measured 2026-08-22 on this machine, scripts/hermes/invoke.sh --profile himmel_agent:
| Axis | Result |
|---|---|
| Small prompt (~1.0 KB) — 3 runs | 12.4 / 12.7 / 14.7 s — p50 12.7s, p95 14.7s |
| CR-sized prompt (~45 KB real diff) — 2 runs | 58.8 / 64.9 s |
| Context accept probes (synthetic, ~4 chars/token) | 300K OK (43.0s), 600K OK (49.1s), 900K accepted (35.5s) — no rejection, no context_length_exceeded at any size; acceptance only, see the caveat below |
JSON-verdict reliability (scripts/cr/hermes-critic.sh --model "") — 3 runs | 3/3 valid fail-closed verdicts, rc=0, full key set; 108.2 / 57.3 / 82.8 s — p95 108.2s |
| Tool-call behaviour under the profile SOUL | correct — --toolsets coding, listed scripts/cr and answered COUNT=50, exactly matching ls -1 | wc -l; 29.3s; no refusal or format quirk |
Notes a future reader needs:
- A 900K acceptance does not prove the model attended to every token. The 900K probe returned
faster than the 600K one, which is what compaction upstream of the model
looks like.
context.overflowfor this lane is alreadycompact-continue; 900000 stays the declared operating window on that basis, unchanged — it was already an operator ruling/measurement before ox-alpha, and these probes establish ACCEPTANCE at that size, not a verified usable window. - Turns compacted by
compact-continuelose their thinking (preserved-thinking change; reported by the ticket, not independently verified) — no thinking blocks survive compaction, so later turns may re-think that work at token cost. --toolsets fsis not a hermes toolset — it exits 2 in ~1s withignoring unknown --toolsets entries: fs. Usecoding.- Recommended
CRITIC_TIMEOUT_SECSfor a hermes critic row: 180s (p95 108.2s x 1.5 = 162, rounded up). That is well under the shared codex-tuned 240s default, so a hermes row must set its owntimeout_secsto get the benefit — inheriting 240s is not wrong, only loose. The 108.2s p95 run overlapped a concurrent 900K context probe; the two clean runs were 57.3s and 82.8s, so 180s carries real margin. - No hermes row exists in
scripts/cr/critics.jsontoday (the panel iscodexonly), so there is no timeout value to correct here, and — contrary to what an earlier draft of this note said — adding one is NOT a config-only follow-up.critic-panel.sh’s per-member reviewer,critic-first-pass.sh, hard-requires--model(exit 2on empty), so a row deferring to the profile default (model: "", the whole point of #1811) needs a real code change there first. Separately, a live free-tier panel row would silently reverse the operator decision recorded inpr-check.md(“paid-by-default — free lane removed deliberately” after qwen3coder/gptoss/kimi were dropped for noise/errors) — that reversal wants its own explicit sign-off, not a side effect of a lanes.json/docs pass. Tracked as a planned follow-up (agreed-rate data for the eventual promotion call needs this wired in first; until then/cr-scoreshas nothing to report for hermes/ox-alpha —hermes-critic.shis not invoked by any live/pr-checkpath today). - The profile is what routes.
hermes-critic.shdoes not pass-p; the no--ppath lands on ox-alpha only because~/.hermes/active_profilereadshimmel_agent. The hermes root config still defaults togpt-5.5/openai-codex, so if the operator switches the active profile, hermes-routed critics silently change model. Doctor advisory:himmel-doctor.shcheck C21 WARNs when the active hermes profile’sconfig.yamlmodel.defaultno longer matches theprofileDefaultModelthis file’slanes.jsonrecords for thehermes-oneshot/hermes-criticsrows — warn-only, it never edits the profile or lanes.json, and never runs the hermes CLI (a plain offline file read). - Temporary. ox-alpha’s availability is time-limited; when it goes, the profile default moves and these numbers expire with it. Nothing in this repo pins ox-alpha by name, which is deliberate.
Effort calibration
Effort is a per-dispatch lever — use the full scale per item, do not flatten
to one default. Raise effort before raising model tier: Fable-5 low ≈
prior-gen xhigh, and the same shift applies down-tier.
The Opus lane runs Claude Opus 5.5 (the chief and headed-arm default,
claude-opus-5-5). Anthropic’s
Prompting Claude Opus 5.5
guide says effort names do not mean the same amount of thinking across models:
Opus 5.5 at medium (its default; Opus 5 defaulted to high) matches or beats
Opus 5 at high on coding and knowledge work, low comes close on several
coding evals, and at a given level 5.5 thinks more per turn than 5 —
especially at xhigh and max. Its instructions: start at medium, reserve
xhigh/max for work where a quality gain was measured, and lower effort
(not prompt wording) to get less thinking. So the opus row in lanes.json
moved from xhigh to medium: no xhigh gain has been measured on this
fleet, and carrying xhigh over from Opus 4.8/5 would buy longer, costlier
turns. Effort defaults carried over from a prior model should still be
re-swept against real evals rather than assumed — that sweep is still outstanding.
Temperature is Claude-API-only — deferred; rides the same sweep.
Where a claude-tier row’s effort is applied: the native
Telegram dispatch (scripts/telegram/run.ts spawnSpec) maps the run’s model
to its tier row (alias opus, or full id claude-opus-…) and exports the row’s
effort as CLAUDE_CODE_EFFORT_LEVEL, over any ambient value; a route’s own
env still wins, and a row with no level-shaped effort leaves the ambient
setting alone. The worker launcher mirrors this for native-lane workers — see the ruling below.
Ruling (2026-09-26): effort-at-dispatch is shipped, on both
paths. Re-verified live on main at cb43cfbfa. Two mechanisms cover the two
dispatch surfaces, both keyed off the same lanes.json effort field
(scripts/lanes/lanes.json:8-9, e.g. "id": "haiku" → "effort": "low",
"id": "sonnet" → "effort": "medium"):
- Telegram/native dispatch (
bfa9201a0):laneEffort()inscripts/telegram/run.ts:318resolves the model to its lane row andrun.ts:328-329folds the row’seffortintoCLAUDE_CODE_EFFORT_LEVELfor the spawned session’s env. - Chief-launched native worker (
79b205218):scripts/handover/chief-kit/headed-arm-worker.sh:746-764mirrors the same match rule (exact model id, orclaude-<tier-id>-prefix) forLANE=native— an explicit ambientCLAUDE_CODE_EFFORT_LEVELalways wins (checked before the lookup,headed-arm-worker.sh:747-748); otherwise it resolves the row viajqagainstlanes.jsonand exports it.scripts/handover/chief-kit/ test-headed-arm-worker.shcarries 18 assertions onCLAUDE_CODE_EFFORT_LEVEL(including cases 34a-34c: tier-row export, no-row leaves it unset, ambient value survives the lookup), all passing on current main (bash scripts/quiet-run.sh suite -- bash scripts/handover/chief-kit/ test-headed-arm-worker.sh).
A third, related mechanism — the tier-reason gate that requires a sanctioned
> **Tier:** opus|fable — <category>: <reason> line before an Opus/Fable worker
launches (a3260c8f6; category tags added in
d5a71757f) — is the “raise effort before tier” enforcement point referenced
above; it lives at headed-arm-worker.sh:565-584. All four shipping SHAs are
ancestors of current main and none has been reverted. This closes the question
as answered, not as a gap: no lane class was found uncovered.
2026-09-07 caveat: the fable/sonnet effort defaults above predate
Fable 5.1 and Anthropic’s Fable 5.1 prompting guide’s instruction to re-run
the effort sweep even for a default already tuned on a prior generation —
that re-sweep has not happened for these lanes. Tracked under the same
re-sweep above; this line exists so the gap is visible at the
point a reader would check it
(handovers/yotamleo/himmel/specs/eval/HIMMEL-fable51-prompting-audit-2026-09-05.md). Same audit’s safeguard-false-positive mitigations:
safeguard-false-positives.md.
GPT-6 Astra — the claudex launcher defaults to medium, low first
The claudex launcher (scripts/claude-codex, documented in
scripts/lanes/lanes.json) defaulted CLAUDE_CODE_EFFORT_LEVEL to high. It
now defaults to medium (an explicitly exported value still wins). The
reason is a vendor calibration note, recorded on the ticket as reported (we have
not re-measured it):
OpenAI/Tibo says GPT-6 Astra on low performs better than GPT-5.6 Sol on high, and anyone who ran Sol at high should move to low or medium on Astra.
The ladder stays; only its resting point moves. Mind which model each lane runs:
codex-exec follows the codex critic in scripts/cr/critics.json (currently
gpt-6.1-sol), and the claudex launcher’s own default model now
matches it (gpt-6.1-sol, CODEX_MODEL in scripts/claude-codex, overridable) —
both lanes run at effort medium by default; export
CLAUDE_CODE_EFFORT_LEVEL=high there if medium under-delivers. The rungs:
| Effort | Use for |
|---|---|
low | the first lever for a mechanical chunk — rename, fan-out edit, fixture/doc sync, a change whose acceptance check is a single command |
medium | default — a well-specified single-worker implementation brief |
high | a multi-file or long-running brief where medium measurably under-delivers |
xhigh | rare; a hard reasoning call, not a default |
Raise effort per dispatch, not per station: a fleet-wide high default spends
codex usage budget tokens on every worker for a quality gain the note above says Astra does
not need. What is not in this change: the measured before/after (an effort
sweep on real briefs) that the ticket also asks for — it belongs to the
usage budget attribution table, which this change leaves untouched.
Usage-scarcity routing rule
While the Claude weekly subscription usage budget is the scarce bucket (bank-preflight.sh now
prints both usage budgets on its one line — five_hour=… seven_day=… codex=5h<n>/wk<n>,
codex=? when the codex figure is missing, stale or the probe fails, and never
a refusal on that account), an Opus parent routes implementation chunks
codex-exec first:
- codex-exec (
scripts/codex/dispatch-codex-exec.sh; Sol — the wrapper’s default model is thecodexcritic inscripts/cr/critics.json, currentlygpt-6.1-sol(gpt-6-solbefore it) — at--reasoning-effort medium,lowfor a mechanical chunk) — the default home of a well-specified implementation chunk, because it draws the codex usage budget rather than the Claude one. - A Sonnet child — only where the codex lane cannot act.
Where codex-exec cannot act today (from the wrapper and the harness docs, not invented — extend this list only from a new observation):
- It cannot commit in a git worktree. The dispatcher pins
--sandbox workspace-writeand refuses--add-dir/-C/--cdand every sandbox-widening flag (dispatch-codex-exec.shinvariant 4), and a linked worktree’s.gitis a file pointing at<primary>/.git/worktrees/<name>(harness-compat.md, worktree note) — a location outside the sandboxed workspace that the dispatcher gives no way to add. Observed in practice: the sandbox edits the tree and the parent commits. Plan the chunk as “edit files, parent commits” (the tree must be clean at dispatch under--shared-branch, so commit before the next handoff). This is a step the parent takes, not a reason to send the chunk to Sonnet: codex-exec still does the implementation. - Its terminal writes are hook-fenced.
.codex/hooks.jsonwiresblock-terminal-write-fence.shon Bash/PowerShell:git push, remote-URL rewrites,ghPR-mutations (create/merge/comment/…) and network CLIs (curl,wget,iwr/irm) are denied unlessCODEX_EXTERNAL_WRITES_OK=1. Edit/Write are fenced byblock-edit-on-main.sh, and lesson-enforcement paths byblock-lesson-enforcement-writes.sh. A chunk that must push, open a PR or call a network API is a Sonnet child’s (or the parent’s) job. - One writer per shared branch.
--shared-branch <branch>takes the repo-wide single-writer lock and refusesmain/masterand a dirty tree (exit 2), and exit 4 when another writer holds the lock — parallel codex-exec chunks onto one branch are serial by construction.
Everything else — editing files inside the worktree, running a scoped suite — is codex-exec’s. This rule is the routing default, not a measurement: the measured before/after of the Claude usage budget saving is deliberately left to the usage budget attribution table (out of scope here, and unchanged).
Context mode — an arming-time choice, not a station default
Context mode used to be set once, station-wide, by a [1m] suffix on the
user-level model key in ~/.claude/settings.json. It is now chosen per arm
via --context 1m|standard (arm-resume.sh, and the 7th positional of
headed-arm.sh). The baseline default is standard;
the headed launchers now use per-profile defaults, including
design’s declared 1m mode and numeric ceiling, without an operator override. A
chief arm (launched through chief.sh, which has no --context flag of
its own) can still opt into 1m via CHIEF_CONTEXT=1m in the launching
shell.
--context drives two independent levers, because measurement showed one of
them alone is a no-op on this account:
1m→ append[1m]to the model id where the id accepts it. Explicit operator overrides pass--autocompact auto; a headed profile default passes that profile’s declared numericautocompactceiling.standard→ no suffix, and pass--autocompact 200000.
Method for everything below (2026-09-07, zero API spend): throwaway sessions
launched with a per-session --settings overlay whose statusLine command was
cat > <file>, with no prompt ever sent. That captures Claude Code’s own
statusline stdin — model.id, model.display_name, and
context_window.context_window_size — without an API turn.
Per-profile ceilings. plugin-profiles.json declares
contextMode (standard or 1m) and integer-token autocompact;
plugin-profiles.mjs <profile> --context returns those fields separately from
Claude settings JSON. Absent fields default to standard / 200000, including
the null operator sentinel. Both headed-arm-worker.sh and headed-arm.sh
validate this output before launching. A composed list takes the largest
member ceiling, and 1m only when a member explicitly declares it.
design declares 1m / 1000000 (no early compaction), so a
design worker needs no env or brief override. All other profiles, including design
kit add-ons launched alone, remain standard / 200000. Integer ceilings below
200000 or above 1000000, malformed fields and unknown profiles refuse; a
standard profile cannot declare a ceiling above 200000. Child autocompact
inheritance remains UNMEASURED: no real session was launched for this audit.
WORKER_SESSION_CONTEXT=1m plus the sanctioned brief line
> **Context:** 1m — operator-ruling: <reason> remains a per-worker override to
auto, as does CHIEF_CONTEXT=1m for a chief. The brief line alone does
not elevate a standard profile; a bare WORKER_SESSION_CONTEXT=1m without it refuses. Launch logs
record the resolved profile, ceiling and context source; argv must match the
profile plus the sanctioned override. The older arm-resume.sh context API is
unchanged; the profile defaults here apply to the two headed launchers.
Context-budget evidence audit — 2026-10-02
Read-only collection: gh pr list --state merged --search worker-burn --limit 1000 --json number,body,mergedAt. Of 813 returned bodies, 738 contain numeric
worker-burn lines. There are 739 worker records: #1113 explicitly records its
original worker and a separate N369 finish worker. One record per measurement line,
not one per appearance of the words worker-burn; unavailable and nonnumeric
placeholders are excluded. Merged dates span 2026-09-12 through 2026-10-02
(UTC), PR numbers #643–#1604 (not every PR in that interval is a sample).
These bodies do not unambiguously state the measured author’s profile.
--profile references describe tests or shipped launch behavior, and a sample
profile=... session=...-dry launch record is not author attribution. They
are therefore an unassigned cohort, never imputed to worker-impl by ticket
subject, date or a launcher default. Its 85,715 calls yield call-weighted
avg-ctx 122,348 tokens (sum(calls * avg-ctx) / sum(calls)), session-mean
avg-ctx 115,408, and session-mean first-turn 65,281. Published k
values have 100-token precision; aggregates are rounded to whole tokens, not
claims of exact underlying usage. This is Q8 data, not authorization to lower
the relay ceiling.
| Profile cohort | Attributable merged workers (n) | Existing profile observation | Budget decision |
|---|---|---|---|
worker-impl | 0 | 70,500 first-turn, #777 (73,000 control) | keep 35,000 |
telegram | 0 | 32,541 first-turn, #680 | keep 45,000 |
lane-impl, lane-review, lane-content, bare (each) | 0 | none | keep 45,000 each |
chief-relay, console-judge (each) | 0 | none | keep 35,000 each |
user, design, console (each) | 0 | none | keep 50,000 each |
design-motion, design-3d, design-imagegen, design-a11y, design-diagram, design-slides, design-reference, design-trial (each) | 0 | none | keep 50,000 each |
operator | 0 | null sentinel, no budget | unchanged |
The two historical observations come from the registry’s existing _comments;
their source PRs merged 2026-09-17 (#777) and 2026-09-12 (#680). Neither is three
measured workers, and no per-profile avg-ctx can be inferred from them. Thus
every contextBudget is retained under the n below 3 rule, even where the
legacy target is demonstrably too low. A follow-up must put a profile field in
the worker-burn measurement line to enable honest per-profile recalibration;
adding that telemetry is outside this PR.
Which model strings accept the [1m] suffix
--model <argument> → the model.id and display_name Claude Code reports:
--model argument | resulting model.id | display_name | suffix honoured? |
|---|---|---|---|
claude-opus-5[1m] | claude-opus-5[1m] | Opus 5 (1M context) | yes |
opus[1m] (alias) | claude-opus-5[1m] | Opus 5 (1M context) | yes |
claude-opus-4-8[1m] | claude-opus-4-8[1m] | Opus 4.8 (1M context) | yes |
claude-haiku-4-5-20251001[1m] | claude-haiku-4-5-20251001[1m] | Haiku 4.5 (1M context) | yes (4.5 only; 5.5 is natively 1M, unmeasured) |
haiku[1m] (alias) | claude-haiku-4-5-20251001[1m] | Haiku 4.5 (1M context) | yes |
claude-sonnet-5[1m] | claude-sonnet-5[1m] | Sonnet 5 | passed through (Sonnet 5 is natively 1M here) |
sonnet[1m] (alias) | claude-sonnet-5[1m] | Sonnet 5 | passed through |
claude-fable-5-1[1m] | claude-fable-5-1 | Fable 5.1 | no — silently stripped |
fable[1m] (alias) | claude-fable-5-1 | Fable 5.1 | no — silently stripped |
Reproduced twice, the second time with the model passed as a single argv element
via a wrapper script, so no glob or word-split can explain it. Fable is the
only family that refuses the suffix, in both alias and full-id form — which is
why a 1m chief arm gets the --autocompact half only, and why the arm log
says so rather than claiming a mode it could not set.
2026-09-22: the chief and headed-arm default moved to
claude-opus-5-5. The rows above were measured on Opus 5 and have not been
re-run for claude-opus-5-5 — its [1m] handling and plain-launch
context_window_size are unmeasured; re-measure with the zero-spend method
above before relying on them.
2026-09-28: the Sonnet tier moved to claude-sonnet-5-5
(relay default, new worker dispatches). Anthropic’s model page documents a 1M
context window and no beta header. The Sonnet 5 rows above have not been
re-run for claude-sonnet-5-5: its [1m] suffix handling, the sonnet[1m]
alias target, and its plain-launch context_window_size are unmeasured —
re-measure with the zero-spend method above before relying on them.
What a plain launch reports
| launch | context_window.context_window_size |
|---|---|
--model claude-opus-5 (plain) | 1000000 |
--model claude-fable-5-1 (plain) | 1000000 |
--model claude-sonnet-5 (plain) | 1000000 |
--model claude-opus-5 --autocompact 200000 | 1000000 |
every [1m] form above | 1000000 |
A live session launched with a plain --model claude-opus-5 ninety seconds
after the settings [1m] was removed reported context_window_size 1000000,
and Claude Code’s own used_percentage of 8 against ~81.3k tokens of usage —
8.1% of 1M, not 41% of 200k. So removing the suffix does not move a session
off a 1M window on this account. A --context standard that only stripped the
suffix would be a measured no-op; the --autocompact half is what makes it real.
--autocompact <auto|100k–1M> does not change context_window_size — it changes
where auto-compaction fires, which is the cost driver actually
measured (turns above 200k). It is accepted by the CLI; the threshold is not
observable pre-prompt — the statusline payload carries no autocompact field, so
do not claim a stronger verification than that.
Subagents inherit — there is no per-subagent control
The Agent tool’s model parameter takes sonnet|opus|haiku|fable only. It
selects a tier; it cannot select a context mode. A subagent inherits the parent
session’s WINDOW — a measured 1M chief’s general-purpose Sonnet
child peaking at 966,764 tokens, far past any 200k ceiling. Be precise about
which half is inherited, because --context sets two different things: the
window, and the autocompact threshold. Only the window is measured here, and on
this account the window is 1M everywhere — the table above shows
--autocompact 200000 leaving context_window_size at 1000000, so there is no
such thing as a “200k worker” on this station, only a worker that compacts earlier.
Whether a child inherits the parent’s --autocompact threshold was NOT
measured — do not assume a standard worker makes its children compact early.
No per-subagent control exists — choose at the worker’s arming.
| lane | default mode | override |
|---|---|---|
chief arm (*-chief.md) | standard | CHIEF_CONTEXT=1m |
| headed worker / worker arm | profile default (design: 1m / 1000000; others: standard / 200000) | WORKER_SESSION_CONTEXT=1m plus reasoned brief Context line; either alone does not enable 1m |
| subagent of either | inherits the parent | none — set it on the parent’s arm |
Given that, the chief-side mitigation is a lean parent (the
relay/judge split), not a scan: scripts/lanes/ceiling-conformance.sh
does not reach subagents, since they carry no -n name.
The worker launcher pins the default, it does not just document it
Workers were already pinned to the standard (200k-autocompact) context mode by
the table above — but the fact lived only in prose, one convention every
ad-hoc chief-kit copy of the worker launcher had to remember correctly.
scripts/handover/chief-kit/headed-arm-worker.sh makes it structural: a thin
wrapper around headed-arm.sh whose resolved worker launch must carry the
cost-driving --autocompact 200000 pair. WORKER_SESSION_CONTEXT=1m now refuses with
exit 2 instead of weakening the worker ceiling; absence of a [1m] model suffix
is not equivalent because Fable-family launches can strip that suffix while
still receiving --autocompact auto. Scheduled workers opt into the same
guard with arm-resume.sh --tier worker; --tier worker --context 1m refuses before
scheduler work. A caller that hands the headed wrapper an already-[1m]-
suffixed model still gets it forwarded unchanged, and headed-arm.sh strips
the literal suffix under standard mode (scripts/handover/chief-kit/ test-headed-arm-worker.sh asserts this end-to-end). The wrapper also adds the
worker-only IMPL_GUARD_OK=1 env and folds WORKER_SESSION_REPO onto headed-arm.sh’s own
HEADED_ARM_REPO seam.
Sanctioned operator-ruled 1M opt-in. The refusal above is not
absolute — a brief (the handover doc headed-arm-worker.sh is given) carrying a
well-formed > **Context:** 1m — operator-ruling: <non-empty reason> line
opens the door for that one worker, same shape as the Tier-line gate:
a fixed marker, grepped by exact prefix, non-blank free text
required. Without the line — or with an empty reason, a malformed line, or no
line at all — the refusal is byte-identical to before. On acceptance the
wrapper still runs the profile, worker system prompt, fleet reservation and subscription usage budget preflight
unchanged; it exports CHIEF_CONTEXT=1m in its own process (after the
env scrub already ran, so that scrub’s own logic is untouched) so
headed-arm.sh’s independent CHIEF_CONTEXT-gate — a file this ticket does
not edit — passes for that one exec, and it switches the internal
HEADED_ARM_REQUIRED_AUTOCOMPACT argv-verify value from the standard 200000
to the resolved auto. The arm log and the --dry-run report both record
context=1m (operator-ruling) plus the reason text.
Worker checkpoint or handover is at 75% of the worker’s --autocompact ceiling
(150,000 input tokens of the 200k pin), with that ceiling as the backstop,
when the launch turns the guard on. Measured 2026-09-27..10-03 over 399
workers: 349 compacted, 309 of those still reached WRAPPED after their last
compaction, and every compaction fired between 157k and 176k. The earlier 60%
hand-off only added relaunches. A later change moved to 65% (27k of headroom),
which stopped every opus worker at 13% fill; a further change set 75% (150k), 7k
before the earliest compaction, and made the guard off by default. The share is
of the ceiling, not the window: an opus worker reports a 1,000,000-token window,
so the 200k ceiling is 20% fill and the threshold 15%.
guard-worker-context-handoff.sh enforces it in two modes chosen at launch with
--context-guard: compact (commit, push, a CHECKPOINT <sha> pushed bullet,
then compact and carry on) and handoff (a RESUME brief, then stop). Reference:
worker-context-guard.md. The absolute turn count is
message.usage.input_tokens + cache_read_input_tokens + cache_creation_input_tokens from the last assistant message in that session’s
own transcript; it excludes output tokens and is not cumulative spend.
scripts/context-fill.sh prints that count alongside fill, keeps --percent
as the bare integer compatibility surface, and interprets --warn-at 1..100
as percent versus values above 100 as token counts. A worker checks
--warn-at 75 after every completed step (docs/handover/worker-preface.md). The
handover skill’s generic task template still checks --warn-at 45; it serves
general handover sessions, not workers.
Fleet-size cap — a provisional 4, grounded in the incident
The context pin above answers how much window one worker burns; it says
nothing about how many workers run at once. 2026-09-07: 9–11
concurrent workers exhausted a WEEK’s usage bucket in under 48 hours, forcing a
fleet-wide halt. scripts/lib/bank-preflight.sh now counts live
workers (processes whose argv carries claude … -n HIMMEL-…/-n LUNA-…,
excluding names containing -chief — chiefs are not workers) and refuses a
new arm — token SKIPPED-FLEET — once the count is at or above
HIMMEL_FLEET_CAP (env, else .env, default 4). Wired at ARM time in
both scripts/handover/chief-kit/headed-arm-worker.sh and
scripts/handover/arm-resume.sh, so a new worker is refused before the
scheduler entry (or the konsole window) is ever created — cheaper than
refusing after a signal/deadline wait. Off switch: FLEET_CAP_OK=1 in the
launching shell only (a per-call prefix is refused by
block-chokepoint-env-prefix.sh, scripts/chokepoints.json).
load_dotenv resolves the PRIMARY checkout’s .env even when
HEADED_ARM_REPO is a linked worktree (the gitignored file lives only in the
primary), but only a value actually set there counts: if the launcher’s
environment does not export HIMMEL_FLEET_CAP and the primary .env does
not set it, the cap falls to 4 and the fifth worker is refused with a
full-looking census. Put HIMMEL_FLEET_CAP on the launcher line itself
rather than relying on the file.
Why 4, not some other number. 4 is a provisional operational cap, not a
derived sustainability figure — no session’s own measured burn rate or duty
cycle has been isolated (codex review findings, twice: the bucket-count
arithmetic this paragraph used to run does not by itself establish a
sustainable concurrency number, and conflates bucket capacity with reset
frequency). The one concrete evidence point is the incident itself: 9–11
concurrent workers exhausted a WEEK’s usage bucket in under 48 hours.
4 — well under that — is the starting cap chosen to leave
headroom for the chief lane (never gated by this cap) and for legitimate
concurrency bursts the operator explicitly overrides via FLEET_CAP_OK=1,
pending a proper per-session burn-rate measurement to replace it with a
derived number.
Launch vs. read. The SKIPPED-FLEET refusal itself only
fires when the caller sets CADENCE_USAGE_LAUNCH=1, declaring an actual arm
intent (arm-resume.sh, headed-arm-worker.sh) — a plain usage budget READ (a
/pr-check critic pass, a statusline probe, a hook-smoke test) gets the same
informational FLEET native=N claudex=M total=X/CAP line, printed
unconditionally either way, but is never itself refused by a cap that exists
to gate new arms, not reads.
claudex worker — implementor-default operating model
headed-arm-worker.sh --lane claudex (or WORKER_SESSION_LANE=claudex) routes a worker arm
through scripts/claude-codex — the Claude Code harness running against
GPT-6.1 Sol (gpt-6.1-sol) via the local CLIProxyAPI service — drawing on the codex weekly
usage budget (bank-preflight.sh parks a claudex worker on
scripts/lanes/codex-bank-probe.ts’s cache, never the Claude five-hour/weekly
check) instead of the Claude subscription usage budget the native lane spends from.
A live lane. claudex is a live implementation lane in
scripts/lanes/lanes.json: its row has no dormant entry, /lanes lists it
without a DORMANT note, and no opt-in variable is needed to dispatch it. Two
routes reach it:
- A worker (
headed-arm-worker.sh --lane claudex) runsgpt-6.1-solby default (a brief’smodelkey overrides it). The launcher’s own default effort ismedium; a chief-dispatched worker runs atxhighthrough its brief’seffortkey, which exportsWORKER_SESSION_EFFORT. - A one-off dispatch (
dispatch-lane.sh --lane claudex) is accepted once the flow-runs ledger holds 5 consecutive verified passes onclaudex/branches (readiness.passesRequired). Until then the readiness gate refuses it.
CLAUDEX_LANE_OK=1 is not a dispatch opt-in. Neither scripts/claude-codex
nor spawn-claudex.ts sets it; only headed-arm-worker.sh --lane claudex
exports it into the worker it launches. usage-preflight.sh reads it to label
that worker’s fleet line claudex, and chief-context.sh strips it before it
arms a chief. One run still requires it: scripts/eval/lane-quality/run.sh --lane claudex exits 3 unless CLAUDEX_LANE_OK=1 is on the command.
Default while the Claude weekly is scarce. A well-specified, single-worker
implementation brief is the claudex lane’s home case precisely because it
draws a different usage budget: every worker that ships on claudex is a worker that never
touched the Claude weekly bucket at all, which is the whole point when that
bucket is the constrained resource (the fleet-cap incident
above, is what scarcity looks like). Prefer --lane claudex for routine worker
implementation work; fall back to native only when the work needs something
claudex structurally cannot provide (next section).
Effort ladder. WORKER_SESSION_EFFORT maps onto CLAUDE_CODE_EFFORT_LEVEL, exported
into the launched process via HEADED_ARM_LAUNCHER_ENV — the same
low/medium/high/xhigh scale as Effort calibration
above, not a separate claudex-only scale:
WORKER_SESSION_EFFORT | When |
|---|---|
medium (default, unset) | routine single-worker implementation briefs — the common case |
high | multi-file or long-running briefs — raise before reaching for a native tier |
xhigh | genuinely hard worker work only — rare |
What stays on Claude tiers. claudex is an impl-only lane (class: "impl" in lanes.json) reached through one wrapper script that cannot see
or message anything else running — it is not a substitute for:
- Orchestration / parenting — a parent that spawns and coordinates
children needs
ListAgents/SendMessage, which no claudex worker has (next paragraph). Parenting stays native. - Automated review / critic passes — cross-model review already has its own dedicated
lanes (
codex,hermes-critics,codex-execas critics via/pr-check); claudex is not one of them and is never itself a reviewer. - Anything needing live coordination with another running session —
chief arms,
war-room-style multi-agent flows, or a worker an operator expects to redirect mid-flight viaSendMessage. - Judgment calls above the brief — a claudex worker executes a well-specified brief; a call that needs escalation (Fable-class judgment, a genuinely ambiguous ticket) belongs on a native tier that can actually escalate.
Operating model — claudex workers cannot ask, so they never park. A claudex
worker is scripts/claude-codex (Claude Code) talking to GPT-6.1 Sol (gpt-6.1-sol) through
CLIProxyAPI; it is not a peer session in this operator’s ListAgents
registry and structurally cannot see or message any native session —
ListAgents/SendMessage simply do not reach it, and it cannot reach them.
The only channel a claudex worker has back to the operator is the handover
document itself: it writes its status, findings, and any blocker straight
into the handover doc (the “N53 Results”-style section this ticket’s own
handover uses), and the chief polls that doc on its own cadence. This makes
one rule load-bearing for every claudex worker brief: never park for
authorization. A native worker that hits a decision point can message a
chief and wait; a claudex worker that does the equivalent — idling on a
signal file, a park loop, an implicit “waiting for approval” — is invisible
to everything except the next doc poll, which can be arbitrarily far away.
Make the reasonable call, record it and the reasoning in the handover doc,
and keep going; halt only for something genuinely destructive or ambiguous
enough that guessing wrong is worse than the wait, and say so explicitly in
the doc rather than going quiet.
The one sanctioned park: the READY → GO merge hold
(docs/handover/worker-preface-claudex.md) does not violate “never park” — it is
bounded by the file inbox channel (scripts/handover/chief-kit/inbox-send.sh
writing, scripts/hooks/claudex-inbox-hook.sh delivering), observable (a READY
bullet in the doc, held by one background Bash wait on the doc for the
matching GO file (merge approval), with a 30-minute timeout, re-issued rather than open-ended), and
required because a merge is irreversible. See also docs/internals/retask-channel.md for the token
discipline governing the GO itself. No other wait qualifies.
Measuring the split. scripts/lanes/bench/scorecard/fleet-scorecard.sh
compares claudex and native workers on real fleet work. It prints one row per
worker in two halves: output quality (judge verdicts, CR findings, rounds to
clean, pushes after READY, main reds, 7-day escapes) and fleet management
(launch to LIVE and READY, chief interventions, protocol misses, stalls,
denials, cost-eq per merged PR). A per-lane table gives totals and medians.
Run it as fleet-scorecard.sh --manifest <chief>.fleet.json [--manifest …] --since <ISO time> --prs <gh pr list --state all --json …>. It reads local
ledgers only and writes to stdout or a local --out path. Any source it
cannot read prints n/a (<reason>), never 0.
Every launch path applies a role-matched plugin profile
A claude session’s plugin set is fixed at launch by --settings <profile file>
(scripts/lanes/plugin-profiles.json). Previously only headed-arm-worker.sh
did this; the chief arm, arm-resume.sh, hop.sh, schedule-resume.sh, the
morning-briefing --llm call and the automated review critic launched claude with the
operator’s FULL plugin set. They now all go through
scripts/lanes/profile-settings.sh <profile> (writes
~/.himmel/launch-profiles/<profile>.json, exit 2 = refuse, never a silent
fallback to the full set) and the profile is picked from the role:
| Launch path | Profile |
|---|---|
chief.sh arm / headed-arm.sh --role chief | console (handover, himmel-ops, qmd, lean-skills; no pr-review-toolkit) |
arm-resume.sh, schedule-resume.sh | from the resumed doc via scripts/lanes/role-profile.sh; --profile <name> overrides (arm-resume) |
hop.sh | HOP_PROFILE, else console / worker-impl from the caller’s role env, else user |
worker brief (headed-arm-worker.sh) | --profile / WORKER_SESSION_PROFILE win; else a profile: <name> line in the brief’s first 60 lines; else the unprofiled-launch refusal stands |
generate-morning-briefing.sh --llm, cr/hermes-critic.sh | bare (floor plugins only) |
role-profile.sh precedence: explicit profile: line, then chief doc, then a
doc carrying a RETASK token (worker-impl), else user. Sites that deliberately
run with no profile (the probes, claude-headless.sh, the cadence runners)
carry a launch-profile-ok: reason; scripts/lanes/test-launch-site-profiles.sh
scans scripts/ and fails on any other claude exec.
Fanout and afk-shift subagents inherit the parent’s plugins (see
“Subagents inherit” above - there is no per-subagent plugin control). So the
parent must be armed with the profile its CHILDREN need, not just the one it
needs itself: a chief that dispatches design work arms with a profile that
carries the design tools, and a fanout parent that will spawn reviewers arms with
a profile that includes the review plugins. Pick it when arming (--profile
on the worker, profile: in the brief, --profile on arm-resume.sh); it cannot
be fixed from inside a running session.
Known gap: the Windows .bat/WSL branch of arm-resume.sh still relaunches
without --settings (the profile file lives in the WSL home); tracked as a
follow-up.
Fold or delegate — the main-loop test
Anthropic’s guidance (“Building agents: when to fold subagents into your main
agent loop”, read with “Subagents in Claude Code”) defaults to one agent loop
and keeps a subagent only for measured context bloat, parallel work that wants
a fresh window, or a fresh view. The rule-by-rule audit against this harness is
in the state repo: specs/research/HIMMEL-7329-fold-subagents-audit-2026-10-24.md.
Why the worker fleet is not subagent sprawl. The fleet is split by context, not by problem type: one ticket is one worker, one worktree, one PR, and each worker is a single main loop that implements its own ticket inline. The orchestrator-inline-guard’s worker exemption keeps it that way; the guard only stops a top-tier parent from doing a worker’s job.
Measured mix (himmel sessions, 3 days to 2026-10-24). 408 subagents across
434 sessions; judges were 343 (84 %) at ~44M input-token-equivalent, general-purpose
61 (~18M: about 26 haiku board or tracker publishes, about 11 sonnet VM runs),
Explore 4. Workers rarely spawn. Judges change the chief’s recorded prior in
27 % of 831 verdicts (flipped: y), so they pass the fresh-view test; whether a
cheaper tier rules the same is unmeasured.
| Role / profile | Delegates | Verdict |
|---|---|---|
| chief | judges, haiku publisher, sonnet VM runner | right shape; judge tier and publisher failures unmeasured (HIMMEL-7330, HIMMEL-6825) |
| worker-impl | rarely; implements inline | right; no live cost budget (HIMMEL-7331) |
| consult / judge session | not measured (a consult has Edit/Write denied) | no evidence either way |
| bare, telegram | not measured | no evidence either way |
Fold-vs-keep is an eval, not a habit (HIMMEL-7330): per subagent class, ≥10 golden tasks, ≥2 runs per task per arm, compare the tasks that flipped rather than the mean, keep the subagent only where folding scores worse.
Cost posture
Fable stays conserved (limited release) — the spread optimizes Sonnet/Opus/impl lanes. Sonnet 5 carried introductory pricing ($2 per million input tokens / $10 per million output tokens) through 2026-08-31; recalibrate in September against Anthropic’s published pricing.
Pricing and per-lane benchmark analysis are volatile: treat any figure here as needing revalidation before it drives a routing decision.
scripts/lib/bank-attribution.sh turns a burn on the Claude
subscription usage budget into a per-session Markdown table — turns, tokens, and wake
source, read straight from ~/.claude/projects/**/*.jsonl — instead of an
unattributed drain. Its cost model: usage budget burn ≈ Σ over sessions of (turns ×
context size at that fill) + subagent contexts, with orphan shells, Monitors,
and codex rows costing 0 on the Claude usage budget.
Measured review loops consumed 29–54M input tokens per PR, with review rounds 4+ alone consuming 16–26M; one stale-base retry burned 17M without changing the reviewed result. The harness therefore caps the paid correction loop at three review rounds per branch. The review-round-4 panel still executes and produces the current head’s result rows; disposition then defers suggestion/nit-only residue to one operator-supplied ticket without a subsequent correction review. Critical and Important findings remain blocking at every review round, and every fresh head still requires its own review even after the branch has reached the cap.
Lane-quality eval harness
Routing should follow measured quality per lane and model.
scripts/eval/lane-quality/run.sh is the measuring tool. It runs the same
frozen task set against one lane and model, so adding a model adds one run and
one row. Phase 1 ships the harness and proves it on the native lane. It makes
no routing claim yet: the table and the per-task-type routing come in
phase 2, once every lane has run.
Tasks (tasks/<id>/): four himmel-shaped jobs, each with a prompt.md
(all the agent sees), a hidden accept.sh, and a reference/ solution:
| Task | Shape |
|---|---|
shell-red-green | a new shell script plus its RED/GREEN test; acceptance also runs the agent’s test against a stub, which it must catch |
doc-plus-code | a new flag on a fixture script plus the matching README change |
hook-refusal | a PreToolUse hook that refuses curl-into-shell and fails closed on bad input |
finding-verify | two review findings, one real and one not; verdicts must cite the deciding line |
Each task runs in a fresh detached worktree at the pinned BASE_SHA, with the
task’s fixture/ committed on top under a fixed author and date. The suite
test-lane-quality.sh checks that every accept.sh fails on the untouched
fixture and passes on the reference.
Per task, runs.jsonl records:
- the acceptance result (
accept_passed/accept_total); - a blind judge score. Opus, given no tools, rates correctness, scope discipline, test quality and honesty from 1 to 5. It sees the task, the diff and the agent’s final report with every model and lane name redacted. It never sees the acceptance result, so the two signals stay independent;
- wall time, turns, tool calls (deduplicated by id) and compactions. A single headless run cannot hand off, so handoffs are measured only when a lane is driven through its worker launcher (phase 2);
- cost: the agent’s
total_cost_usd, which on a subscription lane is the API-price equivalent, and the account-wide five-hour subscription usage budget reading before and after. Any other session running at the same time lands in that delta, so run sweeps on a quiet fleet; - friction: permission denials, hook denials, out-of-scope paths, and
peeked, which flags a transcript that touched the hidden tests.
Run a lane:
bash scripts/eval/lane-quality/run.sh run --lane native --model claude-haiku-5-5bash scripts/eval/lane-quality/run.sh table ~/.himmel/eval/lane-quality/<run-id>Every task is usage budget-preflighted before its agent call
(scripts/lib/bank-preflight.sh; anything but PROCEED stops the sweep with
exit 75). Each agent run and judge run is pinned to
native auth and declares its permission mode: auto for the agent, as workers
run, and dontAsk with no tools for the judge. --max-usd (default 3) caps
the sweep. No task starts once the summed total_cost_usd reaches the cap, and
each agent call gets the remainder as --max-budget-usd.
OpenRouter (phase 2). The operator approved this lane.
--lane openrouter --model haiku runs the agent through
scripts/claude-openrouter, so the launcher’s egress matrix, its PHI guard
and its credit floor all apply (credit below the floor or unknown means no
launch). The alias resolves to the launcher’s pinned OpenRouter slug. Other
models are refused before launch until they get a price row. The judge still
runs native.
Claude Code does not recognize the gateway slug (measured on
anthropic/claude-haiku-4.5, which logs unrecognized_model with
costBasis: unknown; the launcher now pins anthropic/claude-haiku-5.5,
which has its own PRICES row), and its
total_cost_usd is exactly 5x list price, whatever the token mix. One task
reported 2.09 USD where its tokens cost about 0.42 at list price. So on this lane cost_usd is repriced
from modelUsage tokens at list prices (a PRICES table in run.sh), and
the Claude Code figure is kept as reported_cost_usd. --max-usd caps that
real spend. The per-call --max-budget-usd is in Claude Code’s units, scaled
by a logged factor of 4, which stops a call before the real remainder is
spent. That bound relies on the 5x ratio. An unpriced model, a run with no
reported cost or no priced tokens, or a reported cost that is not 5x its list
price, leaves the cost unknown and stops the sweep.
Each row records metered_before and metered_after, which are
openrouter-cost.sh --raw balances. The credit endpoint lags, so a per-task
delta can under-report. Re-read the account balance after the sweep before
quoting its spend. The balance is the real figure. In the first live sweeps
it fell 12-16% more than the list-price repricing (2.00 against 1.75 on one
sweep), so the repriced cost carries a 1.2 markup.
Not enabled. --lane deepseek|claudex exits 3.
- DeepSeek also needs the station opt-in
HIMMEL_DEEPSEEK_INFERENCE_OK=1, exported in the launching shell, and it runs as its own worker once the operator sets it. - claudex bills another usage budget and was not part of the approval.
Escalation shape
The parent does not have to be the top model — an Opus parent spawns a Fable
child for the one hard call; the child answers and returns. The top-tier
parent-selection restriction and its downward-delegation rule are directives
and stay resident in CLAUDE.md — a directive behind a
lookup is a dead directive. What belongs here is the rationale: Fable is
limited-release and conserved; when it is the parent, its role is planning,
judgment, and final synthesis while every implementation chunk goes to a lower
lane. The delegation floor in CLAUDE.md still applies: work
finishable in a handful of tool calls stays with the parent, and verification
is never delegated.
The inline-implementation anti-pattern, its single per-PR exception, and the
second-round batching rule are directives and stay resident in
CLAUDE.md — a directive behind a lookup is a dead
directive. What belongs here is the rationale: batching exists because review rounds
are where a top-tier parent quietly burns its scarce weekly quota on mechanical
edits, and because a worker lane on a shared branch can absorb several findings
per dispatch instead of one round-trip each.
The launch-time > **Tier:** … — tier-return: <reason> category tag that
headed-arm-worker.sh validates is a different thing and stays.
An escalation is measured as an observed re-dispatch, not a declared one: a Sonnet subagent whose task the parent then dispatched again at a higher tier. That is the input a replacement G10 gate should read; until one exists, escalation rate is unmeasured, and a report must say so rather than print zero.