Running a chief
A chief is how a single operator keeps several tickets moving at once across
sessions that each forget everything at the end. What a chief, worker, judge,
relay, dependency chain, parallel wave, arming and side session are is defined once, in
../glossary.md; read that first if any of those words is
new. This page is how to run one.
/chief new starts one. /chief next hands it over to its successor.
For the sources, authors and licences behind the team’s tools, see
Credits.
Who does what
- You (the operator) talk to the chief. It dispatches workers, one ticket per worker, and acts on their questions and on judge verdicts; it merges only on a GO file (merge approval) that it writes.
- Judges and relays serve the chief; neither holds GO or the nonce mint; only the chief does (the authority table is in the glossary).
- Side session. If you work in a side session of your own, hand the chief a ticket and a brief; never pass it a ruling by way of a relay. A ruling reaches the chief from you directly, or not at all.
Chief vs. /afk-shift
They look adjacent and are not interchangeable.
/afk-shift | chief | |
|---|---|---|
| Shape | one session fans out N subagents inside itself | one session dispatches N separate sessions |
| Lifetime | one run, then done | a succession: each chief arms its successor |
| State | in-session | a handover document + a queue lock, survives the session |
| Children | subagents, no window of their own | headed sessions with their own worktrees and PRs |
| Rulings | none; the plan is fixed at fanout | RETASK channel, mid-flight, authenticated |
| Handover | none | /chief next writes the successor + a HANDOFF |
Reach for /afk-shift when you have a fixed list of well-scoped tickets
and want them done unattended. Reach for a chief when the work will need
decisions you cannot pre-write, or will outlive one context window.
Starting one
/chief new # writes the doc, takes the lock, prints the launch line/chief new --name night # a differently-named succession in the same bucket/chief new --arm # also arms it headed, on a signal file + deadline/chief new --dry-run # print what it would write, touch nothingnew resolves your handover root through the shared resolver (HANDOVER_DIR,
else <repo>/handovers) and your bucket the same way the handover skill does,
then writes <root>/<user>/<bucket>/<PREFIX>-nextleg-<date>A-chief.md from
chief-template.md. A second new on the same day
writes …B-chief.md; it never overwrites.
Keep the -chief suffix in a scoped name: --name roadmap-chief, not
--name roadmap. The suffix is load-bearing. chief-compact-reinject.sh
re-injects the live state after a compaction only for a session whose
launch-time -n name ends in -chief (or the older -console), and /rename
does not change that name. A --name roadmap-chief is mapped to
roadmap-chief, and next on a …-console.md predecessor arms a …-chief.md
successor; every guard and reader still treats a live -chief session as a
chief. next derives the successor’s name from its --doc, so a doc without
the suffix hands the mistake down the chain. To fix a mis-named chief, copy
its doc to a …-chief.md path, take the lock on the new path, release the
old one, and leave a pointer stub behind.
It then acquires the queue lock on that document and prints the
release-token: line. That token belongs to the chief you are about to
launch. Record it in the chief’s first Results bullet, because releasing
the lock at wrap requires it. The chief adopts this lock; it does not
acquire a second one. (new takes the lock and then exits, so a chief that
tried to acquire again would be refused by its own startup lock.)
Paste the printed launch: line verbatim, including its env -u … prefix.
That prefix is load-bearing, not decoration: you are almost always pasting into
a terminal that was itself spawned from a claude session, and it clears the
CLAUDE_CODE_CHILD_SESSION / CLAUDE_PID / CLAUDE_CODE_SESSION_ID variables
that shell inherited. A chief that keeps them writes no transcript and gets
UNKNOWN from scripts/context-fill.sh --percent, which is the signal its
own handover contract runs on. Trimming the line to just
claude … silently recreates that failure.
Finally it prints the launch line. Run it in a terminal of its own:
env -u CLAUDE_CODE_CHILD_SESSION -u CLAUDE_PID -u CLAUDE_CODE_SESSION_ID \ CLAUDE_CODE_FORCE_SESSION_PERSISTENCE=1 \ claude --model '<model>' --autocompact auto -n <session-name> "load <doc> and continue"[1m] is appended to the model only for the 4.6 family (claude-opus-4-6,
claude-sonnet-4-6); Opus 4.7+, Sonnet 5+, Haiku 5.5 and Fable run 1M natively
and non-Anthropic ids (gpt-*, glm-*, anything with a /) never take it
(HIMMEL-5501). --autocompact and the Opus 5.5 id need Claude Code 2.1.280 or
newer: an armed launch on an older CLI refuses and names claude update
(/himmel-update brings it current).
Chiefs default to 1m context with --autocompact auto
(a chief exemption). Both chief new and next
accept --context 1m|standard, which overrides the environment and default.
Use --context standard or CHIEF_CONTEXT=standard in the launching shell
to opt down to --autocompact 200000. The printed launch line and armed
launch use the same resolved choice. Fable-family models retain their existing
handling: the CLI strips [1m], so the suffix is not applied; autocompact still
is. Workers and non-chief arms keep their standard default.
A chief wants a real TTY. A session launched without one exits at the first idle cross-session message.
ACTION ZERO
The template’s first section. The chief runs it before anything else and
writes the result as its first Results bullet: who is alive (ListAgents),
what locks are held at the handover root (not just its own bucket; a
bucket sweep reports a false “no held locks”), the primary’s head and remote,
usage-preflight.sh, the live worker processes, host load, and the kit path. Then
it records the lock token new printed (confirming with queue-lock.sh status that the lock is held, and acquiring one only if it reports free)
and tells its predecessor it is live.
It also opens a Telegram inbox: it creates
<bridge root>/consoles/<session>.md and watches it with its one event waiter,
chief-kit/chief-wait.sh, run with Bash run_in_background (no Monitor re-arm loop, so an idle chief takes no turns; a persisted read
cursor means a line appended while no waiter ran is delivered on the next
start; at-least-once, so a waiter killed mid-emit can repeat one line), so
the operator’s /chief <session> <text> (or the older alias /console) from
Telegram reaches the running chief with the operator’s authority (rulings,
halts, new work) but never a permission or settings change and never a
skipped GO verification. The chief replies with
bun scripts/telegram/chief-route.ts reply <chat_id> <text>. Operator-side
usage: ../telegram-bridge.md.
The same waiter pages the operator when a worker’s classifier
denials reach SHIP-STEP or PAUSE-RISK: one Telegram message with the worker
label, count and class, and nothing else. A paged worker is then refused on
re-dispatch (headed-arm-worker.sh exits 14) until someone has looked and run
bash scripts/handover/chief-kit/ack-denial.sh <worker-label-or-doc>;
--ignore-denials overrides. Detail:
../internals/enforcement.md.
Dispatching workers
Pre-dispatch trust-path check: trace the planned scope and
list the trust paths it will plausibly need (shared libraries, hooks, launchers,
settings, or guardrail inventories). Put the exact paths, permitted operations,
and exclusions in the initial brief, with the operator’s up-front scope grant.
If that grant is missing, narrow the task or obtain it before dispatch; a
chief cannot manufacture operator permission. The brief grants task scope,
not a bypass of the worker’s tool-permission envelope. In particular, an auto-mode
worker’s mid-flight inbox EXPANSION may be refused by the classifier and needs
operator confirmation in-window, even with a valid RETASK token. Do not use
repeated inbox revisions as a workaround; see
../internals/retask-channel.md.
When drafting the brief’s Ship: item, spell the attestation trailers’
grammar rather than paraphrasing it: the token is the FIRST word after the
colon: Platforms tested: <os>, Security reviewed: manual — <what you checked> (or claude-code-security-review / pr-review-toolkit / ad-hoc
in place of manual). A paraphrase the worker copies faithfully is how a
non-conforming trailer reaches the pre-push gate.
The chief writes a brief from
worker-brief-template.md and launches it headed:
setsid nohup bash scripts/handover/chief-kit/headed-arm-worker.sh --profile worker-impl \ <session-name> <brief> <signal-file> <deadline-epoch> <log> <model> \ >/dev/null 2>&1 &Two rules govern the fan-out. Collision-check first: list the files each
queued worker will touch and confirm they are disjoint; single-writer means
many readers but exactly one writer per artifact. And every dispatch names an
explicit model: an unnamed one draws on the scarcer parent quota. Tier and
effort guidance, including the chief’s own wake-up budget, is in
../internals/lane-calibration.md.
Three pre-launch checks that each cost a chief a dead worker when skipped:
- An old backlog bug may already be fixed. Before dispatching a worker on a bug filed weeks ago, check that it still reproduces on current main: read the code the ticket names and look for a regression test that covers it. Many have been fixed in passing. Batch the check in one read-only agent pass (still open, fixed, or partial, each with file:line evidence), and verify any “fixed” claim yourself before you close the ticket.
- The Tier line needs a literal em-dash. An Opus or Fable worker launches only
if its brief carries
> **Tier:** opus — <category>: <reason>. With an ASCII hyphen in place of the em-dash,headed-arm-worker.shrefuses: no launch log, nothing inpgrep.--dry-runan Opus or Fable launch first and look forrefusingin its output. - Design-profile workers need a one-time interactive OAuth login. The
designprofile enablesbuilder-visual@himmel, whose.mcp.jsonregisters the hostedagent-native-dispatchMCP server (https://dispatch.agent-native.com/mcp, OAuth). Until the operator has logged in once (/mcp, pickagent-native-dispatch), the server needs auth and a worker cannot answer the prompt, which looks like a hung worker. Tool arguments go to Builder.io servers: never use it on salus/PHI work.block-agent-native-egress.shrefuses the call from a salus session or when the payload names salus.context7needs no login. Do not arm a design-profile worker unattended (--arm, at-jobs, overnight) until a fresh session has been checked to keep the token.
Pass --profile worker-impl on a native-lane worker: it narrows
the worker’s plugin set, appends the standing rules from
worker-preface.md to its system prompt (which is why the v3
brief template no longer repeats them) and exports HIMMEL_LEAN_WORKER_SESSION=1 to
quiet the warn-only SessionStart hooks. It also now passes
--mcp-config/--strict-mcp-config, so a worker only ever sees the qmd MCP
server, not the USER-level MCP roster (a chief gets the same treatment:
qmd and himmel-bus only). A worker dispatched WITHOUT the flag is
unchanged in every respect, including its argv, so the worker system prompt rules must then be
pasted into the brief. Detail:
../internals/lane-calibration.md.
Repo map (off by default). Set WORKER_SESSION_REPO_MAP=1 in the launching shell and
a profiled worker’s system prompt starts with a repo map: the files of
the worker’s repo ranked by PageRank over its primary checkout’s
graphify-out/graph.json, each with its most-referenced symbols, capped at
WORKER_SESSION_REPO_MAP_TOKENS (default 2000) tokens. It is built by
scripts/graphify/repo-map.py; with no graph, or a generator still running after
WORKER_SESSION_REPO_MAP_TIMEOUT (default 30) seconds, or on a host with no GNU timeout to bound
it, the worker launches without it and the launch log says so. It stays off because the A/B on the K7 questions
(HIMMEL-6142) showed no gain: 15/15 answers found with and without it, at the
same token cost.
headed-arm-worker.sh exports HIMMEL_CONSOLE_LEG=1, which
block-worker-askuserquestion.sh uses to structurally deny
AskUserQuestion on the worker, rather than relying on the brief’s prose NEVER.
headed-arm-worker.sh also exports HIMMEL_CONSOLE_NAME into the
worker, resolved from the first of: a --chief <name> flag, this launching
shell’s own HIMMEL_CONSOLE_NAME, or the chief’s own session name if it can
be read reliably. It is never guessed from the process tree, and exported not at
all if no source yields a name. A running chief usually needs no flag: its
own session name resolves automatically. This is what lets a worker’s
merge-block alert (scripts/lib/merge-block-alert.sh) route to
the chief’s own inbox instead of DMing the operator.
Idle capacity is the chief’s duty. tick.sh appends fleet=<live>/<cap>
(usage-preflight’s own census: native + claudex + reserved, HIMMEL_FLEET_CAP)
and capacity=. On capacity=UNDERFILLED:<slack> (live below cap and no worker
launched for TICK_UNDERFILL_MIN minutes, default 10), pull dispatchable work
from the Jira backlog, not only the held queue, and launch up to <slack> workers.
chief-wait.sh wakes on it once per streak (WAKE underfilled) and prints
chief-kit/next-dispatchable.sh --priority-pool-only’s ranked, collision-checked
list (free slots fill from the priority pool; it ends with # pool=<rows>/<slack>, ,LOW
when short; POOL-EMPTY lists the tier-4 NEXT-TIER rows and means unblock
collisions, draft specs for the next priority items, else idle);
chief-kit/gen-briefs.py writes the briefs and launchers (nothing launches
itself). A bare --bucket himmel resolves to <handover-root>/<user>/himmel;
--no-worktree refuses a worker whose worktree does not exist and prints the
worktree.sh new line to run.
Before dispatching a chain of PRs, python3 scripts/handover/chief-kit/chain-graph.py --prs <n>,<n>,...
prints the pairwise hard and soft edges between them (read-only; --json for scripts, --head PR=SHA
to pin a PR to an older head).
capacity=unknown (fleet=?) means the census could not be read, not that
capacity is fine.
A red orphan is owed a worker. An unowned-prs= entry ending :dispatch-owed
(#<pr>:<age>m:dispatch-owed) is an open, non-draft PR with no live worker and at least one
failed check. Launch a resume worker for it on the next WAKE; triaging the red is that
resumed worker’s job. A pending or green orphan keeps its plain #<pr>:<age>m entry.
Reading worker locks and the tick. tick.sh --workers takes absolute
handover-doc paths on ONE legs: line; a relative path drops the bucket
prefix out of the lock key and the worker reads free while it is live. The
queue-lock owner pid is the launcher wrapper, so every owner reads dead
while its worker is alive, and the release token a worker pasted into its LIVE
bullet can be wrong; recover the real one from
${XDG_RUNTIME_DIR}/himmel-queue-lock/ before calling a lock stale.
IDLE-HELD? is heartbeat age, not death: a worker inside a long foreground suite
makes no tool calls. Verify with pgrep against the worker’s session name;
never force-release on the flag alone.
NOLIVE and FORKED. A worker that never took its lock or wrote
a marker bullet reads FREE forever, so the waiter’s key never moved and the
chief was never woken. workers= now shows <N>:NOLIVE once a worker with no held
lock and no marker bullet at all is older than TICK_NOLIVE_MIN minutes
(default 10), counted from the fleet manifest’s added time, else the launch
log’s armed: line, else the doc’s mtime; the change from FREE wakes the
waiter. <N>:FORKED means the worker’s session transcript (found only by its
custom-title, never guessed) ends in a continued-in record: the session was
forked or backgrounded into another one, usually without its worker system prompt. A
WRAPPED, READY, LIVE, BLOCKED or held-lock worker is never either. Act on
both the same way: ListAgents, then SendMessage the worker its lock command
(queue-lock.sh acquire <its doc>), your chief name and its RETASK
token; if it is gone, relaunch it.
tick.sh --workers-from <manifest> reads the same absolute docs
from a fleet manifest that chief-kit/fleet-manifest.sh add|remove keeps,
re-read every sample, so a dispatch or wrap needs no waiter re-start.
Pass --fleet <manifest> before the positional arguments to
chief-kit/headed-arm-worker.sh and chief-kit/close-wrapped-worker.sh to keep
that manifest updated automatically: launch adds its doc only after a successful
handoff; close removes its doc only on a successful close (including benign
prune skips). Failed/refused launches and closes leave the manifest unchanged.
Without the flag, both verbs keep their existing behavior. Dry-run launches
never update the manifest. An update failure returns nonzero and reports that
the launch/close already happened; it does not undo it. Nonces remain chief-only
inputs to live-state.sh --nonce, never sourced from the manifest.
A worker’s MERGED or WRAPPED message is one call, not a turn sequence:
chief-kit/worker-event.sh --doc <chief doc> --fleet <manifest> <message-file>
routes it to chief-kit/wrap-merged.sh and prints HANDLED or ESCALATE <reason>; a judge verdict is one chief-kit/verdict-go.sh call. Chiefs that
ran the hand sequences spent most of their input tokens on them
(HIMMEL-5592 inventory: scripts/eval/chief-load-inventory.py).
Lane canary: scripts/lanes/lane-canary.sh [--lanes a,b] [--timeout <sec>] [--out <report.json>] [--dry-run] launches one no-op worker per
lane through the real launcher and reports PASS, FAIL or SKIP with the time to
the first Results bullet. Run it after a station update or a lane change; the
unit suites check the generated command, this checks that a worker on the lane
actually starts, registers, receives its brief and writes a bullet. Lanes run
one at a time, and a lane whose backend is not configured is SKIP. The report
lands at <handover root>/.canary/latest.json, the file the board reads. A lane
whose launcher refuses on its usage (exit 11, for example OpenRouter credit under
OPENROUTER_MIN_CREDIT_USD) reports FAIL, not SKIP: read the detail column
before treating it as a lane defect.
Worker tabs (off by default): launch the chief with HIMMEL_WORKER_SESSION_TABS=1
in its environment, for example HIMMEL_WORKER_SESSION_TABS=1 claude, and every worker
session it dispatches opens as a tab in one Konsole window per chief instead
of a separate window each. The switch passes to successor chiefs through the
environment. Unset, every launch is unchanged.
- The first worker opens the window.
headed-arm.shrecords that window’s pid in its lock directory astabs-<chief name>.window. Each later worker opens as a new tab in it over Konsole’s D-Bus (qdbus6/Windows/1 newSession), titled with the worker’s session name. - The tab’s process is the worker itself, so closing a worker
(
close-wrapped-worker.shstops its claude process) closes only its tab. A crashed worker leaves only its own tab dead. The window and the other tabs stay up either way. - A worker falls back to a window of its own, never lost, when the recorded
window is gone (that new window becomes the chief’s window), when D-Bus
fails (a
WARN: tab launchline in its launch log), on macOS, for chief arms, and whenHIMMEL_CONSOLE_NAMEis unset. - The window keeps the first worker’s
HIMMEL-tab title, so the KWin rule that places newHIMMEL-windows still matches it. - Limits: two workers dispatched at the same instant before any window exists can open two windows. The tick’s dead-window sweep closes a window only when nothing runs in it, so a dead tab beside live ones stays until you close it.
Claudex workers: the inbox is the only channel
A --lane claudex worker (headed-arm-worker.sh) runs under
~/.claude-codex, which keeps it out of the session registry ListAgents
reads on both ends, so SendMessage reaches it in neither direction, even
though its socket is alive. The only channel either way is the file inbox
(inbox-send.sh): chief → worker is inbox-send.sh <worker-session> --file <path>, delivered as PostToolUse/SessionStart additionalContext on
the worker’s next tool call; worker → chief is the same script addressed to the
chief’s own session name. AskUserQuestion reaches nobody on a claudex worker
while the operator is away; every claudex brief must say so and give the worker
the chief’s exact session name.
Inbox delivery is tool-call-gated. Before ending a turn at BLOCKED,
READY, PR-READY, or a blocking question/PROBE ask, the worker arms ONE bounded
Monitor on bash scripts/handover/chief-kit/inbox-follow.sh --wake <handover-root>/inbox/<session>.md, with timeout_ms: 1800000. Resolve the
root through handover_root() and use only the worker’s own exact session name.
The supported API has no persistent flag. The worker re-arms on expiry for at
most eight windows/four hours per unresolved hold; unrelated wakes do not
reset the wall-clock cap. Full lifecycle, failure recovery and wrap rules:
worker-preface-claudex.md.
The fixed wake envelope is data only: the worker makes a benign tool call to
receive the authoritative inbox hook delivery before acting. The follower’s
.md.cursor never steals the hook’s .cursor/<session> delivery cursor;
restart drains lines appended during a re-arm gap without normal replay.
Suppression can lose a wake, so expiry must trigger hook delivery too. The
short claudex idle-wake probe (n=1) is not a four-hour soak or a
cross-lane persistence claim. No registry or hook authority changes here.
Rulings
A worker that hits something it cannot decide reports to the chief, not to the operator. The chief verifies the finding itself, then answers.
Re-tasking a running worker goes through the RETASK channel: each dispatch
carries a fresh nonce, and a genuine revision is a direct message quoting it.
Expanding or redirecting a worker needs the token; narrowing it or halting it does
not. That asymmetry is deliberate, so a halt can never be argued away. Full
threat model and the verbatim block:
../internals/retask-channel.md.
A ruling that widens what a worker may do is refused by the worker’s own auto-mode classifier, for example an extra push, or SKIP verdicts in place of a suite that a guard blocked. The refusal is correct: a chief ruling is not user consent. Narrow the ask instead (run each suite on its own, with no runner flags), or do the step from the chief, for example push the worker’s verified head yourself and then tell it to continue.
Address inbox-send.sh by the worker’s real session name, the -n it was
launched with (see its launch log). That name may lack the doc’s date suffix.
A ruling sent to the wrong name is never delivered and raises no error, so run
inbox-send.sh --pending after sending.
A PR parked at the round-3 review cap gets one more review round from the chief,
never from the worker. Run
scripts/handover/chief-kit/grant-round.sh <pr> <new-head> <qid>. It grants
only on a signed judge record for that exact head, a PR that has reached the
cap, a head that is still the PR’s live head, and a qid no earlier grant
consumed; one record buys one round. A worker or a relay is refused (exit 3). A
judge’s NO-GO on a head that follows the reviewed one by a test-only or
comment-only commit is written with
write-verdict.sh ... --bind-reviewed <reviewed-sha>: the verdict
keeps naming the judged head and carries a signed reviewed-head: line, and any
other delta is refused with nothing written.
Merges
READY <pr> <head> GREEN → the chief verifies independently (all check-runs
green at that exact head, zero unresolved review threads, attestation trailers
in the first commit) → GO → the worker merges and reports MERGED #<n> → <sha>.
The Jira close is the worker’s call from the brief, not a default:
merge-on-green.sh transitions the ticket only on --jira-transition,
and the brief’s Ship contract carries completes-ticket: yes|no:
yes → the worker passes the flag, no (the ticket spans further
PRs) → it omits it. After MERGED, re-read the ticket: the flag closes only the
first [KEY] of the PR title, so a multi-key PR needs its other ticket checked
by hand.
scripts/handover/chief-kit/ready-check.sh <pr> <full-40-hex-head-sha>
mechanizes that independent verification: it re-runs checks
1-6 (head match + clean merge state, statusCheckRollup all green, zero
unresolved review threads, an automated review ledger row for that head, attestation
trailers in the first commit, a ticket ID on every commit subject) and
prints READY-CHECK PASS|FAIL. It is read-only (no ledger rows, GO files,
or PR comments) and it does not read the three-dot diff; that judgement
call stays the chief’s own, which the script says on its last line. On a
PR that is already MERGED, GitHub reports mergeStateStatus: UNKNOWN
permanently, so check 1 always fails there; that is expected, not a bug, and
the script’s domain is a PR that has not yet merged.
scripts/handover/chief-kit/ready-go.sh [--to <worker> --token <nonce>] [--ledger <chief doc>] <pr> <sha>
is the whole mechanical READY turn in one call: the ready-check.sh output, the coverage line, the trust-path result, the ticket state and the exact GO line to send. It is read-only (no GO file, no message): go.sh and the SendMessage stay yours.
The automated review ledger is keyed by head sha, so every push needs its own review
round, even a one-line fix after a judge’s NO-GO. A ruling of “no new
/pr-check panel” leaves check 4 with no row to find, and the marker-clear
script refuses when no critic answered at that head. Word the ruling as “one
/pr-check round at the new head; only Critical or Important findings block”.
A PR on HIMMEL-2973/2976/2928/2974/2975 is READY only if its body cites
HIMMEL-2977 "GATE <previous lever> PASS <date>" (for 2973:
P0 EXIT <date>; this line carries no separate status word, and its mere
presence, verbatim, is the pass signal for 2973). Open HIMMEL-2977’s comments
and find that citation’s line verbatim: a missing citation or a line not
found is not READY; for the general GATE ... <status> <date> shape (every
ticket except 2973), a found line whose status is not PASS is also not READY.
The chief pulls the primary and the worker wraps (closing its ticket only if the
brief’s completes-ticket: yes says the PR completes it).
The chief sends the GO file (merge approval) by first running bash scripts/handover/chief-kit/go.sh <pr> <full head sha>. The file IS the GO; the SendMessage is the
notification: a worker launched by headed-arm-worker.sh carries
HIMMEL_CONSOLE_LEG=1, and merge-on-green.sh refuses it (exit 17) without a
GO for the exact head it certifies, so a push after GO needs a fresh one.
go.sh runs ready-check.sh itself and writes nothing unless
it passes (exit 4). A --trust-reviewed <qid> GO also needs the
judge’s **GO** for head `<sha>`. verdict under verdicts/<qid>/ (exit 5).
An opt-in auto-merge stops at “awaiting approval” wherever branch protection requires
a review the automation identity cannot give. On a single-maintainer repo the
only path through is the admin bypass, which the merge script refuses by
design, so the chief hands the merge to the operator and sends MERGED
back when it lands.
Fork PRs (himmel adapters)
A PR on a fork we own merges only when the repo is listed with merge_allowed
true in scripts/guardrails/himmel-adapters.json (read from the default branch,
so a worktree edit cannot widen it) and the PR targets that entry’s
himmel_branch. Run the judge, then, from the primary checkout:
bash <anchor>/scripts/handover/chief-kit/go.sh --repo <owner/repo> --trust-reviewed <judge-qid> <pr> <head>(exit 6 not allowlisted or the harness repo itself, 7 PR not OPEN, CLEAN and on the adapter branch at that head or a review thread still open, 5 no signed judge GO).bash <anchor>/scripts/handover/merge-fork-pr.sh <owner/repo> <pr> <head>(chief only; a baregh pr merge --repo <fork>stays refused).
Both scripts require GitHub’s mergeStateStatus CLEAN (every check at the head
passed, no conflict, up to date) and zero unresolved review threads, so a PR
with pending or red checks gets no GO and no merge. A push after the GO needs a
fresh GO.
Handing over
Chiefs hand over at 45 % context fill, or 90 k input tokens in one turn.
/chief next --arm --doc <this chief's own doc> --bucket <its bucket> --prefix <its prefix> --model <its model>Name your own document, bucket, prefix and model explicitly. Without
--doc, next hands over from the highest-lettered doc it can find, which is the
wrong one as soon as another new has run or a successor already exists;
without --bucket and --prefix it re-resolves those from the environment
and writes the successor into the wrong bucket, or under the wrong key;
without --model it re-resolves the model from CHIEF_MODEL or the
built-in default instead of continuing on the model this chief itself runs
on. The rendered chief doc carries the exact command with all four already
filled in.
next writes the successor stub (letter bumped, pointed at this chief and
its HANDOFF) and the predecessor’s -HANDOFF.md skeleton, and arms the
successor on a signal file. The letter rolls past Z in bijective base-26 (Z →
AA → AB → … → ZZ, then refuses) instead of ever refusing at Z; pass --date <YYYY-MM-DD> to pre-mint tomorrow’s first chief (A) immediately, near
midnight, instead of waiting for the date to roll over. Fill in the HANDOFF
(head, what is in flight worker by worker with nonces and lock tokens, rulings made,
the held queue in launch order, what wrapped), then touch the signal path
that next --arm printed and hand your live workers over by name.
Re-brief your live workers before you release. Each
worker’s brief names you as its chief, and a successor is a different session
on a different socket: the successor names itself to you during its ACTION ZERO,
you relay each worker to it from your own session, never by hand: next leaves a
chief with a bus identity a WAKE relay-on-live (or run chief-kit/relay-batch.sh <this doc> --successor <it>). That run sends each native worker one himmel-bus relay record naming
the successor (the bus proves you sent it, so the record carries no token), and each claudex
worker its inbox relay plus the bus record its bus adopt needs. A native record that
fails, or a chief with no bus identity, prints a SENDMESSAGE line quoting the worker’s
token for a worker in Live state: send it from your own session. A failed claudex
notice record prints only BUS-RELAY-FAIL: that worker already has its inbox relay. A
worker in the fleet manifest but missing from Live state prints NOT-IN-LIVE-STATE when
its record fails, when the chief has no bus identity, when it is a claudex worker, or
when its session does not resolve: relay it by hand if it is live. SKIPPED, UNRESOLVED and
UNRESOLVED-LANE lines sent nothing: each needs a relay by hand. So does a FAILED-INBOX line (its claudex inbox send
failed). BUS-RELAY <label> ok means the bus stored the record, not that the
worker saw it: delivery needs the worker’s HIMMEL_BUS_NAME hooks, so the
successor’s WAKE succession partial is the backstop for one that never quotes back. The worker
session’s quote-back is the SUCCESSION accepted Results bullet in its own doc (the successor’s
waiter tallies it); it sends a SendMessage only on a timeout or a decline. That run is the
relay: never re-run it (a retry resends the claudex inbox relays). Unless you pass
--fresh <label>=<token>, it never carries a fresh token, and a relay with none is
complete (the worker keeps its own): native and claudex workers are not rotated on a
bus succession, and WAKE relay-on-live passes no --fresh. A successor that issued fresh
tokens has the outgoing chief run relay-batch.sh <this doc> --successor <it> with one
--fresh <label>=<token> per worker instead: the SENDMESSAGE line and the claudex inbox
relay quote the held token and the fresh one, and a native worker owed a fresh token gets
that SENDMESSAGE rather than a bus record (a bus record is token-free). A claudex worker’s
bus record is sent right after its inbox relay. FRESH-UNUSED <label> means no relayed
worker took that token: hand it over. It prints RELAYED <n> of <m> last, and n
counts its SENDMESSAGE lines: send those relays first, then that line to the successor,
naming every worker still unrelayed (n alone does not say which). A batch that fails or
times out (WAKE relay-on-live ... relay-failed rc=<n>) prints its partial output and no
RELAYED line. On relay-failed, relay by hand every worker with no BUS-RELAY <label> ok or SENT-INBOX line,
then send RELAYED <n> of <m> yourself, n the delivered lines.
The quote-backs are the successor’s to tally; you do not wait for them.
A successor that arrives after you have left can prove itself only with the
two-token chain form; a worker that cannot verify that is stranded (it keeps
working and can still be merged, but cannot be re-scoped). The successor’s
tick reads nonces=UNCONFIRMED:<worker> until each worker’s own SUCCESSION accepted bullet names the successor (RELAYED:<worker>, a relay that kept the
worker’s token, is valid) or the worker has rotated.
Release your lock only after the successor reports LIVE. It sends that
once your RELAYED line arrives (naming the workers you could not relay, and
why: ACTION ZERO step 9), and it is the only evidence the arm actually fired
and the successor completed ACTION ZERO; releasing on the touch alone leaves
an unattended fleet if the launch failed. Relay each worker it names as not relayed by
hand before you wrap. A relay delivered is enough: a worker
drains it at its next tool round whether you are live or gone (S1), so you wrap
without waiting for the quote-backs; workers that did not quote back are named
on the successor’s informational WAKE succession partial.
Wrap only with no subagent still running. An in-process
subagent, a judge subagent above all, dies with its parent session and its verdict
with it. Wrap with chief.sh wrap <your doc> <release-token> '<line>': it
writes WRAPPED and releases the lock, and refuses (exit 4, nothing written)
while chief-kit/live-subagents.sh finds a running child of this session or
cannot tell. The successor’s close-wrapped-worker.sh --chief holds the same
line from the outside: exit 7 means the predecessor still has a live subagent;
retry once it finishes. Every judge subagent persists its verdict with
write-verdict.sh under verdicts/<qid>/, trust path or not, and the HANDOFF
lists the open judge subagents (next fills in the children still running), so a
successor reads a verdict instead of re-running the judge.
How a live subagent is detected from outside its session: Claude Code writes
<projects>/<proj>/<session>/subagents/agent-<id>.meta.json when it launches a
child, and appends a queue-operation enqueue of the child’s
<task-notification> (carrying <task-id><id></task-id>) to the parent
transcript the moment the child stops, whether or not the parent ever reads it.
A child with a registry entry and no later completion record is running. A
foreground child completes with its tool_result instead. The process tree
cannot see a subagent (it is not a process), and the tasks/<id>.output links
outlive the child.
The HANDOFF is the successor’s only required read. Written properly, the succession does not need the predecessor’s transcript at all.
Switching the Claude account
An account switch is a machine-level login: one active Claude account per machine, so it changes the subscription usage budget every worker and chief draws on, not any routing. Do it at a boundary, in this order.
1. Before the switch, bring the workers to a boundary. Let each worker reach a
milestone (READY, FINDING, RESOLVED) or WRAPPED; do not switch mid-turn on
a deep-context worker. The cost is not yet measured: the prompt-cache audit
(../internals/prompt-cache.md, row 11) marks
“an account switch rewrites the full prefix of running sessions” UNVERIFIED.
Its live run L5 was a weekly usage budget reset on the same account, not a switch: 2
sessions kept their prefix, 0 invalidated, 0 compacted (a chief reading about
318k tokens from cache stayed warm). So a real switch is still untested. If it
does invalidate, expect each live session to re-pay its whole prefix, which is
why a worker parked at a boundary is cheaper than one switched mid-turn. Record what
you see (re-run invalidation --event <switch ISO> from that doc’s “Re-run”).
2. Decide every armed resume. List the jobs, find the HIMMEL-Resume-* ones
(at -c <job-id> shows the marker), and keep or cancel each: one armed for the
old account’s reset fires against the wrong subscription usage budget:
atqatrm <job-id>auto-arm-on-cap.sh re-arms a resume on the next tool call once any window is at
or above AUTO_ARM_THRESHOLD (default 90 %) and none exists, so an atrm made
early can be undone before you finish. Cancel last: after the workers are
parked and as the final action of the session, or from a plain terminal.
3. After the switch, refresh the usage cache. The cache is stamped with the
account that wrote it; usage-preflight.sh reports BANK-UNKNOWN while the
stamp does not match the logged-in account. Force
one re-derive, then confirm the new numbers:
USAGE_FORCE_REFRESH=1 bash scripts/statusline/usage-cache-producer.sh <<< '{}'bash scripts/lib/usage-preflight.shPass: usage-preflight prints the new account’s five_hour / seven_day and
PROCEED (or SKIPPED-BANK if that account is genuinely high). BANK-UNKNOWN
means the cache is still the old account’s; refresh again, or check the login.
4. Confirm the weekly budget override voided itself. An override is bound to the
account that set it, so it is no override under the new login; only the operator can
set one (usage-lift.sh set, old name bank-lift.sh). Verify:
bash scripts/lib/usage-lift.sh showExpect usage-lift: INVALID: account after an account switch. Other invalid
reasons are expired, window, trust, parse (or missing when unset).
The doctor reports lift set but INVALID: <reason>. A stale file is harmless
and can be removed with bash scripts/lib/usage-lift.sh clear.
A one-window weekly budget override skips only the weekly ceiling; five_hour still binds.
Whole-second reset values tolerate up to one second of fractional API jitter,
not a different weekly window. Only the operator can enable the standing
“spend to 100 %” policy:
bash scripts/lib/usage-lift.sh set --standingIt records standing:true in the same operator-only, account-bound override file
(no new state path or hook bypass). The policy applies to every fresh window
for that account and defaults CADENCE_USAGE_MAX_PCT to 100 for launch checks.
An explicit ceiling still controls five_hour; either usage budget at 100 refuses,
so extra usage is never spent. Plain set replaces it with a one-window weekly budget override;
clear disables it. Agents must never run either form of set.
The Done-when for this change needs the operator to walk this once at a real switch and record the result (and the cache-probe verdict from step 1) in the PR.
Controlling the VM CI runner
Two VirtualBox VMs (himmel-ci-1, himmel-ci-2, cloned from the
ci-runner-v3 snapshot, 4096 MB and 4 CPU each) can run the 8 ubuntu
shell-unit shards. The chief drives them with one script, each form a bare
relative literal pre-approved in .claude/settings.json:
bash scripts/handover/chief-kit/vm-runner.sh on # both VMsbash scripts/handover/chief-kit/vm-runner.sh on --vms 1 # or --vms 2bash scripts/handover/chief-kit/vm-runner.sh off # kill switchbash scripts/handover/chief-kit/vm-runner.sh statusonrefuses (rc 3) unless the repo’s fork-PR approval policy isall_external_contributors, clones VM 2 when missing, sets theHIMMEL_VM_RUNNERrepo variable toon, removes~/.himmel/ci-runner.stopand starts onehimmel-ci-runner@<n>.serviceuser unit per VM. A unit that will not start switches the runneroffagain.offsets the variableofffirst (so GitHub stops routing to the VMs), writes the stop file and stops the units; a job already running is not re-queued but no new runner is minted.statusprints the variable, per-VM up/idle/busy state and the last error.tick.shcarriesvmrun=<off | ok:<up>/<want>,busy=<n> | DEAD | STUCK | FAILING | DISAGREE:<up>/<want>,online=<n>>.DEADis variable on with no unit up;STUCKis a job running past its budget plus ten minutes;FAILINGis a loop whose newest log line is an error, or a GitHub runner count that could not be read;DISAGREEis more units with a registered, unfinished runner than GitHub shows himmel-vm runners online (fewer online between jobs is normal and readsok).chief-wait.shwakes only on a move toDEAD,STUCKorDISAGREE; answer withstatus, thenoffand a note to the operator.- With the variable
on,ci.ymlruns theshell-unit-shardmatrix on the VM runner for a same-repopull_requestwhose author and actor are the operator accounts; everything else (fork PRs, other authors, push, schedule, dispatch) and the variableoffor unset run onubuntu-latest, with the required check names unchanged.scripts/ci/test-ci-vm-routing.shpins that expression. The routed shard’s apt, sudo and pip steps run on GitHub-hosted runners only (runner.environment == 'github-hosted'); the guest must carry the tools instead (guest-provision.sh). Keep the variableoffuntil the guest image has been rebaked with those packages (ci-runner-v4) and avm-runner-probe.ymldispatch has passed with no operator PR CI running: until then every routed shard would fail and fail-fast blocks the PRs. - A classifier denial of one of these commands means the allow rule is missing or mismatched; see the stuck playbook, never reshape the command.
See also
chief-template.md: the chief’s operating contractchief-handoff-template.md: the handover skeletonworker-brief-template.md: what a dispatched worker getsafk-mode.md: the unattended-pipeline alternative../internals/retask-channel.md: authenticated re-tasking../internals/lane-calibration.md: tiers, effort, wake-up budget