# human node

Pauses the run and waits for an external party to choose one of the
configured transitions. In the web UI the transitions render as buttons; via
MCP and the REST API they appear as a `pendingStep` that an operator (or
another tool) resolves explicitly.

## Purpose

Use `human` whenever the next step requires judgement, sign-off, or context
that isn't available to a script or a model: release approvals, sensitive
deletes, manual data review, "did the email go out?" gates. The run state
is durable: paused workflows survive process restarts and can be resumed
later from any client.

## YAML schema

```yaml
states:
  approve:
    type: human # discriminator
    label: Approve refund over $500 # required, shown as the pause's heading
    prompt: | # optional, free text shown to the human
      Review the refund request and approve or reject it.
    on: # required — at least one transition (schema-enforced)
      approve: process
      reject: cancel
```

## Configuration

| Field    | Type           | Required | Default | Meaning                                                           |
| -------- | -------------- | -------- | ------- | ----------------------------------------------------------------- |
| `type`   | `'human'`      | yes      | —       | Discriminator.                                                    |
| `label`  | string         | yes      | —       | Heading shown in the pause UI / pendingStep payload.              |
| `prompt` | string         | no       | —       | Optional context shown to the human. Surfaced as `output.prompt`. |
| `on`     | transition map | yes      | —       | At least one key (schema-enforced); each becomes an option.       |

There is no `validator`: routing is decided by whichever transition the
human picks. There is no machine-side timeout; a paused run sits in the
`paused` state indefinitely until something resumes it.

## Outputs

`human` records the prompt that was shown but does not generate any
machine-derived output:

| Key      | Type           | Notes                                       |
| -------- | -------------- | ------------------------------------------- |
| `prompt` | string \| null | The configured `prompt`, or `null` if none. |

Downstream nodes typically don't read from a `human` node directly: the
information they need came from earlier nodes. The transition itself is
the signal.

## Transitions

The transition keys are entirely user-defined. Whatever appears in `on`
becomes a button in the web UI and a valid resume token everywhere else.
Common patterns:

```yaml
on:
  approve: process
  reject: cancel
```

```yaml
on:
  approve: process
  hold: notify
  deny: done
```

## Resuming a paused run

Any of the following resume a paused `human` step with a chosen transition:

| Client | How                                                            |
| ------ | -------------------------------------------------------------- |
| Web UI | Click the transition button on the run's pause card.           |
| CLI    | `roscoe resume <runId> <transition>`                           |
| REST   | `POST /api/runs/:id/resume` with `{ "transition": "approve" }` |
| MCP    | The `resume_run` tool.                                         |

Claude Code, or any autonomous agent, should not call `resume_run` on its
own initiative. The entire point of a `human` step is that a person made
the choice, so wait for explicit user input before picking a transition.

## Worked example

```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
```

## Common pitfalls

- **Empty `on` map.** Schema validation rejects a `human` node with no
  transitions. There would be no way to ever resume it. At minimum, give it
  one option.
- **Transition keys that look like booleans without quoting.** YAML parses
  unquoted `yes` / `no` / `true` / `false` as booleans. Quote them
  (`'yes': ...`) or pick non-boolean names (`approve` / `reject`).
- **MCP agents resuming themselves.** An autonomous tool that calls
  `resume_run` defeats the purpose of the pause. Make a `human` step mean
  "stop and wait for the user" in every entrypoint, including MCP.
- **Long-lived prompts that lose context.** A `human` step might be
  resumed hours later. Put any context the operator needs into the
  `prompt` field, since they may not have the upstream node output handy.
- **Confusing `human` with `ai_judge`.** If you want a model to make the
  call, use [ai_judge](/docs/nodes/ai-judge). `human` is for actual
  people.
