Skip to content
Looms
Documentation
On this page

Human approvals

gate() records an approval request and waits for a matching decision. Your application decides what approval, rejection, and expiration mean.

Require an explicit approval#

The complete quickstart checks the review result with a schema requiring outcome: 'approve'. Every other result takes the unpublished branch. A rejected gate can complete normally, so a node dependency alone never authorizes an action.

ts
// Fragment inside the node after a gate:const approved = z.object({ outcome: z.literal('approve') })if (!approved.safeParse(ctx.results.review).success) {  return { published: false }}// Only this branch may request the publication effect.

Deliver a decision#

ts
import { decision, pendingApprovals } from '@swirls/looms/approval'const reviews = await looms.project(runId, pendingApprovals)const pending = reviews.items.find((review) => review.status === 'pending')if (!pending) throw new Error('No pending approval')await looms.signal(runId, [decision(pending.approvalId, 'reject')], {  idempotencyKey: 'decision-request-from-your-application',})

Authenticate the reviewer, authorize access to this run and approval, and validate the action on the server. Use a unique key per logical request and reuse it for retries of that request. A new key represents a new request and does not resolve conflicting decisions.

Concurrent and duplicate decisions#

Correlate waits by approvalId, not only the event type. Concurrent approvals can otherwise satisfy the wrong wait. Serialize decisions on the server and reject decisions for already resolved approvals. A read of pendingApprovals followed by a write is not an atomic compare-and-set across multiple clients.

The runtime validates event shape, but does not implement your reviewer identity, permission policy, or a complete approval lifecycle service. Do not allow clients to submit arbitrary approval.decided events.

Expiration and late replies#

gate() has no automatic timeout option. To expire approvals, define an application workflow or custom thread that records a deadline, waits for a correlated decision or timer, and fails closed on timeout. Record expiration explicitly and reject late decisions at the gateway. Emitting approval.timed_out does not satisfy a gate waiting for approval.decided.

Review UI#

Render only pending items from pendingApprovals. Show the proposed action and relevant inputs, offer approve and reject, disable controls while a request is in flight, and display failures. The server must still enforce the decision policy. Reconnect by reading the recorded state rather than assuming a click succeeded.