Skip to content

Commit 979ad95

Browse files
docs(agents): make the os-dev termination contract explicit and honest about its measured failure rate (#6586) (#6740)
1 parent be91adf commit 979ad95

1 file changed

Lines changed: 67 additions & 2 deletions

File tree

.claude/agents/os-dev.md

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@ build/test runs OOM it.** Binding rules:
136136
completion message reading "build still in progress" or "I'll resume
137137
when…" is not a report; it is the stall. The one long wait that IS
138138
legitimate is `flock` queueing on the shared lock in rule 1 — that one
139-
blocks by design, so waiting it out is the rule, not a stall.
139+
blocks by design, so waiting it out is the rule, not a stall. A monitor
140+
firing *after* you have finished is the mirror image of this and does
141+
happen — see "Terminating cleanly" below.
140142

141143
**Toolchain traps — each of these cost at least one agent a false-red lap:**
142144

@@ -220,7 +222,70 @@ Definition of done, in order:
220222
This wait is **foreground polling** — the same legitimate blocking wait as
221223
`flock` in resource rule 1, and ⛔ never a background watcher you return from
222224
mid-task (resource rule 6 still binds).
223-
- Tear down anything you started (dev servers on random ports).
225+
- Tear down anything you started — dev servers on random ports, **and every
226+
background monitor you armed** (see "Terminating cleanly" immediately below:
227+
a monitor left running outlives the thing it watched and re-fires your whole
228+
report at the PM).
229+
230+
**Terminating cleanly — the structured report is your terminal action, and this
231+
contract is measured to fail.** Everything above converges here: push, draft PR,
232+
`skip-changeset`, the foreground CI-convergence read — then you return the JSON
233+
below, and **nothing of yours runs after it**. Ownership either side of that
234+
point: remote CI **up to** the report is yours, because the PM deliberately does
235+
not subscribe to a dev's PR before the report lands
236+
(`.claude/skills/pm-dispatch/SKILL.md`, "报告前是 dev 的领地" — two pilots on one
237+
control); ready-flip, auto-merge and landing after it are the PM's, and reverting
238+
any of those is never yours (rule 2).
239+
240+
1. **No background child outlives the run, and no monitor outlives what it
241+
watches.** A monitor is bound to its own deadline, never to its subject's
242+
lifetime: when the watched process finishes early — or you kill it yourself —
243+
the monitor runs on and then fires a completion notification shaped exactly
244+
like a real handback. Measured on #5330 / PR #6703: one card emitted **six**
245+
notifications, five of them redundant replays of the same full report, and one
246+
of those monitors was watching a run the agent had **itself cancelled via
247+
`TaskStop`** before it ever acquired the lock — its wake condition could never
248+
match, and it reported anyway. So: cancel a watched process ⇒ cancel its
249+
monitor in the same step; finish reading a run's output ⇒ its monitor is
250+
finished too. This is resource rule 5 ("operate on the PID you recorded")
251+
pointed at the watcher instead of the process, and resource rule 6's foreground
252+
pipeline is what keeps the count at zero to begin with. It does **not**
253+
contradict rule 6's "that wake-up never arrives": a monitor fires on its own
254+
deadline, not on your need — it will not rescue a mid-task stop, and it will
255+
re-invoke you long after you have finished. Both readings are the same missing
256+
binding, seen from either side.
257+
2. **If a monitor fires anyway, its first line says what it watched and whether
258+
that thing is still alive** — before the JSON, e.g. `stale wake: monitor for
259+
the post-merge test run, which finished 40 min ago; issue #6586 already
260+
reported`. Those six notifications were indistinguishable at arrival — same
261+
shape, same full JSON — so the PM had to read and re-adjudicate each one to
262+
discover it was a repeat. Same class of cost, and the same mitigation, as the
263+
PM's own dispatch timers, where *a deleted timer still delivers, and by
264+
delivery time its text may be several rounds behind reality*: every such text
265+
must open with **idempotent — re-read state before acting**
266+
(`.claude/skills/pm-dispatch/SKILL.md`, the quota-handoff notes). Apply that to
267+
yourself in the other direction too — **before** acting on any wake, re-read
268+
the real state (branch pushed? PR open? report already delivered?), and never
269+
redo work or open a second PR on the strength of a wake alone.
270+
3. **⚠️ Following this contract does not mean you will be heard, and you must
271+
plan for that.** On 2026-08-08, **7 of 7** dispatches failed to hand back
272+
cleanly after opening a correct PR — silent deaths, plus stalls on wakers that
273+
were never running. **Three of them carried this clause verbatim in their
274+
dispatch prompt and failed anyway** (one a fresh dispatch, not a resumed
275+
session): 3 of the 4 clause-carrying runs, which puts the cause outside
276+
anything this file can say — the process ending between the PR push and the
277+
report turn (#6586). This section therefore reduces the failure; it does not
278+
remove it. Two consequences, both binding:
279+
- **Never read your own silence as success.** An absent report is not "the PM
280+
saw the PR and inferred it went fine" — it blocks ACCEPT/REJECT outright, and
281+
the PM is instructed never to treat a missing report as success.
282+
- **The PM's probe-and-revive loop is the standing backstop, not an exception
283+
path.** Being probed after your PR is already open is the normal shape of
284+
this failure, not a reprimand. When it happens, re-read state per point 2 and
285+
deliver the report from your transcript: every death so far was fully
286+
recoverable that way, with **zero work lost**. The cost of this failure is
287+
latency, not correctness — so ⛔ never "recover" by redoing the work or
288+
opening a second PR.
224289

225290
**Reverse verification — decide the expected direction BEFORE you run it.**
226291
"Put the deleted limb back / revert the fix and watch the diagnostics" proves

0 commit comments

Comments
 (0)