Skip to content

F: wire the runner and startup recovery into the server #91

Closed

Description

@mchwang

Split out of #87 (decision 4).

Why

No production code builds RunnerDeps. web/cli.ts calls startServer(config, port) without them, so RunnerCoordinator is null, and /api/runner answers "The runner is not available yet." recoverStartup (runner/recovery.ts) has no production caller either.

Needed

  • Build the execution deps from the real TaskWorkspace (F2b: connect the real workspace (partial export, failed-run audit, durable safety findings, commit export) #87) in web/cli.ts, under the database's runner token, with the single-runner lock (acquireRunnerLock).
  • Call recoverStartup at server start, before admission, with D's real recovery, export and removal:
    • recoverLeftovers.
    • exportTaskDiff, whose signature differs from RecoveryDeps.exportTaskDiff: D takes base, imageId and metadataBaseline, and returns { diff, truncated }.
    • removeTaskFilesystems.
  • Persist each allocation's metadataBaseline and base, so recovery can export a recovered storage.
  • Configure runnerRoot and diagnosticsDir.

Depends on #87.

🤖 Generated with Claude Code

Activity

  1. mchwang commented on Oct 1, 2026

    @mchwang
    ContributorAuthor

    Carried over from #87, whose comment thread raised these for when the runner is wired into the server:

    • One coordinator per process and Store. executionDeps.prepare refuses every kind but execute, and the coordinator uses finish for every kind whenever it is defined. Wiring the real runner needs deps that dispatch by attempt kind (execute and fix versus review and check), or one coordinator per kind.
    • Shutdown must await ItemExecutor runs. runner.close() awaits only the coordinator's own jobs. An executor's pause or escalation after the last item settles is a settlement write. So shutdown must await in-flight runTask calls before it closes the Store (step 7).
    • Resume a task stopped between items. If shutdown starts between items, runTask returns stopped with the next item not started. The task stays running and its latest attempt is completed, so startup recovery never requeues it. Recovery (or the I3 requeue) must also resume a running task whose plan has items left and no active attempt.

    🤖 Generated with Claude Code

  2. mchwang commented on Oct 1, 2026

    @mchwang
    ContributorAuthor

    Decisions (2026-10-01), after research found that nothing in production starts a task, there is no production agent launcher, and nothing clears requeue_pending:

    1. Scope. F: wire the runner and startup recovery into the server #91 covers the production deps, a real Claude launcher, startup recovery, and /api/runner actions start and resume. It adds no UI; buttons are a follow-up issue. It ships in two parts:
      • Part 1: deps that dispatch by attempt kind (execute only; other kinds are refused until their deps exist), a Claude launcher with a fixed execute schema.json, and recovery wired to D's real recoverLeftovers, exportTaskDiff and removeTaskFilesystemsAsync. Schema v9 stores each allocation's metadataBaseline and base. ItemExecutor runs are awaited at shutdown.
      • Part 2: start and resume. resume claims requeue_pending and continues from the first unfinished item; this also covers a task stopped between items.
    2. Opt-in. review.json gets an optional runner block with the root, diagnostics directory, committer and optional limits. Without that block, startup is unchanged and /api/runner reports that the runner is not configured. Demos never run the runner.
    3. Image. The agent image is built once at startup, after the Store opens and before recovery and listen.

    Defaults I will build to:

    • Codex is refused in every phase (D: decide how Codex reads code in review, execute and fix (shell is off in every phase) #93), so the runner uses Claude with CLAUDE_CODE_OAUTH_TOKEN.
    • RecoveryDeps changes to take the attempt row and return {diff, truncated}. Recovered storage must match the row's attempt ID and allocation ID. Diffs are saved through saveDiagnostic.
    • Every unowned resource blocks startup, and the message gives its reason.
    • lock.verify() moves to just after the Store opens, before recovery and admission.

    🤖 Generated with Claude Code

  3. mchwang commented on Oct 2, 2026

    @mchwang
    ContributorAuthor

    Part 2 decision (2026-10-02): start accepts a task that is in review or queued. In the same user action it moves an in review task to queued and admits the first plan item. It is refused if any item of the current plan revision has already completed; that case is resume.

    🤖 Generated with Claude Code

  4. mchwang commented on Oct 3, 2026

    @mchwang
    ContributorAuthor

    Done: part 1 (production runner wiring and startup recovery) merged in #102 (80ac5f2), part 2 (/api/runner start and resume) merged in #105 (cce650b). Open design questions from the reviews moved to #107. Buttons in the review screen are not built yet.

    🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions