Skip to main content
Version: 3.0 (next)

Approval Node

Overview​

The Approval node is the human gate. It holds the incoming payload, asks a person the question you wrote, and continues the pipeline with their verdict.

Use it wherever a pipeline is about to do something a person should sign off: change a setpoint, release a batch, push a price list, act on an agent's proposal.

Core Functionality​

It parks, then resumes​

This node behaves differently from every other node in a pipeline, and the difference matters when you read an execution:

  1. The parking run. The payload becomes a pending question and the run finishes right there. Nothing goes downstream. In the execution view this looks like a run that stopped at the gate, because it did.
  2. The verdict run. When someone answers — or the deadline passes — the gate emits one envelope as a new execution starting from this node. That is the run in which your downstream nodes do their work.

So one approved payload produces two executions. That is expected.

One socket, branch on the verdict​

The node has a single output. Both an approval and a rejection travel down it, and the envelope says which happened. Put a Condition node after it and branch on $node["Approve"].result.decision when the two outcomes need different paths.

Who gets asked​

audienceRoles is a comma-separated list of role names whose notification feed the question lands in, for example Process Engineer, Organization.Admin. Leave it empty and everyone who can decide approvals is notified.

Where the question is answered​

A parked question appears under Orchestrate → Approvals, on the Pending tab. The person answering sees the held payload, clicks Approve or Reject, and can add a note. Answering needs the approval:decide permission; audienceRoles only steers notifications and does not limit who can answer. See Approvals for the whole inbox, including why an approval can turn into a stale rejection when the pipeline was saved or disabled after the question was asked.

The deadline​

decisionTTL is how long the question may wait, 1h by default. Past it the gate rejects with reason: timeout. The gate never waits forever and it never auto-approves — a deadline that passes is a no.

Guarding against stale approvals​

proposalFreshness bounds how old the proposal may be at the moment it is approved. Set it to 5m and an approval that lands an hour later rejects with reason: stale rather than acting on data that has moved on. Leave it empty for no bound.

Guarding against a queue nobody drains​

pendingCap caps how many undecided questions one gate may hold, 100 by default. Past the cap a new payload is not parked: it continues immediately with decision: rejected, reason: overflow. The pipeline keeps moving, and the drop leaves evidence in the run rather than disappearing.

Input / Output​

Input​

Whatever the upstream node produced. It is held verbatim and handed back in the envelope, so nothing is lost across the wait.

Output​

The parking run emits nothing. The verdict run emits:

{
"result": {
"decision": "approved",
"reason": "",
"decidedBy": "user:8f2c…",
"note": "Checked against the batch record.",
"decisionId": "d-91a4…",
"title": "Raise setpoint on PMP-104?",
"payload": { "setpoint": 74.5 }
}
}
FieldWhat it tells you
decisionapproved or rejected — the field to branch on.
reasonEmpty when approved. Otherwise rejected (a person said no), timeout (nobody answered in time), stale (approved too late for proposalFreshness), or overflow (the pending cap refused the park).
decidedByWho decided, as a canonical subject. Empty for timeout and overflow, because nobody did.
noteThe decider's note; for stale it also carries the staleness detail.
decisionIdThe decision row this verdict answered — the audit handle.
titleThe question that was asked.
payloadThe held input, unchanged.

Read them downstream as $node["Approve"].result.decision.

Testing in the designer​

A sandbox test run does not park — you are sitting right there. The node prompts inline on the canvas and waits for your answer, and the envelope flows on in the same test run carrying sandbox: true. If the node's execution timeout fires first you get rejected / timeout, exactly the live behaviour.

Usage examples​

Sign off a setpoint change​

  • title: Raise setpoint on PMP-104?
  • decisionTTL: 4h
  • audienceRoles: Process Engineer
  • Downstream: a Condition on result.decision, with the write on the true branch.

Release a batch, but only on fresh data​

  • proposalFreshness: 10m — an approval that arrives after the batch has moved on rejects as stale instead of releasing the wrong batch.

Keep a busy gate honest​

  • pendingCap: 20 — if twenty questions are already waiting, the twenty-first continues as rejected / overflow rather than growing a queue nobody is reading.
Always branch on the verdict

A single socket means the rejected path runs your downstream nodes too, unless you branch. If a rejection should do nothing, send the false branch of a Condition to a No Operation node.

Two executions per approval

The parking run and the verdict run are separate executions. When looking for what happened after an approval, open the later one.


Configuration reference​

The fields below are generated from the node's config contract, so they match what the pipeline validator enforces and what the designer's form offers.

control.approval​

FieldTypeRequiredDefaultValuesDescription
titlestringyes——The question the approver is asked, e.g. "Raise setpoint on PMP-104?" — shown in the approval inbox and the decision history
decisionTTLstringno1h—How long the question may wait for an answer, as a duration such as "30m" or "4h"; must be positive. Past it the gate rejects with reason=timeout — it never auto-approves
proposalFreshnessstringno—at least 0Optional bound on how old the PROPOSAL may be when approved, as a duration such as "5m": an approval landing after it rejects with reason=stale instead of executing old data. Empty = no bound
pendingCapintegerno100at least 1Maximum undecided questions this gate may hold. An overflow proposal is not parked — it continues at once with decision=rejected, reason=overflow
audienceRolesstringno——Comma-separated role names whose notification feeds this gate's questions land in, e.g. "Process Engineer, Organization.Admin". Empty notifies everyone who can decide approvals. A single string, not a list