Description
A park's until conflates "expected to resume" with "about to give up"
4d4f52f (#591) made waiting_until real: an engine_limit park now records the instant it expects to resume, and work-report.ts:127 renders it as "expected to resume in …".
The human handoff deliberately does not set it, and that was the right call — its 15-minute deadline is a give-up, not a resume. Rendering a give-up time under "expected to resume" would tell an owner to wait for something that is about to stop waiting for them.
So the field is correct today only because one of its two producers abstains. That is a gap held open by omission, not by design.
The promise is now half-true, in two places
composition.ts:255— "waiting(deliberately parked —waitNotesays what for and until when)". True forengine_limit, false forhuman.RUN_HEALTH_LEGEND(added by the concurrent The server-side health verdict reports 'working' for all 89 finished runs — the enum has no member for a closed run #588/subordinate_status and the console still read the raw status column — a supervisor waits on an agent the platform knows has stopped #589 work) carries the same promise with the same status.- The sibling that says only "saying what for" (
coding.ts:170) remains the accurate one.
Why this misleads
The two deadlines demand opposite actions. "Resuming at 16:00" means do nothing. "Giving up at 16:00" means intervene now. A single field rendered with a single verb cannot carry both, and the owner cannot tell which they are looking at.
Acceptance criteria
- A deadline that is a give-up is distinguishable from one that is a resume — a second field, or a reason-aware renderer. State which and why.
- The human handoff's 15-minute deadline becomes visible. It is currently knowable only by not being there.
composition.ts:255andRUN_HEALTH_LEGENDstate what is true for every park reason, not the best case.- A guard asserts every park reason's deadline is rendered with the verb matching its kind, denominator over all park reasons per ADR 0002.
- Red demonstrated: a human-handoff deadline must not render as "expected to resume".
Verified vs inferred
- Verified:
work-report.ts:127's clause; that the human path passes nountil; both legend strings;coding.ts:170. - Inferred: nothing. The abstention is deliberate and was reported as such by the agent that implemented it.
Activity
fix(runs): the health legend stops promising a resume time a human ha…
fix(mcp): the health vocabulary is rendered from the enum that emits …
fix(runs): a park states WHICH kind of deadline it has, so the handof…
serge-ivo commentedon Aug 15, 2026
Shipped in
41cebd1onmain. CI green, API + MCP deploys green.AC1 — the decision: derive the verb from the park reason, do not store a second field
workers/api/src/lib/work-report.tsnow holds aPARKStable keyed by everyRunWaitReason,each entry carrying what the park waits FOR and which kind of deadline it has
(
resume|give_up), plus aDEADLINE_CLAUSEtable with one clause per kind. The docblockrecords the alternatives and why they lost:
waiting_deadline_kind, orwaiting_giveup_atbesidewaiting_until) —rejected. The kind of a deadline is not an independent fact; it is entailed by why the run is
parked, and that reason is already stored, already closed, already a platform enum. A column
duplicating it is one fact in two places that a writer has to keep in step and nothing can force
to agree — the shape A run whose engine died at step 1 reported 'running' for 4.35 hours with a fresh lastProgressAt, and wrote nothing to the trace #580/A platform deploy kills in-flight coding runs, and the Pilot writes retryable:true for a reader that does not exist #583/The server-side health verdict reports 'working' for all 89 finished runs — the enum has no member for a closed run #588/subordinate_status and the console still read the raw status column — a supervisor waits on an agent the platform knows has stopped #589 have spent this batch removing — and it costs a
migration to store something derivable.
ifin the renderer — rejected. That is what the ternary chain already was,and it is exactly the shape that let the human case be handled by omission: a chain has no
denominator. It did fall through, too — any unrecognised reason rendered as "a platform update
interrupted it", asserting a specific park about a value nothing recognised. An unknown reason
now renders as a park with no deadline clause at all, because with no kind the instant could
only be printed under a guessed verb.
waitingUntil— rejected. Every non-testreference in the tree either produces that column or hands it to
waitClause; nothing reads itprogrammatically, so it would add a third thing to keep in step for no reader that exists.
RunWaitReasonbecameRUN_WAIT_REASONS, anas constarray (agent-loop-store.ts), for thereason
RUN_HEALTH_STATESis one.AC2 — the handoff's 15 minutes are visible, in two places
nobody does within 15 minutes it stops and reports that it is waiting on you."
waitForHumannow passesuntiltodeps.tick, sowaiting_untilcarries thegive-up instant. It is computed from the polls remaining, so every tick names the same
absolute moment and it survives a Workflow replay — a deadline captured before the loop would
have slid a whole wait forward. The rendered note reads:
…it is waiting for YOU to answer a handoff, and it GIVES UP in 12m if nobody does — that is a deadline for YOU, not a resume time.The 15 minutes is now one constant (
HANDOFF_GIVE_UP_MS) behind the announcement, the expirysentence and the published instant.
AC3 — the legends
RUN_HEALTH_LEGENDnow states what is true for every park reason: the note says what for and,when the end is knowable, when the wait runs out and what running out means — a usage-limit
park resumes then, a human handoff gives up then.
composition.tsrecords the same, and warnsagainst re-deriving a resume time from
waitingUntilin a caller.The
coding.ts:170asymmetry named in the issue is already gone: since #588 both tools publish thesame rendered
runHealthSentence().One residue, deliberately left:
workers/mcp/src/state-vocabulary.ts'swaitinggloss stillsays the note "gives a resume time only when one is knowable". That stays true — a resume
time is still given only for the parks that resume — but it no longer mentions that a park can also
state when it gives up. That file is owned by the in-flight #600/#603 lane and was off limits to
this change. The one-line replacement, for whoever gets it:
AC4 / AC5 — the guard, red demonstrated
run-health-readers.test.tswalksRUN_WAIT_REASONSat run time and asserts each reason'sdeadline is rendered with the verb matching its kind, that both kinds are actually exercised (a
table where every reason was a
resumewould otherwise satisfy half the rule and pass), and thatan unrecognised reason gets no deadline clause. Denominator printed per ADR 0002 G2.
The previous denominator was
Record<RunWaitReason, true>in a test file — inert:workers/api/tsconfig.jsonexcludessrc/**/*.test.tsand vitest transpiles without checking, soit compiled nowhere and could never have gone red. A fourth park reason would have walked straight
past the one guard whose job is to count them.
G4, on the pre-change tree (renderer and writer both reverted, three guards flip):
The third drives the real
resolvePause, so it fails on the production call site rather than on ahand-passed argument.
Verified / not verified
pnpm typecheck(11 projects), fullpnpm test(9,032), biome, all eightscripts/check-*.mjsgates incl.check-surface-lock --require-history,docs:drift, and CIgreen on
41cebd1. No MCP tool description changed (only comments), so no surface-lock bump.pnpm test:e2e— nothing understore/,agents/coder/webwas touched.not been read off a production row. It is pinned by the unit guards above, and the next handoff
will carry it.
#601
Unblocked.
waiting_untilnow has one meaning a reference page can state: the instant thispark's clock runs out, with the kind of ending entailed by
waiting_reasonand rendered fromwork-report.ts'sPARKS—engine_limitandplatform_interruptresume at it,humangives upat it. There is no second field to document and no case where the same value means two things.
serge-ivo commentedon Aug 15, 2026
Leaving this open for one line: the
state-vocabulary.tsgloss above (AC3's MCP half). Everything else — AC1, AC2, AC4, AC5 and the API-side legend — is onmainin41cebd1. That file belongs to the #600/#603 lane; the replacement sentence is in the comment above, and the claim it makes today is incomplete rather than false, so nothing is misreporting in the meantime.serge-ivo commentedon Aug 15, 2026
Live check (read-only, via the MCP-reading agent), for the record: nothing is parked on the estate right now — zero
activecoding sessions across all nine coding-capable instances, runner online (RLs-MacBook-Air.local, 0.4.51, last seen 12:41), so there is nowaitingrun whosewaitNotecould be read back from production. The closest live evidence is twoengine_limitparks that ran out today (boardsbd43f4de-…ande4d2d031-…, both "the coding CLI is still reporting its own usage limit… resets Aug 17 at 4pm") — closed runs, so theirwaitNoteis null by design (work-report.ts:if (run.status !== \"running\" || !run.waitingReason) return null).So the give-up sentence is pinned by the guards and by the deployed code path, and has not been observed on a live row. The next handoff will carry it; I did not start a run to manufacture one.
fix(docs): docs/ is swept, and the detector reads the two shapes that…
docs(runs): the run-lifecycle vocabulary is published, and its value …
serge-ivo commentedon Aug 18, 2026
Audit verdict: delivered — closing.
41cebd15+b4d6ea3b.lib/work-report.ts:214—PARKS, "every park reason, with what it waits for and which KIND of deadline it has (#596)". The kind is derived from the park reason, not stored as a second column, with the reasoning at:280and atagent-loop-store.ts:110: "the thing that renders it, so the verb can never be chosen by a reader that guessed"lib/coding-pause.ts:152— "The deadline is said OUT LOUD (#596 AC2)". Test:coding-pause.test.ts:194"tells the owner BY WHEN, because a handoff deadline they cannot see is one they cannot beat"composition.tsandRUN_HEALTH_LEGENDstate what is true for every parkwork-report.ts:129now reads "when the end is knowable … a usage-limit park RESUMES then, a human handoff GIVES UP then. Read the verb before you decide it needs nothing".workers/mcp/src/state-vocabulary.ts:185records the same correction on the MCP sidelib/run-health-readers.test.ts:213— "Every park's deadline is rendered with the verb matching its KIND (#596 AC4)";:287— "No legend promises a resume time for every park".RunWaitReasonis an array as well as a union specifically so the guard can count it (agent-loop-store.ts:57)work-report.test.ts:324— "a human handoff's deadline is a GIVE-UP, never 'expected to resume'"The issue's framing — "the field is correct today only because one of its two producers abstains" — is what the fix inverted: the human handoff now publishes its deadline, and the reason it is a different kind of deadline is carried in the data rather than in the abstention.