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" })