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#
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: cancelConfiguration#
| 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:
on:
approve: process
reject: cancelon:
approve: process
hold: notify
deny: doneResuming 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#
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: successCommon pitfalls#
- Empty
onmap. Schema validation rejects ahumannode 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/falseas booleans. Quote them ('yes': ...) or pick non-boolean names (approve/reject). - MCP agents resuming themselves. An autonomous tool that calls
resume_rundefeats the purpose of the pause. Make ahumanstep mean "stop and wait for the user" in every entrypoint, including MCP. - Long-lived prompts that lose context. A
humanstep might be resumed hours later. Put any context the operator needs into thepromptfield, since they may not have the upstream node output handy. - Confusing
humanwithai_judge. If you want a model to make the call, use ai_judge.humanis for actual people.