Skip to content

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 /lanes lists on this machine. AGENTS.md is 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

LaneBest forEffort / notes
Haiku 5.5bulk 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.5scoped research; default implementor for well-specified impl briefsmedium 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.5multi-step reasoning; default parentmedium default (≈ Opus 5 high); low for scoped impl; high for heavy parenting; xhigh/max only where a gain was measured — see Effort calibration
Fable 5judgment, taste — hardest calls; escalation targetscale 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.

CellAcceptedAgent cost, 4 tasksWall sJudge mean C/S/T/H
Haiku 5.5 low4/4$0.0641994.25 / 4.75 / 4.25 / 3.75
Haiku 5.5 medium4/4$0.0652094.00 / 4.75 / 4.00 / 4.25
Haiku 4.5 low1/4$0.7064323.00 / 4.50 / 2.00 / 3.00
Sonnet 5.5 medium4/4$0.9552414.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 typeVerdictCondition
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 medium4/4 accepted; add the verify paragraph below; escalate to Sonnet on the first miss
Well-specified implementation, multi-fileDEFERstays Sonnet (default implementor); no multi-file or long-brief task in the set, and Anthropic points agentic coding at Sonnet/Opus
Multi-step reasoning, orchestrationDEFEROpus
Trust paths, hooks, judge, vault, PHINEVERinvariant, 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 low it otherwise stops early on long briefs (raising to medium about 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-default temperature/top_p/top_k on 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_BETAS set 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.

CellAcceptedAgent cost, 14 runsMean turnsMean wall sHook denialsJudge mean C/S/T/H (rep 1)
Haiku 5.514/14$0.1910.34174.71 / 4.57 / 4.14 / 4.57
Sonnet 5.514/14$2.987.24994.43 / 4.86 / 3.86 / 4.29
codex gpt-6.1-sol14/14not priceable (see below)11.8112104.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-sol slug, so its cost_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-green run tripped the per-task --max-usd cap on that mispriced figure (1 turn, 0/14) and was re-run; a codex --max-usd is 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-code and hook-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 typeVerdictEvidence
Automated review-fix and sibling-sweep shaped (cr-fix, class-sweep)ROUTE to Haiku 5.5 medium with a named check4/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.52/2 accepted, 0 hook denials, $0.009 per run
Codex as an implementorNot preferred on qualityno pass-rate gain over Haiku, 2.7x slower, lowest judge test quality; use it for usage budget diversification, not for fit
Sonnet 5.5Keeps open-ended and multi-file workfewest 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)ClassDefault effort conventionCost-optimal context windowQuotaCalibration status
glm — GLM lane (spawn-glm.ts)implsmall-context discipline — chunk big plans1Mglm (flat-rate overflow, not per-token)dormant (HIMMEL-1967)
claudex — claudex lane (claude-codex over CLIProxyAPI)implmodel gpt-6.1-sol; launcher default medium; a chief-dispatched worker runs xhigh through its brief’s effort key; never ultra/max900000 — 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 riskcodexlive — 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/variesnone (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 lanesnone (dashboard-omitted); opt-in CR_PROFILE=paidcalibrated — automated review escalation / second opinions only
copilot-cli — GitHub Copilot CLI (free tier)bulksmall tasks; model tier is mini-class unless the caller overrides the auto pinunverified/variesnone (dashboard-omitted); 2,000 completions/monot 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 dispatchimplinvoke.sh wall-clock timebox (default 1800s) + Nth-identical-deny abort900000 — 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 lastscalibrated — live-proven spawn path; see ox-alpha
codex-exec — codex CLI sandboximplwell-scoped chunks; job registry is per-workspace900000 — 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 lanescodexcalibrated
antigravity-cli — Antigravity CLI (Google AI Plus)bulksimple tasks; --output-format drift seen on Windows buildsunverified/variesnone (dashboard-omitted); free AI-Plus quotanot 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)bulkslow, small tasksunverified/variesnone (dashboard-omitted); free, local wall-clockcalibrated — zero-egress guarantee is structural; the only salus-eligible backend
openrouter-free — OpenRouter free modelsbulksimple tasks only; rate-limitedunverified/variesnone (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)bulksimple tasks; free-tier usage capsunverified/variesnone (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:

AxisResult
Small prompt (~1.0 KB) — 3 runs12.4 / 12.7 / 14.7 s — p50 12.7s, p95 14.7s
CR-sized prompt (~45 KB real diff) — 2 runs58.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 runs3/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 SOULcorrect — --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.overflow for this lane is already compact-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-continue lose 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 fs is not a hermes toolset — it exits 2 in ~1s with ignoring unknown --toolsets entries: fs. Use coding.
  • Recommended CRITIC_TIMEOUT_SECS for 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 own timeout_secs to 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.json today (the panel is codex only), 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 2 on 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 in pr-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-scores has nothing to report for hermes/ox-alpha — hermes-critic.sh is not invoked by any live /pr-check path today).
  • The profile is what routes. hermes-critic.sh does not pass -p; the no--p path lands on ox-alpha only because ~/.hermes/active_profile reads himmel_agent. The hermes root config still defaults to gpt-5.5/openai-codex, so if the operator switches the active profile, hermes-routed critics silently change model. Doctor advisory: himmel-doctor.sh check C21 WARNs when the active hermes profile’s config.yaml model.default no longer matches the profileDefaultModel this file’s lanes.json records for the hermes-oneshot/hermes-critics rows — 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() in scripts/telegram/run.ts:318 resolves the model to its lane row and run.ts:328-329 folds the row’s effort into CLAUDE_CODE_EFFORT_LEVEL for the spawned session’s env.
  • Chief-launched native worker (79b205218): scripts/handover/chief-kit/headed-arm-worker.sh:746-764 mirrors the same match rule (exact model id, or claude-<tier-id>- prefix) for LANE=native — an explicit ambient CLAUDE_CODE_EFFORT_LEVEL always wins (checked before the lookup, headed-arm-worker.sh:747-748); otherwise it resolves the row via jq against lanes.json and exports it. scripts/handover/chief-kit/ test-headed-arm-worker.sh carries 18 assertions on CLAUDE_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:

