PYR0D-353: Count only live workers against the away spend cap, and show headroom at entry #41

Merged
john merged 9 commits from fm/fm-away-spend-cap-live into main 2026-10-02 06:39:05 +00:00
Owner

Story: PYR0D-353 - Away mode: count only live workers against the away spend cap, and show headroom at entry

What was wrong

On 2026-10-01 away mode did no work for 4h21m. The away spend cap in bin/fm-spawn.sh counted every task record that wasn't a secondmate, including workers that had already exited and were only parked on a PR waiting for a merge. At entry, 7 records counted against the default cap of 4 while one worker was running. The captain's words held merges, so the parked records could never drain, and every spawn was refused all night. This is upstream bug kunchenguid/firstmate#6139, carried here until a sync brings upstream's fix. The diagnosis is in firstmate's scout report fm-afk-idle-diagnosis.

The return brief made it worse: it reported "the away session took no action" and "0 outcome(s)". On the daemon path nothing writes the outcome store it reads, so it could not know.

What changed

  • New bin/fm-away-spend-lib.sh holds one shared count of live spenders: records whose worker agent is still running. Liveness is read first: an exited worker parked on a PR, a stopped worker and a record whose endpoint is gone do not count; live workers count, including ones paused on their own job or waiting at a gate. The spawn cap, the return brief's "tasks live" count and the entry read-back all use it. If the count cannot be read, the cap refuses rather than letting work through.
  • The away entry read-back gains a headroom line: the cap and how many workers count against it now.
  • On the daemon path the return brief says the path records no outcomes, instead of claiming no action was taken.
  • The afk skill relays the headroom line at entry.

Tests

Colocated tests cover each change and were proven red against the old code:

  • Cap 4 with seven records (six parked, one live) allows a spawn.
  • Four live workers still refuse.
  • A relaunch stays exempt.
  • The read-back shows headroom.
  • The return brief wording on the daemon path.

The no-mistakes pipeline passed review, test, document and lint at daa7a831.

What stays open

This fixes the first of the two causes. The second is that nothing gives firstmate a turn when the fleet goes idle, and the captain's away words are never re-read on the Claude daemon path. That belongs to the supervision-host adoption (D148 A, fm-adopt-away-host), starting with the Node module-type fix.

Decisions made in review

  • Live agents waiting at a gate or on the captain still count. They hold a session and resume without a spawn, so they never pass the cap check again. The first adversarial review (F2/F3) asked for this. Only exited and PR-parked workers, the ones that stalled the 10/01 night, are freed.
  • A relaunch is exempt only while its own record counts. Restarting a dead worker takes a slot like any spawn (first review F4). Relaunches are firstmate-driven and serial, so the concurrent-relaunch race is noted, not fixed.
  • Launch grace window. A record written within the grace window counts even if its endpoint reads dead, so a just-spawned worker whose agent has not started cannot let a second spawn past the cap.
  • Unverified backends (Zellij, Orca, cmux). A capture failure there is labelled "backend target gone" but proves no death, so it counts unless an authoritative check proves the endpoint gone (second review B1).

Review

Two adversarial reviews by other models (Claude Opus at daa7a831, Codex gpt-6-luna at bf7e1ecd), with every blocking item fixed. A third Codex check of the B1 rework at 878752c8 approved. CI is green at 878752c8.

