Skip to content

Mid-run human approval gates as a first-class workflow primitive #641

Description

@fanhongy

Parent: #583

Context

The workflow I authored needed four human decision points inside one run — approve the extracted claims, approve the adjudication, approve the fix plan, approve the commit — each one a place where a person reads an artifact and the work must not continue without them.

CAO has no primitive for this, so I hand-built it:

  • marker files under state/gates/<n>-<name>.approved;
  • a finish() helper that writes a human-readable NEXT-STEP.md (what was produced, what to read, the exact command to resume) and then exits;
  • SystemExit(0) for the exit — because a non-zero exit reports the run as failed, which misrepresents a deliberate, successful stop as a fault. That distinction cost a round to discover and is invisible in the artifact.

It worked well enough that I would keep the shape. But every author will rebuild it, every author will get the exit-status detail wrong once, and an operator reading cao workflow status cannot tell "stopped for a human" from "crashed".

Gap vs #583

The roadmap's approval is pre-run: resolve the plan, present it, user approves, then "run and observe". The target-experience diagram has exactly one approval, before execution.

That is the right model for a short deterministic job, and insufficient for the long-running staged workflows the roadmap explicitly wants to validate against ("a staged product-build workflow", "the existing PR review workflow"). A PR-review workflow that cannot stop for a human between adjudication and fixing is not the workflow anyone wants — the whole point is that a person adjudicates.

Proposal

Make the mid-run gate a primitive:

gate("plan-approved",
     summary="7 fix clusters across 3 waves",
     artifacts=["state/artifacts/plan.md"])   # blocks until approved
  • A run parked at a gate has its own terminal state — awaiting_approval, distinct from both completed and failed — surfaced in status, results, and the MCP tools.
  • CAO renders the operator view: which gate, what to read, the exact resume command. (This is what I hand-wrote as NEXT-STEP.md; the runtime knows all of it.)
  • cao workflow approve <run_id> <gate> / reject <run_id> <gate> [--reason], with the decision, actor, and timestamp recorded in the journal so an approval is auditable evidence rather than the presence of a file on someone's laptop.
  • Resume from a gate reuses journaled work for everything before it (depends on Milestone 0).
  • A rejection is a first-class outcome, not a crash.

Acceptance criteria

  • A script can declare a mid-run gate with a summary and the artifacts to review.
  • A run stopped at a gate reports awaiting_approval, never failed.
  • status names the pending gate, the artifacts to read, and the resume command.
  • Approval and rejection are explicit commands recorded in the journal with actor and timestamp.
  • Resuming after approval does not re-execute work completed before the gate.
  • A rejected gate terminates the run with a distinct, non-fault outcome.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions