DocsSupervision (zirv ctx)
Interactive use
alias claude='zirv ctx wrap -- claude'The wrapped session is byte-for-byte identical to an unwrapped one until an
intervention, injection happens only at a turn boundary while you are idle, and
any supervision failure drops it back to pure passthrough. Flags you wrap are
kept across a restart, so zirv ctx wrap -- claude --model opus comes back as
an opus session.
wrap types agent-specific text into the session it supervises (/compact,
/exit), so it has to know which agent it is driving. It recognises a command
whose program is named claude; anything else — a wrapper script, npx claude, a differently named binary — needs --agent claude to say so
explicitly, or it refuses rather than typing claude syntax into a program that
may not understand it. --no-supervise and --simple are exempt, since
neither injects anything:
alias claude='zirv ctx wrap --agent claude -- my-claude-wrapper.sh'Note that a restart relaunches the adapter's program, so a wrapped wrapper
script comes back as a bare claude.
wrap learns the transcript path from the turn signals the Stop hook sends,
and forgets it on a restart because the fresh session writes a new file. Set
ZIRV_CTX_TRANSCRIPT to pin a path instead; it outranks every signal and
survives restarts, which is what you want when the agent's hook cannot report
one, and what tests use.
Inline supervision#
A supervised run is either a dashboard pane — visible in a sidebar, its result mailed back — or an inline run in the terminal that started it. Those are the only two shapes; there is no invisible stdout-captured child.
zirv ctx exec and zirv ctx loop are always inline: they supervise a child
in this terminal, by definition. zirv agent chooses: if any dashboard is
live on this machine it asks that dashboard for a pane — preferring the one
you were launched from, then one already hosting this repository, then any
live one — and prints the pane's short id. If none is live it says so in one
line and supervises the child right here:
zirv ctx agent: no live dashboard -- running codex inline in this terminalIt never refuses, and it never launches a dashboard of its own. A pane carries
everything a delegation asks for except a tool-call ceiling (no verified
counter) and trailing -- <flags> beyond a --model pin (they would become
argv on the pane's own harness child, and a spawn request is untrusted data);
both are announced on stderr rather than dropped silently. --timeout-secs
and --max-restarts are honoured either way.
A dashboard pane gets a short kebab-case name (at most 24 characters), shown in
the pane bar and sidebar, the agent tree and its SELECTED panel, NEEDS YOU
cards, the activity log, zirv ctx status and the From-session line of the
mail it sends. --name <label> sets it; without it one is derived from the
first words of the prompt, never worker. A clash with an existing worker gets
a numeric suffix. zirv ctx send --to-session and nudge accept the name as
well as the short id; a name two sessions share is an ambiguity error. The role
is a separate field and is still shown. Inline (non-pane) runs are unnamed.
zirv ctx exec --prompt "$PROMPT" -- claude -p "$PROMPT" --session-id "$SID"--prompt is what a restart re-sends; without it (and without a -p,
--print or exec argument that zirv can read the prompt out of), a rot
verdict ends the run instead of restarting it. --session-id names the
session, and --transcript points at the first child's transcript when the
adapter cannot derive it. Both describe the first child only: every restart is
a new session whose transcript path is derived again.
Passing the whole command is optional. With --prompt and nothing after --
that names a program, exec builds the launch from the adapter itself — the
same way every restart does — so the prompt never has to be encoded into argv
and read back out:
zirv ctx exec --agent claude --prompt "$PROMPT"
zirv ctx exec --agent claude --prompt "$PROMPT" -- --model opus # extra flagsThis is what a YAML agent step uses, and it is why a prompt that happens to
begin with - or to look like a flag is still just a prompt.
Exit codes for supervised runs#
<!-- zchk-doc-exit-codes:start -->| Code | Meaning |
|---|---|
| the child's own code | the run finished on its own |
75 | rot was detected and exec could not carry on, either because the restart budget was spent or because no prompt was available to restart with. loop also returns it when consecutive cycle failures hit max_failures |
76 | the same, for a wall-clock timeout rather than rot |
77 | the token or tool-call budget was reached; the run checkpoints and stops without restarting |
78 | provider capacity or overload errors persisted until the restart budget was spent |
79 | the provider account ran out of credits or quota; the run stops without retrying |
80 | a writing delegation was refused because the tree already has a live writer; retry after it finishes or use --worktree |
81 | progress stalled after a steering nudge and its grace period, and the restart budget was spent |
82 | the worker report failed its result contract or claimed deliverables that do not exist |
The code names the reason, not which limit ran out: 75 means rot and 76
means timeout, whether the run stopped because the budget was exhausted or
because there was no prompt to restart with. A usage-limit hit is neither, it
parks and relaunches without consuming the restart budget.
Migrating an existing loop#
Replace a long-lived orchestrator session with a stateless loop, and wrap worker dispatch so individual runs get restarted rather than merely killed:
# .zirv/commands/issue-loop.yaml
name: Issue Loop
commands:
- command: zirv ctx loop --prompt-file .zirv/issue-loop-prompt.md --interval-secs 900zirv ctx exec --prompt "$WORKER_PROMPT" -- claude -p "$WORKER_PROMPT" --session-id "$SID"Durable state must live outside the session (GitHub issues and labels, for
example), because every cycle starts with a clean context. Once zirv ctx hook stop is registered, remove any older canary Stop hook from
~/.claude/settings.json: two Stop hooks scoring the same session is noise, and
the older one blocks stops, which this one deliberately never does.