Skip to content

Implement structured run events and cooperative cancellation (#29) - #59

Draft
4nass wants to merge 3 commits into
mainfrom
feature/structured-events-cancellation
Draft

Implement structured run events and cooperative cancellation (#29)#59
4nass wants to merge 3 commits into
mainfrom
feature/structured-events-cancellation

Conversation

@4nass

@4nass 4nass commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Delivered

  • Versioned durable job events with cursor pagination and resume.
  • Lifecycle, context, provider, stage, test, review, approval, completion and failure events.
  • Idempotent cancellation for queued/running jobs; late provider output is discarded.
  • Cooperative watcher, process-group termination, and worktree cleanup.
  • Stable event/cancellation documentation.

Validation

  • 790 tests passed, 2 existing collection warnings.
  • Targeted jobs/worker/CLI tests: 122 passed.

Closes #29

@4nass

4nass commented Aug 15, 2026

Copy link
Copy Markdown
Owner Author

Status

Delivered: durable versioned lifecycle events, cursor replay, full lifecycle coverage, idempotent cancellation, process-group termination and cleanup.

Residual: authenticated REST/SSE consumption and provider-API interruption details remain in #47 and provider adapters.

Closes #29 is present in the PR description.

4nass and others added 2 commits August 17, 2026 20:33
…ng a stop

Three defects in the cancellation half of #29. The event stream half was
fine; none of these were visible to tests that only checked the job row.

The request was recorded as a failure of the work it cancelled.
CancellationRequested subclassed RuntimeError, so the broad handlers that
turn a stage's problems into a failed StageResult swallowed it: cancelling
mid-stage produced "backend failed: CancellationRequested" and the DAG
carried on to the next stage. It is a BaseException now, for the same
reason KeyboardInterrupt and asyncio.CancelledError are — an instruction
to unwind is not an error some layer handles and continues past.

Worktrees survived. run() has no try/finally, and the only cleanup was a
block placed after the executor, so a cancellation raised from verify,
review or a correction attempt walked straight past it and left the
integration worktree, its task worktrees and the branch on disk under a
job reporting itself stopped. Cleanup now lives with the `with` block that
already owns the run's target-level state, so it fires wherever the unwind
starts, and a cancelled stage takes its own worktree with it.

The row said `cancelled` while an agent was still holding a subprocess.
That is the same lie this queue refused to tell when it declined to cancel
a running job at all. A running job is now moved to `cancelling` by
whoever asks, and only the worker that actually stopped writes
`cancelled`; a run that finished before it noticed resolves to the outcome
it really reached.

Both regression tests fail on the previous code — one with DID NOT RAISE,
the other naming the worktree that survived.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eady does

The contract's stable state vocabulary listed `cancel_requested` before an
implementation existed; the state added with the cancellation fix should
carry that name rather than invent a second one for the same thing.

Co-Authored-By: Claude Opus 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.

Emit structured progress events and support cooperative cancellation

1 participant