ctx.suspend(payload) and resume it with lua workflows resume; see Operate runs.
Verified against lua-cli 3.33.0.
Before you begin
- A workflow that compiles (Author a workflow).
- The users, role, or group that should decide, as known to your organization.
1
Add an approval step
.approval(id, options) shows the previous entry’s output as the payload and parks the run until a decision arrives. Put a .map() in front of it so the payload is exactly what the approver should see and edit.src/workflows/refund-approval.ts
approver is 'creator' (the default), 'org-admins', { users: [...] }, { role }, { group }, or { governance: { policyId } }; excludeInitiator keeps whoever started the run from deciding. timeoutHours defaults to 168 and is at most 720; businessHours makes the deadline count working time. onTimeout is 'deny', 'cancel-run', 'fail', or up to three { escalateTo, timeoutHours } hops ending in one of those; 'continue' behaves as 'deny' with decision: 'timed_out'. A non-empty editablePaths (amount, drafts[*].body, summary.title) makes the payload editable, editedPayloadSchema validates the edit, and fourEyes: { edit, approve } keeps the editor from also approving.2
Read the decision in the next step
The step after an approval receives the approval’s output, never the payload, so give its
inputSchema a .passthrough() and read the original with getStepResult('<map id>') or getInitData().approved is false on a denial and on a timeout under 'deny'; decision is 'approved', 'denied', or 'timed_out'; text is the approver’s note or the decision word, and note holds the note verbatim when one was left. editedPayload and editRevision appear when the payload was edited; timedOut, escalations, and items when a deadline chain or per-item approval decided. Under the default onDeny: 'continue' the run continues and your step decides what a denial means; onDeny: 'fail' fails the run.3
Wait for a signal
.waitForSignal(id, options) parks the run until a named signal arrives from outside it. A signal that arrives before the wait begins is held and consumed when it does.src/workflows/invoice-paid.ts
schema validates each delivery and rejects a payload that does not match. acceptedSources defaults to ['webhook', 'api', 'user']; add 'agent' to let the agent deliver it from chat. The step completes with { payload, source, signalId, receivedAt } (receivedAt in epoch milliseconds), or with { received: false, timedOut: true } when onTimeout is 'continue'; under the default 'fail' a timeout fails the step.Local runs only. Under
lua test workflow, --signal completes the wait with the payload itself rather than the { payload, source, signalId, receivedAt } envelope, so this step returns closed: false offline and closed: true on the platform.4
Deliver the signal from a webhook
Start the run with a With a run id in hand,
correlationKey (Workflows.start(…, { correlationKey }) or lua workflows start --correlation-key) and the webhook needs no run id.src/webhooks/payment-received.webhook.ts
Workflows.signal(runId, 'payment.received', payload, { dedupeKey }) delivers to that run and answers { accepted, consumed, stepId }; dedupeKey makes a redelivered webhook a no-op. From a terminal, lua workflows signal <runId> payment.received --payload '{"invoiceId":"inv_abc123","amount":120}' --dedupe-key evt_abc123 does the same.Local runs only. The deployed runtime fails
Workflows.signal and Workflows.signalByKey with signal_unavailable, so a deployed webhook delivers the signal through the REST API instead; lua test webhook runs this file as written.5
Answer an approval from the CLI
An approval is addressed by its Save the edited payload to a file and approve against that fingerprint, or deny with a note:Only a signed-in person can decide; an API key is refused. The same decision is available in the desktop app and the inbox, and an organization policy can restrict which surfaces (
wfa_… id, which lua workflows status <runId> --json exposes as suspendedFor.approvalId. To edit the payload, read it first: the fingerprint pins the revision you saw.Output
desktop, inbox, chat-card, channel-reply, cli, api) may approve, exclude the initiator, or require a fresh sign-in (STEP_UP_REQUIRED).6
Verify
Run both workflows offline: deny the refund, then deliver the payment signal.
Output
--approve [email protected] answers with an edited payload, checked against editedPayloadSchema before the run starts and against editablePaths when the approval is reached. A wait you did not answer stops with exit 2 and SIGNAL_UNANSWERED waitForSignal "payment" needs --signal payment.received=<json> (no interactive stdin).If it isn’t working
PAYLOAD_MISMATCH
PAYLOAD_MISMATCH
Someone edited the payload after you read it. Run
approval-payload again, apply your edit to the new revision, and approve with the new fingerprint.APPROVAL_REQUIRES_HUMAN
APPROVAL_REQUIRES_HUMAN
You ran
resume on an approval step, or resolve-step with an API key. Approvals are answered with approve, by a signed-in person.SIGNAL_SCHEMA_INVALID
SIGNAL_SCHEMA_INVALID
The payload does not match the wait’s
schema. The message lists the failing paths; a rejected delivery does not consume its dedupeKey, so send the corrected payload with the same key.Next steps
Operate runs
Watch a parked run, resume a suspended step, and retry or resolve a parked one.
Workflow builder reference
Every
approval() and waitForSignal() option and the output fields.Handle a webhook
Verify the provider’s signature before you signal a run.
Workflows runtime API
signal, signalByKey, and resume from code.
