# Human approval flow

Use this when you want a deterministic automated pipeline to stop, ask a
real person to confirm, and only continue if they say yes. It's the canonical
shape for refund approvals over a threshold, expense sign-off, release
publishing, and any "draft, review, ship" loop where the wrong answer is
expensive.

## The workflow

A `human` node sits between two `script` nodes. The first script step
prepares some output, the human reviews it, and the second script step only
runs if they approve.

```yaml
name: refund-approval
version: 1
description: Demonstrates a human approval pause/resume between two script steps.
initial: prepare
states:
  prepare:
    type: script
    label: Draft the refund decision
    script: |
      console.log('Refund request is 3 days past the 30-day window, amount $640');
      export default true;
    validator:
      kind: boolean
    on:
      'true': review
      'false': done

  review:
    type: human
    label: Approve refund over $500
    prompt: Review the refund request and approve or reject it.
    on:
      approve: process
      reject: done

  process:
    type: script
    label: Process refund
    script: |
      console.log('Refund processed');
      export default true;
    validator:
      kind: boolean
    on:
      'true': done
      'false': done

  done:
    type: end
    label: Done
    outcome: success
```

Save it as `refund-approval.workflow.yaml` in `~/.roscoe/workflows/`. It's the
same shape as the `approval` starter that ships with `roscoe init`
(script → human → script → end), just relabeled for a refund scenario.

## Running it

From the CLI:

```bash
roscoe run refund-approval
```

The command prints a run id and exits. The run is now paused at `review`.
Resume it with the chosen transition:

```bash
roscoe resume <run-id> approve   # → process step runs
roscoe resume <run-id> reject    # → straight to done
```

From the web UI, the run page surfaces two buttons (`approve` and `reject`)
when execution reaches the `human` node. From Claude Code via MCP, the run
pauses with a `pendingStep` describing the prompt and valid transitions.
Claude is required to display these to the user and wait for their reply
before calling `resume_run`.

## Why these node types

`script` with `validator: kind: boolean` routes on the value the script
returns: `'true'` when truthy, `'false'` when falsy. This is the simplest
gate; any step that can fail in a way you want to surface should be a
boolean script. Notice the on-keys are quoted strings: YAML would otherwise
parse `true:` as a real boolean key. `human` pauses the run and waits for a
transition key; the keys in `on` are arbitrary, so pick verbs that read well
as buttons (`approve`, `reject`, `defer`, `escalate`). `end` with
`outcome: success` is the terminal node that sets the recorded run outcome;
use `failure` for the rejected branch if you want the run marked red in
history.

## Variations

If a rejection should count as failure, split the terminal into two `end`
nodes, one `outcome: success` and one `outcome: failure`, and wire
`reject: rejected-end` so the run is recorded as a failure. For a three-way
decision, add a `defer` transition pointing back to `prepare` (or to a
separate `requeue` step); the web UI renders one button per `on` key
automatically. To show the draft to the human, reference
`{{ prepare.stdout }}` in the human prompt (the `prepare` step already
writes its output there) so reviewers see what they're approving.

## See also

- `/docs/nodes/script` — the value it returns, validators, environment passthrough
- `/docs/recipes/conditional-branching` — a non-human alternative when the
  decision can be expressed as a rule
