A workflow is a directed graph of typed nodes wired by edges, authored in
the visual builder and serialized to YAML. The canonical shape is a single
JSON Schema generated from the same
zod source the web app validates against (web/src/lib/api-schemas-workflow.ts),
so the document, the editor, and the runtime never disagree.
Every workflow exported from the builder begins with a modeline that points your
editor’s yaml-language-server
at the schema, giving inline validation and node-kind autocompletion:
Stages annotate existing graph nodes; they do not add execution edges or a second
runtime. Each node_key must name one node in the same workflow and can appear
only once. role is intentionally an open semantic label, so custom and
Marketplace workflows can use their own vocabulary (common roles are plan,
implement, and review). who is optional metadata for the intended agent,
profile, or owner.
Branch on a predicate to different output handles.
human
Pause for a human decision or input.
end
Terminal node.
code
Run a Bash/Python/TypeScript/Go/Rust/C# snippet (defaults to Bash). Unbound: hermetic container, no network/DB. A Python/TypeScript node may declare resource_bindings to run on the credentialed sandbox with vetted egress + a host-mediated guarded DB call (ADR 0058).
db_query
Read from a bound PostgreSQL resource. params resolve ${{ nodes.* }}/${{ trigger.* }}/${{ steps.* }} typed (#990); the SQL text itself is deliberately never templated.
db_execute
Write to a bound PostgreSQL resource. params resolve references like db_query (#990); SQL text never templated.
http_request
Make an outbound HTTP call (SSRF-guarded, IP-pinned). url, headers and body resolve ${{ }} references typed (#990); the resolved URL passes the same SSRF vetting as a literal one.
transform
Reshape data flowing between nodes.
notify_telegram
Send a Telegram message.
notify_email
Send an email. Optional attachments (#1050): up to 5 entries of content_b64 (base64 or a ${{ nodes.<key>.<field> }} reference), filename, content_type; 10 MiB decoded total. An unresolvable or undecodable attachment fails the node loudly — an invoice mail never sends hollow.
notify_webhook
POST to a webhook.
notify_discord
Send a Discord message.
loop
Iterate over a collection. over is a TYPED value slot (#990 Teil 2): a single ${{ nodes.<key>.<field> }} / ${{ trigger.<field> }} reference resolving to an array, or a static JSON array literal. The legacy string-interpolation form was removed — a mixed string fails loudly with a migration hint.
Run a marketplace connector node. Optional resources pin (#1044): {"<resource_kind>": "<resource name>"} selects WHICH same-kind workspace resource each required kind binds; an unpinned kind keeps the deterministic first-by-name default, a pinned name that does not exist fails the run loudly.
ssh_exec
Run ONE command on a bound ssh_host resource (#1049). The command is STATIC (no ${{ }} templating — that would be a shell-injection channel); dynamic data goes on stdin_b64. Output (stdout/stderr/exit_code) is byte-capped; a non-zero remote exit fails the node. The host/port are SSRF-vetted and IP-pinned; the private key/password come only from the resource secret.
Upstream node results flow to a node via SC_NODE_INPUTS; a trigger payload via
SC_TRIGGER_CONTEXT. See the build-your-first-workflow tutorial
to author one in the visual builder.