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:
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user