**Story:** PYR0D-353 - Away mode: count only live workers against the away spend cap, and show headroom at entry ## What was wrong On 2026-10-01 away mode did no work for 4h21m. The away spend cap in `bin/fm-spawn.sh` counted every task record that wasn't a secondmate, including workers that had already exited and were only parked on a PR waiting for a merge. At entry, 7 records counted against the default cap of 4 while one worker was running. The captain's words held merges, so the parked records could never drain, and every spawn was refused all night. This is upstream bug kunchenguid/firstmate#6139, carried here until a sync brings upstream's fix. The diagnosis is in firstmate's scout report `fm-afk-idle-diagnosis`. The return brief made it worse: it reported "the away session took no action" and "0 outcome(s)". On the daemon path nothing writes the outcome store it reads, so it could not know. ## What changed - New `bin/fm-away-spend-lib.sh` holds one shared count of live spenders: records whose worker agent is still running. Liveness is read first: an exited worker parked on a PR, a stopped worker and a record whose endpoint is gone do not count; live workers count, including ones paused on their own job or waiting at a gate. The spawn cap, the return brief's "tasks live" count and the entry read-back all use it. If the count cannot be read, the cap refuses rather than letting work through. - The away entry read-back gains a headroom line: the cap and how many workers count against it now. - On the daemon path the return brief says the path records no outcomes, instead of claiming no action was taken. - The afk skill relays the headroom line at entry. ## Tests Colocated tests cover each change and were proven red against the old code: - Cap 4 with seven records (six parked, one live) allows a spawn. - Four live workers still refuse. - A relaunch stays exempt. - The read-back shows headroom. - The return brief wording on the daemon path. The no-mistakes pipeline passed review, test, document and lint at daa7a831. ## What stays open This fixes the first of the two causes. The second is that nothing gives firstmate a turn when the fleet goes idle, and the captain's away words are never re-read on the Claude daemon path. That belongs to the supervision-host adoption (D148 A, `fm-adopt-away-host`), starting with the Node module-type fix. ## Decisions made in review - **Live agents waiting at a gate or on the captain still count.** They hold a session and resume without a spawn, so they never pass the cap check again. The first adversarial review (F2/F3) asked for this. Only exited and PR-parked workers, the ones that stalled the 10/01 night, are freed. - **A relaunch is exempt only while its own record counts.** Restarting a dead worker takes a slot like any spawn (first review F4). Relaunches are firstmate-driven and serial, so the concurrent-relaunch race is noted, not fixed. - **Launch grace window.** A record written within the grace window counts even if its endpoint reads dead, so a just-spawned worker whose agent has not started cannot let a second spawn past the cap. - **Unverified backends (Zellij, Orca, cmux).** A capture failure there is labelled "backend target gone" but proves no death, so it counts unless an authoritative check proves the endpoint gone (second review B1). ## Review Two adversarial reviews by other models (Claude Opus at daa7a831, Codex gpt-6-luna at bf7e1ecd), with every blocking item fixed. A third Codex check of the B1 rework at 878752c8 approved. CI is green at 878752c8.
The away spend cap counted every task record, so exited workers parked on a PR
waiting for a merge held every slot and no spawn could run all night
(kunchenguid/firstmate#6139). The cap, the return brief's tasks-live count, and
a new headroom line in the entry read-back now share one count of records whose
worker endpoint is alive and not parked, held, or done. On the away daemon path,
which writes no outcome store, the return brief says it records no outcomes
instead of claiming the away session took no action.
no-mistakes(document): Relay away entry spend headroom in afk skill
Some checks failed
CI / Test coverage guard (pull_request) Successful in 32s
CI / Behavior portable parallel 2 (pull_request) Successful in 15m23s
CI / Behavior portable parallel 1 (pull_request) Successful in 17m32s
CI / Lint 2 (pull_request) Successful in 23m58s
CI / Lint 1 (pull_request) Successful in 24m12s
CI / Behavior portable serial 1 (pull_request) Successful in 20m19s
CI / Behavior portable serial 3 (pull_request) Successful in 17m10s
CI / Behavior portable serial 2 (pull_request) Successful in 16m3s
CI / Behavior portable serial 4 (pull_request) Successful in 14m24s
CI / Behavior portable serial 6 (pull_request) Successful in 15m46s
CI / Behavior portable serial 7 (pull_request) Successful in 16m15s
CI / Behavior portable serial 8 (pull_request) Successful in 16m6s
CI / Repo invariants (pull_request) Successful in 8s
CI / Behavior portable serial 5 (pull_request) Successful in 26m15s
CI / Behavior tests (Herdr) (pull_request) Has been cancelled
CI / Behavior portable serial 9 (pull_request) Has been cancelled
daa7a8314f
Rework the away spend count after review: count paused, gated and blocked workers, cap a dead worker's relaunch, count a read with no result, and decide the daemon path from its flag
All checks were successful
CI / Test coverage guard (pull_request) Successful in 27s
CI / Behavior portable parallel 2 (pull_request) Successful in 15m0s
CI / Behavior portable parallel 1 (pull_request) Successful in 17m22s
CI / Lint 2 (pull_request) Successful in 23m34s
CI / Lint 1 (pull_request) Successful in 25m54s
CI / Behavior portable serial 2 (pull_request) Successful in 16m5s
CI / Behavior portable serial 1 (pull_request) Successful in 20m29s
CI / Behavior portable serial 4 (pull_request) Successful in 14m41s
CI / Behavior portable serial 3 (pull_request) Successful in 17m16s
CI / Behavior portable serial 6 (pull_request) Successful in 15m59s
CI / Behavior portable serial 7 (pull_request) Successful in 16m18s
CI / Behavior portable serial 8 (pull_request) Successful in 16m14s
CI / Repo invariants (pull_request) Successful in 8s
CI / Behavior portable serial 5 (pull_request) Successful in 26m48s
CI / Behavior tests (Herdr) (pull_request) Successful in 12m51s
CI / Behavior portable serial 9 (pull_request) Successful in 23m8s
422459768f
A worker that declared a wait on its own job, a pipeline gate or blocker
firstmate answers while away, and an unreadable endpoint all resume without a
spawn, so they count. Only done and failed records and records whose endpoint is
gone leave the cap. A relaunch is exempt only while its record counts. A record
whose state read left no result counts as spend. The return brief reads whether
the away daemon ran from its mode flag instead of re-deriving it from the
harness and config, which drops the engine-lib helper that collided with
upstream's function of the same name.
no-mistakes(test): Fix afk test fixtures for liveness-first spend count
All checks were successful
CI / Test coverage guard (pull_request) Successful in 30s
CI / Behavior portable parallel 2 (pull_request) Successful in 15m22s
CI / Behavior portable parallel 1 (pull_request) Successful in 17m31s
CI / Lint 1 (pull_request) Successful in 22m15s
CI / Lint 2 (pull_request) Successful in 26m4s
CI / Behavior portable serial 2 (pull_request) Successful in 16m5s
CI / Behavior portable serial 1 (pull_request) Successful in 20m24s
CI / Behavior portable serial 3 (pull_request) Successful in 17m17s
CI / Behavior portable serial 4 (pull_request) Successful in 14m42s
CI / Behavior portable serial 6 (pull_request) Successful in 15m48s
CI / Behavior portable serial 7 (pull_request) Successful in 16m11s
CI / Behavior portable serial 8 (pull_request) Successful in 16m5s
CI / Repo invariants (pull_request) Successful in 8s
CI / Behavior portable serial 5 (pull_request) Successful in 26m14s
CI / Behavior tests (Herdr) (pull_request) Successful in 13m53s
CI / Behavior portable serial 9 (pull_request) Successful in 24m11s
bf7e1ecdbf
fm-crew-state.sh reports "backend target gone" for any capture failure on a backend with no liveness classifier (zellij, orca, cmux). That proves no death, so the spend count treats it as spending there; a verified dead endpoint, a torn-down worktree, and the other authoritative gone reasons still free the record.
no-mistakes(document): Say stopped workers do not count against away spend cap
All checks were successful
CI / Test coverage guard (pull_request) Successful in 30s
CI / Behavior portable parallel 2 (pull_request) Successful in 15m19s
CI / Behavior portable parallel 1 (pull_request) Successful in 17m25s
CI / Lint 2 (pull_request) Successful in 23m57s
CI / Lint 1 (pull_request) Successful in 24m15s
CI / Behavior portable serial 2 (pull_request) Successful in 16m2s
CI / Behavior portable serial 1 (pull_request) Successful in 20m22s
CI / Behavior portable serial 4 (pull_request) Successful in 14m27s
CI / Behavior portable serial 3 (pull_request) Successful in 17m15s
CI / Behavior portable serial 6 (pull_request) Successful in 15m52s
CI / Behavior portable serial 7 (pull_request) Successful in 16m11s
CI / Behavior portable serial 8 (pull_request) Successful in 16m9s
CI / Repo invariants (pull_request) Successful in 8s
CI / Behavior portable serial 5 (pull_request) Successful in 26m15s
CI / Behavior tests (Herdr) (pull_request) Successful in 13m16s
CI / Behavior portable serial 9 (pull_request) Successful in 24m2s
878752c823
john merged commit 6b0fa30426 into main 2026-10-02 06:39:05 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
john/firstmate!41
No description provided.