feat(#10): Wave A — human_gate node kind + await parking - #120
Conversation
The third node kind (ADR 0010): a human_gate parks the Run for approval. Its only input is an optional prompt for the TTY confirmation; it carries none of the shell/agent subprocess fields and emits no `when`-producible field, so any `when` ref to a gate is a config error. Registered in the kind->inputs and kind->producible maps and the node-kind Literal. Slice 1 of #10 (Wave A). Parking behavior follows.
A human_gate node now parks the run at the scheduler fixpoint instead of executing: the gate is recorded `awaiting`, the run `parked` (no finished_at and no run_finished -- it is not finished), downstream stays unreached, and a gate_awaiting event marks the park. `caw run` reports the parked run and its awaiting gate and exits 0 (a park is not a failure) in a non-TTY session. Extends the #30 vocabulary owners with the gate statuses (parked/awaiting/ rejected) and events (gate_*). RunResult carries awaiting_node_ids and reports `parked`; a human_gate reaching the node dispatch is a guarded invariant breach. Slice 2 of #10 (Wave A). Approval/rejection follow in Wave B.
3aebbea to
092e2e5
Compare
$review: Standards and Spec ReviewReviewed PR #120 against Standards
Spec
Verification run:
Summary: 3 Standards findings, 2 Spec findings. Worst issue: the PR introduces Human Gate status/control vocabulary but leaves resume eligibility and gate shape validation inconsistent with ADR 0010. |
code-review-and-quality reviewFindings first:
What looks good:
Validation:
Overall: request changes. |
…eligibility with ADR 0010 (#10 review) Addresses the #120 review (two reviewers, three converging findings): - Reject `retries`/`timeout` on a human_gate node. ADR 0010 says the subprocess-shaped fields do not apply (a gate runs no process); the inputs-level ones were already forbidden, but the node-level `retries`/`timeout` still validated and were silently ignored. They are now a config error. - A parked RunResult is no longer `succeeded`. `succeeded` returned True whenever the run-down nodes succeeded, even when parked -- risking a Pattern Controller that branches on `result.succeeded` misclassifying a parked iteration as a finished success. A parked run is now `succeeded == False` (status stays `parked`). - `rejected` is non-resumable. The status was added to the vocabulary but `_NON_RESUMABLE_RUN_STATUSES` still held only `succeeded`; ADR 0010 and CONTEXT.md refuse a rejected run alongside succeeded. `parked` stays resumable. Tests: a gate with retries/timeout is a config error; a parked run reports succeeded=False; is_resumable refuses rejected/succeeded and admits parked/failed.
|
Thanks — all three Standards/Spec findings were right and are fixed in 1721612 (TDD: a failing test each, then the fix). 1. 2. Parked run reported 3. New tests: a gate with Gates after the fix: |
First of three sequential waves implementing the Human Gate (#10), per ADR 0010. This wave makes a run park at a gate; approval/rejection (Wave B) and multi-gate/TTY/e2e (Wave C) follow.
What's in this wave
Slice 1 —
human_gatenode kind (feat(model))HumanGateNodeInputswith an optionalprompt, registered in the kind→inputs and kind→producible maps and the node-kind Literal.when-producible field, so anywhenref to a gate is a config error (ADR 0010:approvedis not a branch source in v0.1).Slice 2 — await parking (non-TTY) (
feat(executor))awaiting) instead of executing: it consumes no concurrency slot and launches no task.awaiting, the runparked(nofinished_at, norun_finished— a parked run is not finished), downstream stays unreached, and agate_awaitingevent marks the park.caw runreports the parked run + awaiting gate and exits 0 (a park is not a failure) in a non-TTY session.parked/awaiting/rejected(status.py) and eventsgate_awaiting/gate_approved/gate_rejected(events.py).rejectedand the approve/reject events are defined here but consumed in Wave B.RunResultcarriesawaiting_node_idsand reportsparked; a human_gate reaching the node dispatch is a guarded invariant breach.Tests / gates
gate_awaitingevent, norun_finished); CLI-seam parked-exit (exit 0 + "parked", not "succeeded").ruff+mypy --strictclean (60 files), 489 non-e2e pass, and a real-agent e2e sanity (CAW_E2E_AGENT=claude: agent graph run + CLI) 6 pass — the central scheduler change does not regress real runs.Acceptance criteria progressed (#10)
human_gateparks at the fixpoint: nodeawaiting, runparked, State persisted, process exits cleanly in a non-TTY sessiongate_awaitingEvent🤖 Generated with Claude Code