EffortUse for
lowthe first lever for a mechanical chunk — rename, fan-out edit, fixture/doc sync, a change whose acceptance check is a single command
mediumdefault — a well-specified single-worker implementation brief
higha multi-file or long-running brief where medium measurably under-delivers
xhighrare; 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:

  1. codex-exec (scripts/codex/dispatch-codex-exec.sh; Sol — the wrapper’s default model is the codex critic in scripts/cr/critics.json, currently gpt-6.1-sol (gpt-6-sol before it) — at --reasoning-effort medium, low for a mechanical chunk) — the default home of a well-specified implementation chunk, because it draws the codex usage budget rather than the Claude one.
  2. 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-write and refuses --add-dir / -C / --cd and every sandbox-widening flag (dispatch-codex-exec.sh invariant 4), and a linked worktree’s .git is 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.json wires block-terminal-write-fence.sh on Bash/PowerShell: git push, remote-URL rewrites, gh PR-mutations (create/merge/comment/…) and network CLIs (curl, wget, iwr/irm) are denied unless CODEX_EXTERNAL_WRITES_OK=1. Edit/Write are fenced by block-edit-on-main.sh, and lesson-enforcement paths by block-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 refuses main/master and 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 numeric autocompact ceiling.
  • 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 cohortAttributable merged workers (n)Existing profile observationBudget decision
worker-impl070,500 first-turn, #777 (73,000 control)keep 35,000
telegram032,541 first-turn, #680keep 45,000
lane-impl, lane-review, lane-content, bare (each)0nonekeep 45,000 each
chief-relay, console-judge (each)0nonekeep 35,000 each
user, design, console (each)0nonekeep 50,000 each
design-motion, design-3d, design-imagegen, design-a11y, design-diagram, design-slides, design-reference, design-trial (each)0nonekeep 50,000 each
operator0null sentinel, no budgetunchanged

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 argumentresulting model.iddisplay_namesuffix 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 5passed through (Sonnet 5 is natively 1M here)
sonnet[1m] (alias)claude-sonnet-5[1m]Sonnet 5passed through
claude-fable-5-1[1m]claude-fable-5-1Fable 5.1no — silently stripped
fable[1m] (alias)claude-fable-5-1Fable 5.1no — 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

launchcontext_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 2000001000000
every [1m] form above1000000

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.

lanedefault modeoverride
chief arm (*-chief.md)standardCHIEF_CONTEXT=1m
headed worker / worker armprofile default (design: 1m / 1000000; others: standard / 200000)WORKER_SESSION_CONTEXT=1m plus reasoned brief Context line; either alone does not enable 1m
subagent of eitherinherits the parentnone — 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) runs gpt-6.1-sol by default (a brief’s model key overrides it). The launcher’s own default effort is medium; a chief-dispatched worker runs at xhigh through its brief’s effort key, which exports WORKER_SESSION_EFFORT.
  • A one-off dispatch (dispatch-lane.sh --lane claudex) is accepted once the flow-runs ledger holds 5 consecutive verified passes on claudex/ 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_EFFORTWhen
medium (default, unset)routine single-worker implementation briefs — the common case
highmulti-file or long-running briefs — raise before reaching for a native tier
xhighgenuinely 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-exec as 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 via SendMessage.
  • 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 pathProfile
chief.sh arm / headed-arm.sh --role chiefconsole (handover, himmel-ops, qmd, lean-skills; no pr-review-toolkit)
arm-resume.sh, schedule-resume.shfrom the resumed doc via scripts/lanes/role-profile.sh; --profile <name> overrides (arm-resume)
hop.shHOP_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.shbare (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 / profileDelegatesVerdict
chiefjudges, haiku publisher, sonnet VM runnerright shape; judge tier and publisher failures unmeasured (HIMMEL-7330, HIMMEL-6825)
worker-implrarely; implements inlineright; no live cost budget (HIMMEL-7331)
consult / judge sessionnot measured (a consult has Edit/Write denied)no evidence either way
bare, telegramnot measuredno 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:

TaskShape
shell-red-greena new shell script plus its RED/GREEN test; acceptance also runs the agent’s test against a stub, which it must catch
doc-plus-codea new flag on a fixture script plus the matching README change
hook-refusala PreToolUse hook that refuses curl-into-shell and fails closed on bad input
finding-verifytwo 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:

Terminal window
bash scripts/eval/lane-quality/run.sh run --lane native --model claude-haiku-5-5
bash 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.