← All Examples

approval-pipeline

A two-step workflow that pauses for a human approval signal between build and deploy. Demonstrates ctx:wait_for_signal and how a workflow can sit indefinitely without consuming worker resources.

What it does

ApproveAndDeploy(input)
  ├─> activity "build"          →  produce {image, sha}
  ├─> wait_for_signal "approve" →  receive {by = "alice"}
  └─> activity "deploy"         →  return {url, approver}

While waiting for approve, the workflow's status in the dashboard is RUNNING and an event WorkflowAwaitingSignal is recorded — but no worker is busy on it. Send the signal to wake it up.

Run

# Terminal 1
assay serve

# Terminal 2
cd examples/workflows/approval-pipeline
assay run worker.lua

Start a build:

curl -X POST http://localhost:8080/api/v1/workflows \
  -H 'Content-Type: application/json' \
  -d '{
    "workflow_type": "ApproveAndDeploy",
    "workflow_id": "deploy-prod-001",
    "task_queue": "default",
    "input": {"git_sha": "abc123", "target_env": "production"}
  }'

Watch http://localhost:8080/workflow/ — the workflow runs build, then sits at WorkflowAwaitingSignal. Approve it from the CLI:

assay workflow signal deploy-prod-001 approve '{"by":"alice"}'

Within ~1s the workflow completes:

curl -s http://localhost:8080/api/v1/workflows/deploy-prod-001 | jq .result
# "{\"url\":\"https://.../deploy/...\",\"approver\":\"alice\"}"

You can also send the signal from any HTTP client:

curl -X POST 'http://localhost:8080/api/v1/workflows/deploy-prod-001/signal/approve' \
  -H 'Content-Type: application/json' \
  -d '{"payload": {"by": "bob"}}'

Source

worker.lua

-- approval-pipeline — pause between activities for human approval.
-- Run: assay run worker.lua  (with `assay serve` running on :8080)

local workflow = require("assay.workflow")
workflow.connect(env.get("ASSAY_ENGINE_URL") or "http://localhost:8080")

workflow.define("ApproveAndDeploy", function(ctx, input)
    -- Step 1: build
    local artifact = ctx:execute_activity("build", { ref = input.git_sha })

    -- Step 2: wait indefinitely for an `approve` signal. The worker
    -- yields here; the workflow consumes no resources until a signal
    -- arrives via POST /workflows/:id/signal/approve.
    local approval = ctx:wait_for_signal("approve")

    -- Step 3: deploy
    return ctx:execute_activity("deploy", {
        image = artifact.image,
        env = input.target_env,
        approver = approval and approval.by or "unknown",
    })
end)

workflow.activity("build", function(ctx, input)
    -- Real impl would call a CI system. Simulated for the example.
    return {
        image = "registry.example.com/app:" .. input.ref:sub(1, 8),
        sha = input.ref,
    }
end)

workflow.activity("deploy", function(ctx, input)
    -- Real impl would call k8s / nomad / etc.
    return {
        url = "https://" .. input.env .. ".example.com/app",
        approver = input.approver,
    }
end)

log.info("approval-pipeline worker ready")
workflow.listen({ queue = "default" })