Skip to content
Talk to our solutions team

Control & Coordination

Locks, waits, suspension, error signalling and source control. Every parameter with its type, default and description, plus worked examples.

Distributed mutex — serialize concurrent workflow runs on a named resource. Two backends: postgres (lease table) and dragonfly (Redis-compatible).

acquire returns an opaque token that must be supplied to refresh and release. There is no fencing token; the lease is enforced only at the lock store, not at any downstream resource.

Single task that dispatches on op: (acquire / refresh / release / peek).

FieldTypeRequiredDefaultDescription
opstringNo"acquire"Operation: acquire / refresh / release / peek
backendstringNo"postgres"postgres or dragonfly
dsnstringYesPostgres DSN or Redis URL
tablestringNokis_locksPostgres lease table name (ignored for dragonfly)
keystringYesLock key
setvarstringNoCapture the result map into this workflow variable
FieldTypeUsed byDescription
ttldurationacquire, refreshLease duration
waitdurationacquireBlock-and-retry budget. Default 0 = fail fast
ownerstringacquireOptional metadata stamped on the lease
tokenstringrefresh, releaseToken from the matching acquire

Durations accept Go-style strings (5m) or a bare integer (seconds).

acquire: { success, acquired, attempts, key, token, expiresAt, error }. refresh: { success, refreshed, expiresAt, error }. release: { success, released, error }. peek: { success, held, token, owner, expiresAt, error }.

name: serialize-deployments
tasks:
- name: take-deploy-lock
lock:
op: acquire
backend: postgres
dsn: "{{lock_dsn}}"
key: deploy/prod
ttl: 10m
wait: 30s
owner: "deploy-{{run_id}}"
setvar: deploy_lock
- name: deploy
shell:
script: ./deploy.sh prod
- name: drop-deploy-lock
lock:
op: release
backend: postgres
dsn: "{{lock_dsn}}"
key: deploy/prod
token: "{{deploy_lock.token}}"
- name: rotation-lock
lock:
op: acquire
backend: dragonfly
dsn: "redis://dragonfly.internal:6379/0"
key: certs/rotation
ttl: 5m
wait: 1m
setvar: rot
- name: refresh-mid
lock:
op: refresh
dsn: "{{lock_dsn}}"
key: migration/2026-05-19
token: "{{mig.token}}"
ttl: 2m
- name: who-holds-it
lock:
op: peek
backend: dragonfly
dsn: "redis://dragonfly.internal:6379/0"
key: nightly-report
setvar: holder
- name: report
when: "{{not holder.held}}"
shell:
script: ./nightly-report.sh

Same operations as separate Script functions: lock.acquire, lock.refresh, lock.release, lock.peek. See docs/namespaces/lock.md for the full namespace reference.

Polling primitive — blocks until a runtime condition becomes true. Active complement to the passive suspend task.

wait is a single task that dispatches on a for: discriminator. Today only for: http is implemented; tcp / dns / exec / file / db variants are planned.

FieldTypeRequiredDefaultDescription
forstringNo"http"Wait kind
setvarstringNoCapture the result map into this workflow variable
FieldTypeRequiredDefaultDescription
urlstringYesTarget URL
methodstringNo"GET"HTTP method
headersmapNoRequest headers — sent on every poll
bodystringNoRequest body — reused on every poll
statusint or [int]No200Acceptable status code(s)
bodymatchstringNoRegex the response body must match
bodycontainsstringNoSubstring the response body must contain
headermatchmapNoPer-header regex constraints
intervaldurationNo5sPoll interval
timeoutdurationNo5mTotal timeout
initialdelaydurationNo0Delay before the first poll
insecuretlsboolNofalseSkip TLS certificate verification
expectfailureboolNofalseSucceed when the URL stops responding

Durations accept Go-style strings (5s, 1m30s) or a bare integer (seconds).

