a dead watch wakes its commander instead of staying dead

the machine rebooted 2026-08-24 at 12:40 and took the deploy commander's loop
with it. a release was tagged the next day and sat there. the only evidence
was a word missing from a card.

the obvious fix is for the platform to respawn the loop, and docs/command-hq.md
settled on 2026-08-18 that it must not: the agent is only notified about a
process it started and is tracking, so a platform-spawned watch would run
correctly, fire, and reach nobody. that trades a visible failure for an
invisible one, which is worse — not watching is at least legible.

so the platform notices and hands the turn back. the agent re-arms itself, the
way it already does after every fire, and the process it starts is tracked.
the owner's two conditions hold by construction: the commander is still the
only thing that starts a watch, and the indicator still reads the pid file.

DETECTION is the pid file, not watcher_armed. that column is a claim only one
path sets and most commanders never take it — both commanders here read false
while one was demonstrably watching. a pid file that exists with nothing alive
behind it means a watch that was running and is not.

which made stop ambiguous, so stopCommanderProcesses now removes the file.
without that, a deliberate stop looks exactly like a crash and the reviver
would ask the commander to re-arm what the owner just switched off.

the commander now keeps WATCHERS.md — what each watch is, the exact command,
the interval, what it does when it fires — because when the wake arrives the
transcript is long past the turn that armed it and the platform deliberately
does not remember the command. a commander predating the file may rebuild from
JOB.md, a script in its directory, or the turn that armed it; inventing one it
cannot find is forbidden, since a wrong watch looks like a working one.

the owner is notified separately from the commander being woken. they are
different audiences and only one of them can act when the wake does not work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
2026-08-25 16:27:39 +00:00
co-authored by Claude Opus 5
parent 16da10150b
commit e1f5d01b90
6 changed files with 280 additions and 0 deletions
+27
View File
@@ -214,6 +214,31 @@ export function commanderClaudeMd(params: CommanderClaudeMdParams): string {
'The platform cannot find this process and cannot recover a spawned agent id after it exits, so this',
'record is the only one. Without it, deleting this commander leaves its loop running.',
'',
'### `WATCHERS.md` — how to start your own watch again',
'',
'A pid is not a watch. It is a number that stops meaning anything the moment the process ends, and it',
'ends more often than you would think: the machine reboots, something kills the tree it was spawned',
'under, the loop hits a bug and exits. When that happens the platform will notice, and it will hand',
'the turn back to you asking you to start the watch again.',
'',
'**At that moment your only source is this file.** The transcript may be long past the turn that armed',
'it, and the platform deliberately does not remember your command — it never starts a watch itself,',
'because a process it spawned could not reach you when it fired.',
'',
'So keep `WATCHERS.md` true, and write it the moment you arm, change or stop a watch. One entry each:',
'',
'- **what it watches** — in a sentence, so a person can tell whether it is still wanted.',
'- **the exact command**, verbatim, the way you ran it. Not a description of it.',
'- **how often** it checks, and how it decides something has happened.',
'- **what it does when it fires**, or which file says.',
'- **when you armed it**, and anything you learned since that would change how you start it.',
'',
'The test: could you restore this watch from that entry alone, having read nothing else? If not, the',
'entry is a note rather than a record, and the watch dies with the process.',
'',
'Rewrite the entry when you re-arm. A stale command in this file is worse than no file, because it',
'will be followed.',
'',
...credentialSection(params.credentialPath),
'## The shape: a trigger, a ledger, and up to three steps between them',
'',
@@ -345,6 +370,8 @@ export function commanderClaudeMd(params: CommanderClaudeMdParams): string {
' the job is settled and kept true after that.',
'CLASSIFICATION.md the category rules, if it classifies. yours, and expected to change.',
'DISPATCHING.md what happens to a result beyond keeping it, if anything. yours.',
'WATCHERS.md every watch you are running, exactly enough to start it again. yours,',
' rewritten whenever you arm, change or stop one.',
'data/ everything this commander has produced. ALWAYS called data.',
' <category>/ only if it classifies — one directory per category, as they are agreed',
' unclassified/ only if it classifies — produced and recorded, placement still open',