0035 — An idle Driver has one waker, and a dead one has one reviver, chosen by a file
- Status: Accepted (the rule). Built in two deploy bundles, both open and not deployed, because each deploy restarts a fleet process and that step waits on Gera: the Supervisor half in this repo, the watcher half in
PropFlow-Technologies/agent-smith. ADR-0004 puts the record at decision time. It supersedes one row of ADR-0030 decision 4; see What this supersedes. ⚠️ AMENDED 2026-09-29 — THE WATCHER'S OWNERSHIP OF REVIVAL IS BOUNDED BY A GRACE, AND THE SUPERVISOR HALF IS DEPLOYED. Decision 4 said a fresh heartbeat means the Supervisor spawns nothing. The Supervisor that shipped (#236, deployed 2026-09-29 16:43Z) defers only until it has itself seen the Driver dead for 15 minutes. The watcher half (agent-smith #670) is still open and not deployed. Decision 4 is corrected in place; read Amendment 2026-09-29 at the foot. (Appended toStatuson purpose: this adds NO line, so every line-number citation into this file stays true.) - Date: 2026-09-29
- Deciders: Gera, 2026-09-29, on what the Driver is for: "giving a session all of its necessary tools to drive something to full completion. Though honestly, most models are good enough now that they don't really need the guardrails. But it is good to have because sometimes they prematurely quit or they lose sight of the board." This ADR is about the first of those two failures. ADR-0036 is about the second.
What was measured
A read-only audit of the mini on 2026-09-29 (05:40–06:30 UTC), four live Drivers: page-speed 1fdb267c, settings e40d61df, module-switch 74d91d18 and homepage 048857ea. All four are Driver-first: driverSid == workerSid, and the work runs in the Driver's own sub-agents (ADR-0025).
Four things can wake a Driver, and none of them covers a Driver-first Driver that went idle with its board short.
| Waker | Why it misses that Driver |
|---|---|
Stop gate (drive-gate) |
It fails open on purpose. It releases after 25 minutes with nothing changed, after 180 minutes in total, or after 6 blocks in a row. Once it has released, nothing takes over. |
nudge check-in (ADR-0030 row 4) |
watcher-doctor arms it, and has run as a system-domain LaunchDaemon since 2026-09-27 22:18. Every check-in it armed died before its first tick. nudge.log has 455 CHURN lines on 09-28. Three of the four Drivers had no live --_watch process, checked with ps. |
| Supervisor quiet-stop | Rule 6 refuses whenever worker equals driver, which has been the default shape since 2026-09-12. Every live task's log says quiet-stop: no wake — worker and driver are the same session. |
| Temporal watcher | It nudges only a Driver that is busy and stalled. An idle Driver it leaves alone. |
A Driver that dies has two revivers, and they neither use the same test for death nor read each other.
- The Supervisor checks liveness by session id: roster, then panes, then process parents. It spawns a fresh successor session. It stops after
MAX_RESPAWNS = 5restarts over the task's whole life. page-speed is at 5/5, so its next death parks the task instead of reviving it. - The Temporal watcher checks liveness by roster name (
match_by_name). It resumes the same session withclaude --resume <sid> --bg. It stops after 5 revives per run.- The run itself ends at a 72-hour hard lifetime even while the task is open. settings' watcher closed
EXPIREDat 2026-09-27T17:12Z with "4 revive(s)", and its task.json still saysworking. - A session renamed after its watcher was armed looks dead to the watcher, and the watcher revives a session that is still alive.
- The run itself ends at a 72-hour hard lifetime even while the task is open. settings' watcher closed
The decision
The Supervisor is the one waker for an idle Driver. For a Driver-first task, rule 6 no longer refuses. The Supervisor wakes the Driver when all of the following hold:
- The task is neither terminal nor
awaiting-human.awaiting-humanmeans a person owes the next move. - The Driver is alive. This is read three ways; an undetermined reading does nothing.
- The Driver has been quiet for at least 15 minutes, the Stop gate's own quiet window. Quiet means the Driver's transcript and every sub-agent transcript under its session stopped growing. A Driver waiting on its own sub-agents is working, not idle.
- The backoff has run out. It grows 15 → 30 → 60 → 90 minutes while the task's authored state does not move, and resets when it does. "Authored state" is the same fingerprint the watcher uses: task.json, acceptance.json and events.jsonl.
The wake text is the canonical Driver check-in,
watcher-doctor --checkin-text <task>. There is one source for it, and it carries ADR-0033's rule that "nothing moved" is not an answer. The idle-streak audit is asked when the wake fires, not when it is armed. An unreadable board still wakes: a missed wake costs a quit, while an extra wake costs one turn.Why the Supervisor: it already sweeps every task file every 60 seconds. Coverage is triggered by the artifact (ADR-0030 principle 2), so nothing has to be armed per Driver, and there is no per-Driver process that can die. The check-in needed both an arm per Driver and a process per Driver, and both failed silently for two days.
- The task is neither terminal nor
The
nudgecheck-in stops being a Driver waker once decision 1 is deployed.watcher-doctorstops counting Drivers as expected lanes, a local-bin change that ships with the Supervisor deploy.nudgekeeps all its non-Driver jobs, and its launcher bug is still owed for them. Until the deploy, the check-in stays as the interim waker. On 2026-09-29 it was armed by hand from a user shell for module-switch and homepage.The Temporal watcher keeps the one wake the Supervisor cannot give: busy and stalled, meaning a wedged turn. That is not an idle wake, and it is unchanged.
Only one reviver acts for a task at a time, and a file decides which.
- The watcher writes
~/.claude/jobs/tasks/<task>/watcher.jsonon every tick. It holdsworkflowId,runId,sidandtickAt, plus what that tick's probe saw (rosterOk,found). The probe activity writes it, so no new workflow command exists and running histories replay unchanged. - While
tickAtis less than 10 minutes old, the watcher owns revival for a bounded grace. The Supervisor counts no dead readings toward a respawn and spawns nothing until it has itself seen the Driver dead for 15 minutes (WATCHER_REVIVE_GRACE_MS). Past that, revival comes back to it. See Amendment 2026-09-29. - When the file is stale or absent, the Supervisor owns revival, as it does today.
- The Supervisor reads a file and never asks Temporal (ADR-0003).
Why the watcher goes first: it resumes the same conversation, so the Driver keeps its board in context, which is the other failure Gera named. It also survives worker restarts, and it holds the item claims. The Supervisor's successor path remains for when the watcher itself is gone.
- The watcher writes
Liveness is judged by session id. Names are labels. The watcher finds its roster row by sid first and by name second. A revive mints a new sid, and the watcher already re-captures it from the revive's output. A row counts as gone only when neither the sid nor the name matches. A live session is never revived just because it was renamed.
Restart caps count a window, not a lifetime.
- Neither reviver may revive a task more than 5 times in any rolling 24 hours.
- The Supervisor keeps the times in task.json
driverRespawnTimes. The watcher carries its own list across continue-as-new. driverRespawnsstays as a lifetime count, for display only.- A sixth death inside 24 hours still parks the task and escalates, as it does today.
The watcher lives as long as its task is open. At its lifetime ceiling it continues-as-new instead of expiring, as long as the task is not terminal. Two exits stay:
- The 2-day no-movement idle exit, which is how an abandoned task ends.
- The nudge-ceiling
FAILEDexit.
Temporal's terminal state is copied into the task by the task's only writer.
- The watcher already appends a
closedevent to events.jsonl when it ends. - The Supervisor, which writes task.json through
writeTask, copies{status, closedAt, reason}into task.jsonwatcherand into STATE.md's health line. - "Watcher closed while the task is open" is a named health failure there, never "OK".
- The copy never changes
state. Only the finisher declares a task done (DESIGN §9c).
- The watcher already appends a
A revival counts only if the revived session drives
Live evidence from 2026-09-28/29 added three decisions. Each one makes a wake or a revival that arrives also one that acts.
- A kickoff or revival brief is delivered as the session's own prompt, never pasted into its pane.
- Evidence: the Supervisor revived module-send-gate at 2026-09-28T02:10Z (tmux
op-module-send-gate,claude --permission-mode auto) by pasting its brief. The model replied: "I read the task's files but haven't taken on the Driver role yet. Your message was entirely pasted text, with nothing you typed yourself… I want your go-ahead first." - Pasted content is treated as untrusted, so every revival or kickoff delivered by pane paste can produce a Driver that sits idle, which is a quit by construction.
- The brief rides the
claudeCLI prompt argument on spawn and on--resume, the way ccswitch already restarts sessions (--resume <sid> "[restart…]"). - Applies to the Supervisor's spawn and revive, and to local-bin's
start-driveranddriver-kickoff. - Mail to a session that is already driving (
msg, quiet-stop wakes) is not a kickoff and is unchanged.
- Evidence: the Supervisor revived module-send-gate at 2026-09-28T02:10Z (tmux
- A spawned session is never left on a first-run prompt. Evidence: a Claude Code first-run dialog ("Teach auto mode about your environment?") held the auto-mode
op-module-send-gatesession on a prompt, holding a message it never read. Every interactive prompt the fleet is known to meet at launch is pre-answered at spawn, by settings or flags. A spawn that lands on a prompt it cannot answer is a failed spawn and is logged as one, not as a Driver. - A respawn-cap alarm offers an action, not only an acknowledgement.
- The alarm was not lost.
sess-propflow-ef's cap fired on 09-13, 09-14, 09-15 and 09-24 witharchitect=no-addressee. Each time it reached the decision ledger (reached: decision ledger sup-operator-respawn-cap-2…-15), whichescalate()does by design when no Architect session is live. Each row was resolved by a person. - The puller deliberately does not route
sup-*rows to the decider: an alarm is not a question. - The defect is the row's options: "Acknowledged — close this alarm" and "Already handled — close it". Answering it executed nothing, so every time a person also had to un-park the task by hand (ADR-0013: answered is not executed).
- With decision 6 the alarm fires only on five deaths inside 24 hours, not on five over a fortnight. When it does fire, its row offers revive once more (the Supervisor clears the window and respawns) or leave it parked. The Supervisor executes the answer on its next sweep.
- The alarm was not lost.
Rejected
- Fix the
nudgelauncher and keep it as the Driver waker. It needs one arm and one process per Driver, and both failed without a sound for two days. ADR-0030 principle 1 says to delete the second path, not widen it. The launcher fix is still owed, but fornudge's other jobs. - Make the Temporal watcher the idle waker. An idle-wake loop inside a workflow adds history on every tick for the life of the task. page-speed's history is already long enough that its queries time out. Judging a Driver-first Driver's quiet also needs transcript and sub-agent mtimes, which the Supervisor already reads.
- Make the Supervisor the only reviver. It would throw away the running conversation on every death. The successor would re-derive the board from STATE.md, which is the lost-board failure this system exists to prevent.
- A Stop gate that never releases. A wall in a Stop hook traps a session whenever the board read is wrong. It stays a speed bump and fails open. Whatever runs after it releases is this ADR's decision 1.
What this supersedes — precisely, and nothing more
| Record | Superseded | What stands |
|---|---|---|
| ADR-0030 decision 4, row "Waking the Driver that owns it" | The owner, nudge check-in armed by driver setup, becomes the Supervisor's Driver-first wake, which covers every task file with nothing to arm. |
The rest of the table, and principles 1–6. The check-in text is unchanged and remains the single source of what a wake says. |
supervisor/src/quietstop.ts rule 6 (code, not an ADR) |
"The worker is never the driver" as a refusal. | Rules 1–5 for tasks that do have a separate worker. |
Consequences, and what happens to the four live Drivers
Nothing in a live task directory is written by this ADR or by either bundle's code before it is deployed.
- page-speed (
sess-propflow-ef): its 5 lifetime respawns carry no timestamps. The windowed count therefore starts at 0 on deploy, and its next death revives instead of parking. - settings (
sess-propflow-24): its watcher expired, and after the Supervisor deploy that shows as a named health failure. Its work is finished (the finisher shows no Settings group open). Closing the task is a separate step, awaiting approval. - module-switch, homepage (
sess-module-switch,sess-propflow-58): covered by the hand-armed check-in until the Supervisor deploy, then by the Supervisor. - Deploy order is free.
- With the Supervisor half live and the watcher half not,
watcher.jsondoes not exist, so the Supervisor revives exactly as it does today, plus the window and the waker. - With the watcher half live and the Supervisor half not, the watcher gets sid-first liveness and its longer life, and the Supervisor ignores a file it does not read yet.
- The double revive ends only when both are live.
- With the Supervisor half live and the watcher half not,
- Deploying the watcher half means merging it. agent-smith redeploys its worker by itself within about 25 minutes.
- Deploying the Supervisor half means a build and a restart of the Supervisor process.
- The known live risk until both halves ship. page-speed's
driverRespawnswas reset from 5 to 0 by hand on 2026-09-29, with Gera's approval. While two revivers act (the Supervisor by sid, the watcher by name), one real death can produce two Drivers. The runbook for each bundle names this.
Amendment 2026-09-29 — a fresh heartbeat buys a watcher 15 minutes, not ownership
What changed. Decision 4 read "while tickAt is fresh … the Supervisor spawns nothing". The Supervisor that shipped defers for a bounded grace instead. Receipt: supervisor/src/revival.ts decideDeadDriver, deployed at b3ebf76.
- If the heartbeat is fresh and the Driver has been dead less than 15 minutes (
WATCHER_REVIVE_GRACE_MS, measured by the Supervisor's own liveness reading), the Supervisor defers. - If the Driver stays dead past that, the watcher is ticking but not reviving: wrong roster name, its own errors, a spent budget, or a roster it cannot read. Revival comes back to the Supervisor, and its log says so.
- If the heartbeat names a different sid and that sid is alive, the watcher already revived the Driver under a new sid. The Supervisor adopts that sid and revives nothing.
Why. Found in the review of #236 (§2): "the absence of action reads as ownership" is the shape a supervisor must not have. The review of agent-smith #670 (round 5) then found the concrete case.
- A watcher whose probe cannot read the roster (for example,
claude agents --jsonchanged shape or timed out) cannot see a death, so it cannot revive. - It still writes a fresh
tickAtevery tick, and its lifetime renews while the task is open. - Under the unamended text, nobody would revive that Driver until the watcher's 2-day idle exit. Under the grace, it is 15 minutes.
What does not change.
- The watcher still writes the file on a blind tick.
rosterOkandfoundstay diagnostic, and the Supervisor does not weigh them. - Skipping blind ticks would cost more than it saves. A watcher blind from its first tick would never publish its
workflowId, and that id is howstartfinds a running watcher before it mints a second one (ADR-0036). - The Supervisor's liveness reading is roster, then panes, then process parents, so the roster outage that blinds the watcher does not blind the Supervisor.