FieldTypeDescription
successboolWhether the wait condition was met
attemptsintNumber of polls performed
elapsedMsintTotal elapsed milliseconds
lastStatusintHTTP status of the final poll
lastBodystringBody of the final poll
lastHeadersmapHeaders of the final poll
errorstringFailure reason on timeout / cancellation
name: wait-for-health
tasks:
- name: deploy
shell:
script: ./deploy.sh
- name: ready
wait:
for: http
url: http://localhost:8080/healthz
status: 200
interval: 2s
timeout: 90s
name: wait-for-job
tasks:
- name: complete
wait:
for: http
url: "https://api.example.com/jobs/{{job_id}}"
headers:
Authorization: "Bearer {{api_token}}"
bodycontains: '"status":"done"'
interval: 5s
timeout: 30m
- name: gone
wait:
for: http
url: http://localhost:8080/healthz
expectfailure: true
interval: 1s
timeout: 30s
- name: ready
wait:
for: http
url: https://api.example.com/state
bodycontains: '"ready":true'
setvar: state_check
- name: report
print:
message: "Ready after {{state_check.attempts}} polls in {{state_check.elapsedMs}}ms."

Execute the same polls from Script scripts.

FunctionDescription
wait.http()Poll an HTTP endpoint until the response satisfies the criteria
ParameterTypeDefaultDescription
urlstringTarget URL (required)
methodstring"GET"HTTP method
headersmapRequest headers
bodystringRequest body
statusint or [int]200Acceptable status code(s)
bodyMatchstringRegex the body must match
bodyContainsstringSubstring the body must contain
headerMatchmapPer-header regex constraints
intervalint or string5sPoll interval ("5s" or integer seconds)
timeoutint or string5mTotal timeout
initialDelayint or string0Delay before the first poll
insecureTLSboolfalseSkip TLS verification
expectFailureboolfalseSucceed when the URL stops responding
FieldTypeDescription
successboolWhether the condition was met
attemptsintPolls performed
elapsedMsintTotal elapsed milliseconds
lastStatusintStatus of the final poll
lastBodystringBody of the final poll
lastHeadersmapHeaders of the final poll
errorstringFailure reason
// Wait for an endpoint to return 200
let r = wait.http({
url: "http://localhost:8080/healthz",
interval: "2s",
timeout: "90s"
})
if (!r.success) {
throw "service never came up: " + r.error
}
// Block on a long-running job
let r = wait.http({
url: "https://api.example.com/jobs/" + jobId,
headers: { Authorization: "Bearer " + token },
bodyContains: '"status":"done"',
interval: "5s",
timeout: "30m"
})
// Wait for shutdown
wait.http({
url: "http://localhost:8080/healthz",
expectFailure: true,
interval: "1s",
timeout: "30s"
})
// Match a status list and inspect the final response on timeout
let r = wait.http({
url: "https://api.example.com/v",
status: [200, 204],
bodyMatch: "\"version\":\"2\\.\\d+\\.\\d+\"",
timeout: "10m"
})
log("attempts=" + r.attempts + " lastStatus=" + r.lastStatus)

See docs/namespaces/wait.md for the dedicated namespace reference.

Pause workflow execution until triggered with data. Can suspend at different phases of task execution.

ParameterTypeRequiredDefaultDescription
whenstringNoexecuteWhen to suspend: execute, before, after
messagestringNo-Message to log when suspending
ValueDescription
executeSuspend during Execute phase, resumes when triggered with data
beforeSuspend before Execute runs
afterSuspend after Execute completes
name: suspend-approval
tasks:
- name: deploy-staging
shell: ./deploy.sh staging
- name: wait-for-approval
suspend:
message: "Waiting for production deployment approval"
- name: deploy-production
shell: ./deploy.sh production
name: suspend-before
tasks:
- name: wait-before-start
suspend:
when: before
message: "Workflow paused before starting task"
name: suspend-gate
tasks:
- name: run-tests
shell: npm test
- name: manual-qa-gate
suspend:
message: "Waiting for QA team approval"
- name: publish
shell: npm publish

Test utility task that throws errors at configurable phases. Useful for testing error handling in workflows.

