vein: run control — cancel, pause/resume, durable resume (RUN_CONTROL_SPEC) - #1615
Merged
Conversation
…_SPEC rungs 1-3)
Engine + server implementation of RUN_CONTROL_SPEC.md:
- RunController (src/run-control.ts): cooperative checkpoint at every
boundary (DAG steps, loop/foreach iterations, retry attempts, agent
tool loop), tree linkage for nested runs, quiesce accounting via
unit-scoped control views.
- Cancel: CancelledError is a distinct outcome — run.cancelled event,
status "cancelled", never the error path (no retry/onError), teardown
fires, partial outputs stay journaled. executeFlow now settles every
in-flight branch before finalizing (no events after the terminal).
- Pause/resume (in-memory): parks every active branch at its next
boundary; run.paused/run.resumed markers record the gap; runs listing
reports paused; agent step checkpoints between tool calls
(prepareStep).
- Durable resume (journal replay): step.end outputs keyed by path replay
as step.replayed (zero cost); first missing path executes live.
run.start records workflowHash + params for the validity guard and
faithful re-execution; failed/cancelled/stale runs resume, successful
runs need from=<stepPath> (forced invalidation incl. transitive
dependents and sequential-loop tails); torn JSONL tails skipped;
run.resumed reopens SSE tails past an old terminal event.
- Endpoints: POST /workflows/:name/runs/:runId/{cancel,pause,resume}.
- Tree linkage threaded through every launch site: launchDetached,
vein.run (parentRunId), authoring runWorkflow (meta/run-workflow
passes ctx.runId), chat run_workflow (AiDeps.trackRun).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…re flyout - Topbar: Cancel (confirm dialog states the subtree consequence) + Pause on a live run; Resume on a paused run and on dead runs (stale / error / cancelled — the stale badge finally has a purpose: it marks resumables). - Step flyout: "Re-run from here" on non-live runs — durable resume with from=<true event path> (drill-aware). - runEpoch re-tails the SSE stream after a durable resume (the previous tail closed at the old terminal event). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- OptimizerCapability.run accepts parentRunId; evolve-loop and eval/optimize pass ctx.runId so generation/eval/reflect runs attach under the launching run's controller — cancelling or pausing an evolve run now reaches every nested run (RUN_CONTROL_SPEC §2.2). - Both loops checkpoint between generations (ctx.control, §2.1 code-step opt-in). - harvey/evolve-loop consumes ctx.journal on durable resume: journaled generations replay (state — best/sinceImprove/stop logic — is rebuilt from their synthetic #gen outputs, which now also carry the approach summary + digest excerpt so later briefings stay faithful); the loop continues from the first missing generation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…in AGENTS.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements all three rungs of
vein/RUN_CONTROL_SPEC.md— cancel, pause/resume, and durable resume for run trees, including the nested trees the lab produces (harvey-evolve → optimizer.run → harvey-evolve-gen → …).Rung 1 — Cancel
RunController(src/run-control.ts): cooperativecheckpoint()awaited at every boundary — between DAG steps, loop/foreach iterations, retry attempts. Registered at every launch site (launchDetached,vein.run, authoring'srunWorkflow, chat'srun_workflow), superseding the oldactiveRunsset.parentRunId(meta/run-workflowand the lab optimizer passctx.runId), so cancelling a parent cancels its whole subtree via the effective-state walk.CancelledErroris a distinct outcome:run.cancelledterminal event,status: "cancelled", never retried or diverted intoonError,onRunEndteardown still fires, partial outputs stay journaled.executeFlownow settles every in-flight branch before finalizing, so no step events land after the terminal event.POST /workflows/:name/runs/:runId/cancel(404 unknown, 409 terminal) + a topbar Cancel button with a subtree-consequence confirm.Rung 2 — Pause / resume (in-memory)
pause()parks every active branch at its next boundary; quiescence is tracked via unit accounting (a parkedctx.control.checkpoint()releases its unit, so a paused agent counts as quiesced).run.paused/run.resumedmarkers record the gap in the log; the runs listing reportspaused.prepareStep) — the highest-value boundary in the lab's workloads. evolve-loop and eval/optimize checkpoint between generations viactx.control.Rung 3 — Durable resume (journal replay)
src/journal.ts:step.endoutputs keyed by path (last write wins) become the replay journal;runWorkflow({ journal, resume: true })replays journaled paths asstep.replayed(zero cost, no side effects) and executes live from the first missing path. Skip/gate logic reconstructs naturally; foreach re-runs only the failed#i; loop replays iterations and re-evaluatesuntilagainst replayed$current.run.startrecords the workflow content hash (resume refuses a changed DAG unless forced) plus per-run params/paramOverrides so re-execution uses the original knobs;run.resumedreopens SSE tails past an old terminal event.frominvalidation:error/cancelled/stale runs resume as-is; a successful run needsfrom: <stepPath>— the target, its transitive dependents (computed per flow level, recursing through statically-named subflows), its iteration children, and later iterations of an enclosingloopare dropped from the journal.POST …/resumeis dual-purpose: live controller → releases a pause; dead run → journal replay. UI: Resume on stale/error/cancelled runs, "Re-run from here" in the step flyout (drill-aware true paths).harvey/evolve-loopconsumesctx.journal: journaled generations replay with loop state (best / sinceImprove / stop logic) rebuilt from their synthetic#genoutputs, which now carry the approach summary + digest excerpt so later briefings stay faithful.Tests
36 new tests in
src/run-control.test.ts(controller semantics, cancel/pause mid-DAG/mid-foreach/mid-retry, replay correctness incl. skip/gate reconstruction andctx.journal, torn tail, tail reopening, and full HTTP round-trips of cancel/pause/resume + the hash guard + re-run-from-here). Full suite: 533/533; vein, web, and mcp all typecheck.🤖 Generated with Claude Code