Skip to content

F1b: runner coordinator for attempts, slots and shutdown - #56

Draft
mchwang wants to merge 1 commit into
feat/f1-store-lifecyclefrom
feat/f1b-coordinator
Draft

mchwang wants to merge 1 commit into
feat/f1-store-lifecyclefrom
feat/f1b-coordinator

Conversation

@mchwang

@mchwang mchwang commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

Lane F, step F1, slice F1b: runner coordinator. Stacked on #53 (F1a, the Store); merge #53 first. Related: #22, #51.

What this does

Adds runner/coordinator.ts, the per-process owner of in-memory jobs, slots and unresolved markers, as specified in docs/implementation/runner-lifecycle.md. D is injected (RunnerDeps.start), so this runs against a fake D until #51 lands; nothing is wired to the server yet.

  • Admission in two steps. In one synchronous turn, the coordinator checks it is open, that the task has no job or marker, and that a slot is free, then reserves the slot. Then Store.admitAttempt runs. A refused transaction releases the reservation in the same turn. Writable kinds share one slot and read-only kinds share another; both limits are configurable.
  • Preparation. Host-side preparation gets an AbortSignal. The task budget timer records time-limit. The attempt deadline, before launch, fails the attempt with "Timed out" and records no first reason.
  • Launch check. In one synchronous turn: the row is still pending, there is no first reason (saved or in memory), there is time left and the context is current. Then captureInvocation, then D's start. A context change during preparation ends stale without calling D. A stop during launch ends from its first reason. A start error ends failed with the launch error.
  • Running. If markRunning is refused because a first reason exists, the handle is cancelled and settles normally. If the write fails with a storage error, the handle is cancelled with capture-failure, settled, and the slot stays held under a start-not-saved marker.
  • Stops. The first reason wins. It is kept in memory and saved when possible; status().stopRequested.saved shows when saving failed. The slot is never freed on cancel, only after settled.
  • Settlement. A clean result is validated, then the Store chooses the terminal state from the first reason, D's result and context currency. A failed terminal write leaves a result-not-saved marker that holds the slot until restart.
  • Cancel task and shutdown. cancelTask goes through the Store and stops the running work. close() rejects admission, records shutdown only where no reason exists, and awaits every job with no timer. It leaves the Store open for the caller to close.

Validation (head 3767065)

  • npm run typecheck: passes.

  • CI's unit set (npm test minus the Docker suites): 488 passed, 0 failed. 18 of those tests are new, in test/runner-coordinator.test.ts, using a fake D whose promises the test controls, and real SQLite.

  • Mutation check: six guards were broken one at a time, and each was caught:

    • a stop freeing the slot early;
    • the launch check skipping the context comparison;
    • no marker after a failed terminal write;
    • close() not waiting;
    • a later reason overwriting the first;
    • the reservation kept after a Store refusal.
  • AGENTS.md race regressions covered here:

    • lease expiry or a clock jump, then a retry while the original still runs;
    • a timeout, then the provider stays unsettled, then a retry;
    • shutdown, then a new request;
    • an abort, then the subprocess settles later;
    • a context change before launch.

    The Store-level cases (an old attempt settling after a retry, two processes admitting at once) are in F1a.

  • Browser tests weren't rerun: this slice doesn't touch web/.

Not in this slice

  • F1c: the Store write gate, coordinator barriers and server wiring, /api/runner, and the merge-coordinator catches.
  • F1d: startup recovery, the requeue claim and the OS lock.
  • F1e: the planning endpoints, and feedback wiring into review actions.
  • Task storage. Removing it after the terminal write is F2 (writable attempts).
  • Recorded preparation process group. Saving preparation_pgid is part of F1d, together with the abortable D helpers from D follow-ups required by the F1 runner lifecycle contract #51.

🤖 Generated with Claude Code

In-memory jobs with two-step admission and a slot reservation;
abortable preparation with task-budget and deadline timers; a
synchronous launch check before D's start; first-reason stops that keep
the slot until D settles; Store-chosen terminal states; unresolved markers
on storage failure; cancel task; and shutdown that keeps existing reasons
and waits for every job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mchwang added a commit that referenced this pull request Sep 26, 2026
Merge main. Record the F1 lifecycle contract (#49) and open F1a-F1c
(#53, #56, #57); record the ranked Issues screen (H4a, #55) with H4b's
trust action remaining; Issues is now a menu link, not a placeholder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mchwang added a commit that referenced this pull request Sep 26, 2026
* docs: reconcile the design plan with the code

Record where the code differs from the approved plan and update stale status:

- Record Ask as an interim exception to R1: it runs the vendor CLI on the
  host with tools off until lane F moves it into the lane D container.
- Amend D20: there is no "Merge anyway"; to override a blocker, merge on
  GitHub. Matches docs/implementation/guarded-merge.md.
- Tick T1, T2, T4, T5, T10, T13, T14 with test evidence; point Files lines
  at core/linking.ts and core/approvals.ts instead of never-created modules.
- Mark increment 1 merged; add a lane status table (C, D, K done; E, F, H
  progress); record decided open questions (issue ranking, AgentDiff).
- Add a verified status note for design tasks DT2-DT15; none newly ticked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: note merge-queue support in the guarded merge doc

The guarded merge gate doc still said merge-queue branches stay blocked.
#46 (closing #24) added queue lifecycle support. Point to merge-queue.md,
and state that adapters without queue inspection still fail closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: bring plan status up to date with F1 and H4a

Merge main. Record the F1 lifecycle contract (#49) and open F1a-F1c
(#53, #56, #57); record the ranked Issues screen (H4a, #55) with H4b's
trust action remaining; Issues is now a menu link, not a placeholder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: add F1d and Ask PRs to lane status

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Align merge-queue wording with the merged K2/K3 support

README no longer says merge queues block merging; it describes the
enqueue-then-confirm behaviour. The plan's wave-3 note records the old
block as history instead of a live instruction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record the K-lane queue block as history in the task table

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* README: disclose that Ask runs the agent CLI on the host

The plan (R1 exception) says README states this limit; it did not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add F1e (#60) and the #51 merge condition to the F lane row

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* README: distinguish queue-removal retry from changed-head review

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Plan: mark the install preflight and npx entry as planned

The CLI checks only the Node version today; say so instead of describing
the git/gh/container/sign-in preflight as current.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant