DSL cheatsheet

Every workflow builder, step option, condition, trigger, and the flowctl commands to drive a run — on one page.

The whole DSL is one call to workflow({ … }). This page is the quick reference; the guides go deeper on each construct.

The workflow() object

KeyTypePurpose
namestringRequired. The definition name you start runs against.
versionstringOptional display label.
descriptionstringOptional.
initialobjectStarting variables; --input is merged over this at start.
inputSchemaJSON SchemaOptional; validates trigger --input.
triggersarrayRequired. One or more triggers (see below).
stepsarrayRequired. Step builders; exactly one has start: true.
result(vars) => objectOptional terminal reducer for queryable output.
outcomeTemplatesobjectMustache strings for run-history rendering.
inboxTemplatesobjectMustache {title, subtitle} for HITL inbox cards.

Step builders

BuilderSignatureNotes
stepstep(name, opts, fn)Regular node. fn is always required, even for pure routers.
formform(name, opts, fn)Parks WAITING_FORM; fn(ctx, data).
signalsignal(name, opts, fn)Parks WAITING_SIGNAL; fn(ctx, data).
webhookwebhook(name, opts, fn)Parks WAITING_WEBHOOK; fn(ctx, data).
connectorconnector(name, opts)External call; no positional handler.
agentagent(name, opts, fn)Parks WAITING_AGENT; asks the driving agent; fn(ctx, data).
queryquery(name, opts, fn)Read-only; not part of the flow graph.

The handler contract: function(ctx) { return { … } } for steps; function(ctx, data) { … } for form/signal/webhook/agent, where data is the inbound payload. The returned object is merged into ctx.vars.

Step options (opts)

OptionTypeMeaning
startboolThe single entry step.
nextstringNext step name, or "end".
labelstringDisplay name for UI / narrative.
branchesarray[branch(cond, target), …] — first true condition wins.
loopobject{ condition, next, breakNext } (while-loop; next is the body).
foreachobject{ collection, itemVar, body, next } — iterate an array variable.
parallelobject{ steps: [names…], next } — run concurrently, then join.
onErrorstringStep to route to when the handler throws (ctx.error is populated).
retryPolicyobject{ maxAttempts, initialInterval, backoffCoefficient, maximumInterval }.
narrativefunctionfn(ctx, status, branch) → human-readable status text.

Conditions (branches & loops)

Expressions over the current variables:

  • Comparisons: ==, !=, >, <, >=, <=
  • Connectives: AND, OR
  • Literals: true, false, numbers, 'single-quoted strings'
  • Catch-all: list branch("true", "fallback") last
branches: [
  branch("total > 1000 AND status != 'pending'", "manual_review"),
  branch("payment_status == 'success'", "fulfill"),
  branch("true", "reject"),
]

Triggers

BuilderStarts a run via
apiTrigger(name)flowctl run start --workflow <name>
formTrigger(name)Submitting a trigger form
webhookTrigger(name)HTTP POST to the trigger URL
scheduleTrigger(name, { cron, collision, timezone })A cron schedule

scheduleTrigger cron is 5-field, minute-granular. collision is "skip" (default), "queue", or "parallel".

Scheduled runs with human steps

Under the default skip collision, a scheduled run that parks on a form/agent can block later fires until a human responds. Decouple the human leg, or use queue / parallel. Keep an apiTrigger for manual testing.

Connector adapters

requestMapper(ctx) returns the request; responseCombiner(ctx, response) reads the result — and the envelope differs per adapter. Always confirm shapes with flowctl conn schema <connection> <operation>.

AdapterrequestMapper returnsresponseCombiner sees
generic-http{ body, path_params?, query_params? }{ status_code, body, headers }
llm (OpenRouter){ model, messages: [{ role, content }] }{ choices, usage, … }
composioflat action args, e.g. { recipient_email, subject, body }{ data, successful, error } — check successful
mcpthe MCP tool’s argument objectthe tool’s result payload

Control directives (returned from handlers)

CallEffect
action.transition("step")Jump to a named step (overrides next).
action.end()End the run now (COMPLETED).
action.fail("reason")Marks the run FAILEDonly takes effect on a terminal step (one whose flow resolves to "end"). To fail mid-flow, route to a terminal failing step via onError / a branch.

action.log("msg") is accepted for compatibility but is currently a no-op (nothing is emitted or persisted) — use it sparingly, and don’t rely on it for diagnostics yet.

Driving a run with flowctl

# Definitions
flowctl def create --file <workflow.js>        # register; prints id
flowctl def list

# Runs
flowctl run start (--workflow <name> | --def <id>) --input '<json>' [--channel <id>]
flowctl run status <exec> -o json
flowctl run watch <exec>
flowctl run cancel <exec>

# Resume parked runs
flowctl form submit <exec> <formName> --data '<json>'
flowctl signal send <exec> --name <signalName> --data '<json>'
flowctl webhook call <exec> <path> --payload '<json>'
flowctl agent submit <exec> <name> --type json --result '<json>'

# Read-only
flowctl query run <exec> <queryName>