Skip to content

F1a: persist the runner lifecycle in the Store - #53

Draft
mchwang wants to merge 2 commits into
mainfrom
feat/f1-store-lifecycle
Draft

mchwang wants to merge 2 commits into
mainfrom
feat/f1-store-lifecycle

Conversation

@mchwang

@mchwang mchwang commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Lane F, step F1, slice F1a: the Store. The F1 contract (#49) is merged; this PR now targets main. Related: #22, #51.

What this does

Adds the durable half of the F1 runner lifecycle contract (docs/implementation/runner-lifecycle.md). There is no coordinator, HTTP or shutdown wiring yet; those are later slices.

  • Schema v6 and backfill. New tables tasks, attempts, user_actions and feedback_events. Every v5 plan gets one task row with the documented defaults. A merged plan becomes merged and gets its task-closed event. A null budget means "not started", never expired.
  • Guarded attempt transitions. Admission (including retry) checks the task status, the state version, the requeue claim, a pending cancel, an active attempt and the captured context in one transaction. recordFirstReason is the same-state "Stopping" transition, and markRunning is refused once a stop reason is recorded.
  • The Store chooses the terminal state. settleAttempt takes D's result and the in-memory first reason, and applies the precedence in runner/lifecycle.ts (classifySettlement), so the precedence lives in one place:
    1. the saved or in-memory first reason (a D stop that came before shutdown wins);
    2. then a changed context, which gives stale;
    3. then D's own stop reason;
    4. then the exit status and validation.
  • Two counters. Every plan-revision, snapshot or assignment change increases both the context generation and the state version in the same transaction (#savePlan, #snapshot, setAssignment). Lifecycle changes increase only the state version.
  • Task closure.
    • cancelTask closes at once, or, with an active attempt, stops it first and closes when it settles. A pending cancel beats the time limit.
    • Cancel is refused while a merge is being submitted or queued.
    • finishMergeAttempt(merged) now closes the task and writes task-closed in the same transaction.
  • User actions. userAction gives exact replay for a UUID-v4 action ID: the same request returns the saved response without re-applying it. A different request with the same ID is refused. Guard refusals are recorded and replayed; storage errors are not.
  • Feedback events. recordFeedback works only inside a user action, and supersedes by source. feedbackEvents is lane J's read path and is available once the task closes.
  • Nested transactions. #transaction now joins an outer transaction, so a user action can wrap existing Store methods atomically.

Contract change found while implementing

The running → failed guard in the contract said "no first reason". That contradicted the round-19 rule that a D stop before shutdown ends failed. I fixed the row on #49 (e44d04a).

Validation (head 4c83e78, rebased onto main after #49 merged)

  • npm run typecheck: passes.
  • CI's unit set (npm test minus the Docker suites): 470 passed, 0 failed. 26 of those tests are new, in test/runner-lifecycle-store.test.ts. The 26th test, from contract round 35, checks that a result never completes into a task that isn't running, and fails without the guard.
  • Mutation check: six guards were broken one at a time, and each was caught by at least one test:
    • markRunning ignoring the first reason;
    • admission skipping the context check;
    • a pending cancel not winning;
    • refusals not recorded;
    • shutdown ignoring D's reason;
    • no context bump on a snapshot change.
  • npm run test:browser: 47 passed in a full run.
  • Flaky browser tests already on main: three review.spec.ts tests are timing-sensitive ("preserves edits made while a question submission is in flight" ×2, and "drains an in-flight question request…"). Run alone three times each, they failed 2 of 12 with this change and 3 of 12 without it. So this PR doesn't cause them; I'm flagging them separately.
  • The Docker suites (agent-*) weren't run locally; this PR doesn't touch agents/.

Not in this slice

F1b (coordinator), F1c (shutdown wiring and /api/runner), F1d (startup recovery and the OS lock), F1e (planning endpoints and wiring feedback into review actions). The F1 implementation must not merge as a whole until #51's pre-F1 D items land.

🤖 Generated with Claude Code

mchwang and others added 2 commits September 26, 2026 01:19
Schema v6 with a v5 backfill: tasks, attempts, user_actions and
feedback_events. Guarded attempt transitions, where the Store chooses
the terminal state from the first reason, D's result and context
currency. State version and context generation counters, cancel task,
merge closure, replayable user actions (including refusals) and
feedback events.

Implements the Store slice of docs/implementation/runner-lifecycle.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mchwang
mchwang force-pushed the feat/f1-store-lifecycle branch from 3ce52eb to 4c83e78 Compare September 26, 2026 08:21
@mchwang
mchwang changed the base branch from docs/f1-runner-lifecycle-contract to main September 26, 2026 08:21
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