Skip to main content

Human-in-the-Loop

Some workflows shouldn't proceed without a person's sign-off — sending a high-value email, deploying code, refunding a customer. OrcFlows supports this natively with approval steps that pause a running workflow indefinitely (durably, via Temporal signals) until a human approves or rejects.

Approval node types

typeBehavior
human-approvalGeneric approval gate — you choose the notification channel via notify_type.
approval-slackConvenience alias — pre-fills notify_type: "slack".
approval-telegramConvenience alias — pre-fills notify_type: "telegram".

Example:

{
"id": "review_email",
"type": "human-approval",
"depends_on": ["draft_email"],
"config": {
"message": "Send this email to {{steps.draft_email.output.to}}?\n\n{{steps.draft_email.output.body}}",
"notify_type": "slack",
"notify_to": "#approvals",
"notify_token": "{{ secret.SLACK_TOKEN }}"
}
}

message, notify_to, and notify_token are resolved as expressions against the full execution context, so the approval message can embed any prior step's output.

How it works under the hood

  1. When the workflow execution reaches an approval step, it marks the step as waiting in the database (MarkStepWaitingActivity) and sends a notification (Slack message, Telegram message, etc.) containing the rendered message and one-click Approve/Reject links.
  2. The Temporal workflow then blocks indefinitely on a signal channel named approve-<step_id>workflow.GetSignalChannel(ctx, signalName).Receive(...). This wait is fully durable: it survives worker restarts and deploys.
  3. A human responds via:
    • One-click linksGET /api/v1/executions/{id}/approve/{stepId}?decision=approve|reject (public, token-less; the execution ID + step ID combination provides sufficient entropy for buttons embedded in Slack/Telegram messages).
    • The dashboard — the Pending Approvals view (GET /api/v1/approvals) lists every execution currently waiting, across all workflows.
    • The APIPOST /api/v1/executions/{id}/signal with {"signal": "approve-<step_id>", "data": {"approved": true, "comment": "..."}}.
  4. Once the signal is received, MarkApprovalCompletedActivity records the decision and the workflow continues — or, if approved: false, the step's output contains the rejection so downstream steps (or continue_on: "error") can branch accordingly.

If the execution is cancelled while waiting, the approval step is marked as cancelled (comment: "cancelled") and the workflow stops cleanly.

Retrying past an approval

If a workflow execution is retried (POST /api/v1/executions/{id}/retry) after an already-approved step, the retry does not re-prompt — the prior approval decision is replayed from PreloadedOutputs, so the workflow proceeds straight through.

Pending approvals view

GET /api/v1/approvals returns every step currently in the waiting state across the tenant — surfaced in the dashboard as a single inbox so reviewers don't need to dig through individual executions.

Next