ParameterTypeRequiredDefaultDescription
phasestringNoexecuteWhen to throw: execute, before, after, all
messagestringNo-Custom error message
ValueDescription
executeThrow error during Execute phase (default)
beforeThrow error during BeforeExecute phase
afterThrow error during AfterExecute phase
allThrow error in all phases
name: throw-simple
tasks:
- name: fail-task
throw:
message: "This task intentionally fails"
name: throw-before
tasks:
- name: fail-before-execute
throw:
phase: before
message: "Error during initialization"
name: throw-test-handling
tasks:
- name: trigger-error
throw:
phase: execute
message: "Simulated failure for testing"
on_failure:
- name: handle-error
print:
message: "Error was caught and handled"

Git operations using go-git (pure Go).

OperationDescription
cloneClone a repository
commitCreate a commit
pushPush to remote
pullPull from remote
statusGet repository status
add-remoteAdd a remote
remove-remoteRemove a remote
ParameterTypeRequiredDescription
commandstringYesOperation to perform
directorystringYes*Repository directory
urlstringYes*Repository URL (for clone)
filestringNoFile to add and commit
contentstringNoContent to write to file
messagestringNoCommit message
authorstringNoAuthor name
emailstringNoAuthor email
remotestringNoRemote name (default: origin)
usernamestringNoHTTP basic auth username
passwordstringNoHTTP basic auth password/token
depthintNoClone depth (0 = full)
name: git-clone
tasks:
- name: clone
git:
command: clone
url: https://github.com/user/repo.git
directory: /path/to/dest
username: "{{git_user}}"
password: "{{git_token}}"
name: git-commit-push
tasks:
- name: commit
git:
command: commit
directory: /path/to/repo
file: config.yaml
content: "key: value"
message: "Update config"
author: John Doe
- name: push
git:
command: push
directory: /path/to/repo
remote: origin
username: "{{git_user}}"
password: "{{git_token}}"

Git version control functions.

FunctionDescription
git.clone()Clone a repository
git.commit()Create a commit
git.push()Push to remote
git.pull()Pull from remote
git.status()Get repository status
git.addRemote()Add a remote
git.removeRemote()Remove a remote
ParameterTypeDefaultDescription
urlstring-Repository URL (required)
directorystring-Local directory path (required)
usernamestring-Auth username
passwordstring-Auth password/token
depthint0Clone depth (0 = full)
ParameterTypeDefaultDescription
directorystring-Repository directory (required)
messagestring-Commit message
authorstring-Author name (required)
emailstring-Author email (required)
filestring-File to add/modify
contentstring-Content to write to file
ParameterTypeDefaultDescription
directorystring-Repository directory (required)
remotestring"origin"Remote name
usernamestring-Auth username
passwordstring-Auth password/token
// Clone a repository
let result = git.clone({
url: "https://github.com/user/repo.git",
directory: "/path/to/dest",
username: input.git_user,
password: input.git_token
})
// Get repository status
let status = git.status({
directory: "/path/to/repo"
})
if (status.success) {
log("Is clean:", status.clean)
log("Changed files:", status.files)
}
// Commit changes
git.commit({
directory: "/path/to/repo",
file: "config.yaml",
content: "key: value\n",
message: "Update configuration",
author: "John Doe",
})
// Push to remote
git.push({
directory: "/path/to/repo",
remote: "origin",
username: input.git_user,
password: input.git_token
})
// Pull from remote
git.pull({
directory: "/path/to/repo",
remote: "origin",
username: input.git_user,
password: input.git_token
})
// Add remote
git.addRemote({
directory: "/path/to/repo",
name: "upstream",
url: "https://github.com/upstream/repo.git"
})
// Remove remote
git.removeRemote({
directory: "/path/to/repo",
name: "upstream"
})

DNS record CRUD (via libdns) and system-resolver lookups. Single task that dispatches on op:. Six providers in v1: cloudflare, route53, gcloud, azure, digitalocean, godaddy.

