Skip to content

Carry out Operator Handoffs in the conformance harness (#383) - #386

Merged
lbacik merged 1 commit into
mainfrom
fix/issue-383-harness-handoff
Nov 10, 2026
Merged

lbacik merged 1 commit into
mainfrom
fix/issue-383-harness-handoff

Conversation

@lbacik

@lbacik lbacik commented Nov 10, 2026

Copy link
Copy Markdown
Owner

Closes #383 (narrowed scope: handoff + handoff-crash; the rest is split out to #385).

What changes

Harness. BlockingExecutor now has the same operator seams as the real executor (set_operator_handoff_provider, set_operator_handoff_reporter, operator_request_id). It picks up a pending handoff now at its next poll, reports delivered then begun (so a second handoff now is rejected), and ends the attempt with the handoff outcome and an operator_request Handoff note. The engine's own finalization decides completed or not_fulfilled.

Engine (lifecycle.py). Running that path against the real GitWorkspace exposed two bugs:

  • Every Operator Handoff ended not_fulfilled. After cleanup returned the tree to the base, the handoff rechecks re-read the note from the working tree and found nothing. The note is still validated on the attempt's tree before release. The post-cleanup rechecks now only apply the 360 s whole-command budget.
  • A crash mid-handoff ended completed after recovery. Contract spec §14 requires an interrupted handoff to be not_fulfilled, with partial work retained. Startup reconciliation now fails a still-accepted handoff of a non-finalized attempt. Recovery still finishes the attempt's recorded effects and finalizes it with its verdict. The already-finalized replay path is unchanged.

Other.

  • agent-operator-contract is pinned to v1.0.1. uv.lock is gitignored in this repo, so only pyproject.toml changes here.
  • CONTEXT.md gains the Operator Handoff glossary entry from the brief.

Tests

  • Harness executor tests: delivered → begun → handoff outcome with an operator_request note, a second handoff now rejected once begun, and no latch without a pending handoff.
  • An engine regression over the real GitWorkspace: the Operator Handoff completes after cleanup.
  • A lifecycle test: a handoff interrupted by a crash ends not_fulfilled, and recovery still finalizes the attempt.
  • Live harness tests: handoff now completes, and the crash → restart → recovery sequence ends not_fulfilled.

Verification

🤖 Generated with Claude Code

The harness executor now carries the real executor's operator seams: a
pending `handoff now` is picked up at the next poll, reported delivered
then begun, and the attempt ends with the `handoff` outcome and an
`operator_request` Handoff note. The engine's own finalization decides
`completed` or `not_fulfilled`.

Running that path against the real GitWorkspace exposed two engine bugs:

- The handoff rechecks after cleanup re-read the note from a working
  tree that cleanup had already returned to the base, so every Operator
  Handoff ended `not_fulfilled`. The note is validated on the attempt's
  tree before release; the post-cleanup rechecks now only apply the
  whole-command budget.
- After a crash mid-handoff, recovery completed the handoff. Per contract
  spec section 14 an interrupted handoff is `not_fulfilled`: startup
  reconciliation now fails a still-accepted handoff of a non-finalized
  attempt, and recovery still finalizes the attempt with its verdict.

Also pins agent-operator-contract to v1.0.1 and adds the Operator
Handoff glossary entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the bug Something isn't working label Nov 10, 2026
@lbacik
lbacik merged commit 6c335cb into main Nov 10, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Producer suite: sealing under hold, halted event, handoff acknowledgement and agent_unreachable cascade

1 participant