Approvals — human-in-the-loop

Some steps should not be automatic: shipping an order over a threshold, sending the customer email, deleting production data. The Approval node parks the run until a person decides — and every pending gate across your whole fleet lands in one inbox in the Nucleus header.

The gate

Drop Approval (Logic group) where the decision belongs. Its config:

PropertyMeaning
titleThe question, verbatim, on the approval card — “Approve order?”
descriptionContext for the decider
timeoutMs0 = wait indefinitely; otherwise the gate expires after this long
onTimeoutWhat expiry does: approve, reject, or error (fail the run into its recovery lane)

The node has two outputs — approved and rejected — so both outcomes are wires, not status codes.

A real example

The shipped demo gates an HTTP-triggered order flow:

{
  "nodes": [
    { "id": "hook", "type": "trigger.http.webhook",
      "config": { "method": "POST", "path": "/orders", "contentType": "application/json" } },
    { "id": "gate", "type": "flow.approval",
      "config": { "title": "Approve order?",
                  "description": "A new order needs a human decision before it ships.",
                  "timeoutMs": 0, "onTimeout": "reject" } },
    { "id": "ok",   "type": "output.http-response", "config": { "statusCode": 202, "bodyMode": "payload" } },
    { "id": "deny", "type": "output.http-response", "config": { "statusCode": 403, "bodyMode": "payload" } }
  ],
  "edges": [
    { "sourceNodeId": "hook", "sourcePort": "output",   "targetNodeId": "gate", "targetPort": "input" },
    { "sourceNodeId": "gate", "sourcePort": "approved", "targetNodeId": "ok",   "targetPort": "input" },
    { "sourceNodeId": "gate", "sourcePort": "rejected", "targetNodeId": "deny", "targetPort": "input" }
  ]
}

POST an order to /orders and the caller waits while the run parks at the gate. Approve → the caller gets 202; reject → 403. (To answer the caller before the gate instead, put an HTTP Response node in front of it — early response.)

The fleet-wide inbox

The bell in the Nucleus header polls every pending gate across every authorized host. Each card shows the gate’s title and description, the workflow and app, the host, how long it has waited, and a live countdown when a timeout is set. Approve / Reject (with an optional note) resumes the parked run immediately — the deciding user’s name is recorded with the decision.

Deciding requires the executor role; viewers see the inbox read-only. Hosts also show their own apps’ pending gates in a panel on the host dashboard.

Where the run actually waits

In a deployed app the parked run lives in the app’s process; the host proxies its pending-gate listing and decisions. Pending gates don’t survive an app restart — by design in the current model: a restarted app starts clean, and an undecided gate’s caller sees the connection close rather than a silently dropped decision.

In the editor

Approval gates work in test runs too — the run pauses, Studio surfaces the gate, and you decide it inline. Same node, same semantics, before anything is deployed.