The same providers are reused by the letsencrypt task for DNS-01 challenges, so credentials map between them.

FieldTypeRequiredDefaultDescription
opstringNolistlist / set / append / delete / lookup
providerstringYes (CRUD)Provider name (see spec)
credentialsmapYes (CRUD)Provider-specific auth (see spec for keys)
zonestringYes (CRUD)Zone name (example.com)
timeoutdurationNoWall-clock cap
setvarstringNoCapture result map into a workflow variable
FieldTypeDescription
typestringA, AAAA, CNAME, TXT, MX, SRV, NS, etc.
namestringRelative to zone (www); @ for apex
valuestringFor MX: hostname. For SRV: "port target". Otherwise the literal value
ttldurationTTL
priorityintMX or SRV priority
weightintSRV weight
OpAdds to base {success, error}
list / set / append / lookuprecords
deletedeleted
- name: add-www
dns:
op: append
provider: cloudflare
credentials:
api_token: "{{ cf_token }}"
zone: example.com
records:
- type: A
name: www
value: 203.0.113.10
ttl: 300
- name: spf
dns:
op: set
provider: cloudflare
credentials: { api_token: "{{ cf_token }}" }
zone: example.com
records:
- type: TXT
name: "@"
value: "v=spf1 include:_spf.google.com -all"
MX records (priority surfaced as its own field)
Section titled “MX records (priority surfaced as its own field)”
- name: mx
dns:
op: set
provider: cloudflare
credentials: { api_token: "{{ cf_token }}" }
zone: example.com
records:
- { type: MX, name: "@", value: "primary-mx.example.com.", priority: 10 }
- { type: MX, name: "@", value: "secondary-mx.example.com.", priority: 20 }
- name: lookup
dns:
op: lookup
name: www.example.com
type: A
server: 1.1.1.1:53
timeout: 10s
setvar: result
- name: report
print:
message: "Resolved to {{ result.records[0].value }}"

Same five operations as Script functions: dns.list / dns.set / dns.append / dns.delete / dns.lookup. See docs/namespaces/dns.md for the full namespace reference.

// Add a record
dns.append({
provider: "cloudflare",
credentials: { api_token: cfToken },
zone: "example.com",
records: [{ type: "A", name: "www", value: "203.0.113.10", ttl: 300 }]
})
// Verify propagation
let r = dns.lookup({ name: "www.example.com", type: "A", server: "1.1.1.1:53" })
if (r.records.length === 0) {
throw "DNS not propagated yet"
}

Web scraping using an external scraper service. Extracts content from web pages.

ParameterTypeRequiredDefaultDescription
scraperurlstringYes-Scraper service URL
urlslistYes-List of URLs to scrape
setvarstringNo-Variable to store results
continueonerrorboolNofalseContinue on scraping errors
timeoutstringNo5mScraping timeout
contextlimitintNo15000Max characters per chunk
PropertyTypeDefaultDescription
urlstring-URL to scrape
depthint1Link crawl depth
limitint2Max pages to scrape
secureboolfalseUse secure mode
structuredboolfalseReturn structured data
authenticatemap-Authentication config
name: scraper-basic
vars:
scraper_url: "https://scraper.example.com"
tasks:
- name: scrape-docs
scraper:
scraperurl: "{{scraper_url}}"
urls:
- url: "https://docs.example.com/guide"
depth: 2
limit: 10
setvar: doc_content
name: scraper-multi
tasks:
- name: scrape-competitors
scraper:
scraperurl: "{{scraper_url}}"
continueonerror: true
timeout: 10m
urls:
- url: "https://competitor1.com/pricing"
- url: "https://competitor2.com/pricing"
- url: "https://competitor3.com/pricing"
setvar: pricing_data
name: scraper-auth
tasks:
- name: scrape-protected
scraper:
scraperurl: "{{scraper_url}}"
urls:
- url: "https://internal.example.com/docs"
depth: 3
limit: 50
authenticate:
type: bearer
token: "{{api_token}}"
setvar: internal_docs