Workflows
Build DAGs of nodes connected by edges to define workflow logic.
What it is. A deterministic set of instructions: a standard operating procedure you can declare, version, and reuse.
Use it when you already have a procedure and want it to run the same way every time. Workflows are also available to agents as tools, so the reasoning layer can invoke the exact one.
Works with triggers (what starts a run), node types (the steps), reviews (human approval), and streams (where output persists).
A workflow is a directed acyclic graph (DAG) of nodes connected by edges. Each node performs a discrete unit of work. The flow block defines the edges between nodes.
Workflow structure
| Field | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Display name |
description | string | No | Description |
Cycles are not allowed in the outer flow block. For in-workflow iteration, use type: map or type: while nodes (child workflows), or trigger separate runs via a schedule.
Root node
Every workflow has exactly one root node. The root node is the entry point for the workflow run. It defines:
inputSchema: the shape of data the workflow accepts from a triggeroutputSchema: the data passed to downstream nodes
Use the root { ... } syntax (not node root).
Regular nodes
Additional nodes use node name { ... }. Each node has a type and type-specific configuration. Downstream nodes define only schema (the node’s output shape). Their input is derived from the outputs of upstream nodes, accessed via context.nodes.
Flow block
The flow { ... } block defines edges between nodes.
source -> target: a simple edgesource -["label"]-> target: a labeled edge, used withswitchnodes
Example: multi-node workflow
This workflow normalizes form input, summarizes it with an LLM, and sends a confirmation email.
schema contact_payload {
label: "Contact message"
schema: @json {
{
"type": "object",
"required": ["name", "email", "message"],
"properties": {
"name": { "type": "string" },
"email": { "type": "string" },
"message": { "type": "string" }
},
"additionalProperties": false
}
}
}
workflow process_form {
label: "Process Form"
description: "Normalize input, summarize with LLM, send email"
root {
type: code
label: "Entry"
inputSchema: contact_payload
outputSchema: contact_payload
code: @ts {
const { name, email, message } = context.nodes.root.input
return {
name: name.trim(),
email: email.trim().toLowerCase(),
message: message.trim(),
}
}
}
node summarize {
type: ai
kind: object
label: "Summarize"
schema: @json {
{ "type": "object", "required": ["text"], "properties": { "text": { "type": "string" } } }
}
model: "gpt-4o-mini"
prompt: @ts {
const { name, message } = context.nodes.root.output
return `Summarize this message from ${name}: ${message}`
}
}
node notify {
type: email
label: "Send confirmation"
from: @ts { return "[email protected]" }
to: @ts { return context.nodes.root.output.email }
subject: @ts { return "We received your message" }
text: @ts {
const summary = context.nodes.summarize.output.text ?? ""
return `Thanks for reaching out. Summary: ${summary}`
}
}
flow {
root -> summarize
summarize -> notify
}
}Branching with switch nodes
A switch node routes execution to one of several labeled branches. The router function returns the name of the branch to follow. Each branch is then connected using a labeled edge in the flow block.
schema score_payload {
label: "Score input/output"
schema: @json {
{
"type": "object",
"required": ["score"],
"properties": { "score": { "type": "number" } },
"additionalProperties": false
}
}
}
schema route_branch_result {
label: "Switch branch result"
schema: @json {
{
"type": "object",
"required": ["result"],
"properties": { "result": { "type": "string" } },
"additionalProperties": false
}
}
}
workflow route_by_score {
label: "Route by Score"
root {
type: code
label: "Entry"
inputSchema: score_payload
outputSchema: score_payload
code: @ts {
return { score: Number(context.nodes.root.input.score) || 0 }
}
}
node route {
type: switch
label: "Route"
cases: ["high", "low"]
router: @ts {
return context.nodes.root.output.score > 50 ? "high" : "low"
}
}
node handle_high {
type: code
label: "High path"
schema: route_branch_result
code: @ts { return { result: "High priority" } }
}
node handle_low {
type: code
label: "Low path"
schema: route_branch_result
code: @ts { return { result: "Low priority" } }
}
flow {
root -> route
route -["high"]-> handle_high
route -["low"]-> handle_low
}
}Further reading
- Node types: all available node types and their configuration
- Context: type-safe access to node outputs via
context.nodes - Streams: typed workflow output persisted for other workflows and integrations