Human in the Loop
The Human in the Loop block pauses workflow execution at that exact point and waits for a person to review, edit, or approve before anything downstream runs. Unlike a simple timer, the pause is a real, durable checkpoint: the entire execution state is snapshotted and persisted, so the workflow can sit paused for minutes, hours, or days and pick up exactly where it left off the moment someone responds.
This is different from the Wait block. Wait sleeps for a fixed, bounded duration and then continues on its own. Human in the Loop pauses indefinitely — there is no timeout — and only continues when a human explicitly submits a response through the resume UI or resume API.
Overview
The Human in the Loop block enables you to:
Pause and persist: Snapshot the full execution state to the database the moment the block runs, so the pause survives redeploys and restarts
Notify approvers: Fire configured tools (Slack, Email, etc.) to alert whoever needs to act
Present reviewable data: Show a structured "Paused Output" — the same builder used by the Response block — so the approver has context
Resume from checkpoint: Continue the exact same execution once a human submits their input, with that input merged into the block's output
How It Works
- Execution reaches the block. Instead of finishing and moving on, the workflow engine
writes a row to the paused-executions store containing the full execution snapshot (workflow
graph, block states, variables) and marks the execution's log status
pending. - Notifications fire. Any tools configured in Notification run immediately, typically to post a message or send an email that includes the resume link.
- A human responds. The approver opens the Resume UI at
/resume/[workflowId]/[executionId](the block'surloutput) or calls the resume API directly, submitting values that match the Resume Input schema you defined. - The resume is validated and either started or queued. If no other resume is currently running for this paused execution, it starts immediately. If one already is, the new submission is queued in FIFO order — resumes for the same paused execution are serialized one at a time, never run concurrently.
- The engine rehydrates the snapshot and marks the Human in the Loop block as executed. Its output is now the original Paused Output merged with the human's submission (see What's Available After Resume below).
- Downstream blocks unlock. The edges leading out of the block become eligible to run, and execution continues through the rest of the graph — including back into a Loop or Parallel scope if that's where the pause happened.
- The execution completes once every pause point is resumed. A single execution can contain
more than one Human in the Loop block; each has its own independent pause point. The parent
execution's status moves from
paused→partially_resumed→fully_resumedas each one is answered.
The resumed run keeps the same execution ID as the original run — it isn't a new, disconnected execution. Your logs and execution history show one continuous timeline that simply has a gap while it was paused.
Configuration Options
Paused Output
Defines the structured data shown to the approver while the workflow is paused — the same
builder interface as the Response block. Use it to surface exactly what the
reviewer needs to make a decision (a drafted message, a computed score, the record in question).
Reference upstream data with <variable.name> or <block.output> syntax.
Notification
Configure one or more tools (Slack, Gmail, and other supported integrations) to run the moment the block pauses. This is how approvers actually find out there's something waiting on them — typically posting a channel message or sending an email that links to the resume page.
A pause with no Notification configured waits silently. Someone has to already know to check the workflow's paused executions to ever resume it.
Resume Input
Defines the schema of fields a human can fill in when resuming — field names and types, similar to the input schema on a Start/Input trigger. This schema drives two things: the form rendered on the resume UI, and the expected shape of the JSON body posted to the resume API.
How to Use
Drop a Human in the Loop block wherever the workflow needs a real checkpoint — after a draft is generated, before a payment is sent, before a record is deleted.
Configure Paused Output with the data the approver needs to see to make a decision.
Configure Notification so the right person or channel is alerted as soon as the pause happens.
Configure Resume Input with the fields you want back — an approval flag, edited text, a comment.
Connect downstream blocks normally. They can reference both the paused data and whatever the human submits on resume.
Resuming a paused execution
Open the block's url output — a link to
/resume/[workflowId]/[executionId] — which renders the Paused Output and a
form built from your Resume Input schema. Submitting the form calls the resume API for you.
POST /api/resume/{workflowId}/{executionId}/{contextId}
Content-Type: application/json
{
"input": {
"approved": true,
"comments": "Looks good, ship it."
}
}The response is either:
{ "status": "started", "executionId": "...", "message": "Resume execution started." }or, if another resume for this same execution is already running:
{ "status": "queued", "executionId": "...", "queuePosition": 2, "message": "Resume queued. It will run after current resumes finish." }To build a custom approvals dashboard instead of using the built-in resume page, list every
currently paused execution for a workflow with GET /api/workflows/{id}/paused (optionally
?status=paused), or fetch one execution's full detail — including its snapshot and queue — with
GET /api/workflows/{id}/paused/{executionId}.
What's Available After Resume
The block only ever declares one field as an official output, but the runtime output object is richer once a human has responded:
| Output | Type | Description |
|---|---|---|
url | string | Link to the resume UI for this pause point |
After resume, the block's output additionally includes:
submission— the object of fields the human submitted, matching your Resume Input schema- Each submitted field flattened directly onto the output, so both
<humanInTheLoop1.submission.approved>and<humanInTheLoop1.approved>work submittedAt— ISO timestamp of when the human responded_resumed—trueonce resumed_resumedFrom— the execution ID the pause resumed from_pauseDurationMs— how long the workflow actually sat paused, in milliseconds
{
"url": "https://app.example.com/resume/wf_123/exec_456",
"submission": { "approved": true, "comments": "Looks good, ship it." },
"approved": true,
"comments": "Looks good, ship it.",
"submittedAt": "2026-08-16T09:41:02.000Z",
"_resumed": true,
"_resumedFrom": "exec_456",
"_pauseDurationMs": 184320
}If the Human in the Loop block sits inside a Loop or Parallel container, each iteration pauses
independently with its own contextId. You can approve iterations in any order — approving one
doesn't unblock the others.
Example Use Cases
Email Reply Approval
Scenario: An agent drafts a reply; a human approves before it sends
- An Agent block reads an incoming customer email and drafts a reply
- Human in the Loop pauses, showing the drafted reply as Paused Output
- Notification posts to a Slack channel with the resume link
- A support lead opens the link, edits the draft if needed, and submits approval
- Gmail sends the (possibly edited) reply using the human's submitted text
Expense Approval Gate
Scenario: Only large expenses need a manager's sign-off
- Condition block checks whether the expense exceeds a threshold
- If it does, Human in the Loop pauses with the expense details as Paused Output
- Notification emails the manager the resume link
- Manager submits approve/reject plus an optional comment via Resume Input
- Router block branches on
<humanInTheLoop1.approved>to process or reject the expense
Content Moderation Before Publish
Scenario: AI-generated content needs a moderator's sign-off
- Agent block generates a social post from a content brief
- Human in the Loop pauses, showing the generated post
- Moderator edits the copy and submits the final text via Resume Input
- Downstream block publishes using the moderator's submitted (not the original) text
Best Practices
- Keep Paused Output focused: Show only what the reviewer actually needs to decide, not the entire workflow state — it's easier to review a short, relevant summary than a data dump.
- Design Resume Input for a clear decision: Include a simple boolean or enum field (like
approved) alongside any free-text fields, so a downstream Condition or Router block can branch cleanly instead of parsing prose. - Always configure Notification: A pause nobody is told about just sits there. Wire up at least one notification tool so the right person knows to act.
- Remember resumes are serialized: If more than one person might respond to the same pause, only one submission runs at a time and later ones queue in order — design your workflow (or a Condition check right after resume) to handle a second, possibly redundant, submission gracefully.
- Expect real wait times: Because the pause is a persisted snapshot rather than a held-open connection, it's meant for waits measured in minutes to days, not milliseconds. Use the Wait block for short, bounded delays instead.