Skip to main content

Updated Sep 30, 2026

Operations (step types)

Each step in an agent graph has an op that tells the runner what to do next. Steps share common fields: id (must match the key in the steps map), next (empty string "" ends the path), optional out for where results are stored, and an op-specific block (call, agent, loop, etc.).

Build with AI

See Vibe coding and the flow authoring guide for complete JSON patterns.

For AI assistants

Authoritative JSON shapes and edge cases (especially LOOP scheduling) are in the flow authoring guide.

Summary​

opPurposeConfig block
CALLInvoke a functioncall.function_id, call.args
AGENTRun an LLM (optional tools)agent — see LLM steps
LOOPIterate an arrayloop.over, loop.var, loop.step
CONDITIONContinue only if truecondition.condition
BRANCHStart parallel pathsbranch.parallel_paths
JOINWait for all branches(no extra config)
TRANSFORMTemplate format or regex extracttransform
WAITPause wall-clockwait.duration
SPAWNRun a subplanspawn.plan_id, spawn.input

CALL​

Invokes one function by id (for example semantic.query). On failure the step errors and the current path stops.

{
"id": "fetch_accounts",
"op": "CALL",
"next": "summarize",
"out": { "set": "@.rows" },
"call": {
"function_id": "semantic.query",
"args": {
"query": "SELECT account_id, name FROM accounts LIMIT 50"
}
}
}

See Functions reference.

AGENT​

Runs an LLM with prompts and optional tools. Detailed options are on LLM steps.

LOOP​

Repeats a child step for each element in an array. Scheduling is breadth-first: for body steps A → B, the engine runs A for all items before any B. Do not rely on a global written in A being read in B within the same iteration—use locals within the iteration and out.append on globals only when aggregating after all iterations.

Max 1000 iterations.

{
"id": "each_ticket",
"op": "LOOP",
"next": "",
"loop": {
"over": "tickets.results",
"var": "ticket",
"step": "classify_one"
}
}

CONDITION​

If the condition expression is true, execution follows next. If false, the current path stops (this is not an if/else). Use BRANCH with separate paths when you need alternatives.

{
"id": "gate",
"op": "CONDITION",
"next": "notify",
"condition": {
"condition": "$.has_results"
}
}

BRANCH and JOIN​

BRANCH schedules multiple entry steps at once; JOIN waits until all paths reach it before continuing.

{
"id": "fan_out",
"op": "BRANCH",
"next": "join_all",
"branch": {
"parallel_paths": ["path_slack", "path_email"]
}
}
{
"id": "join_all",
"op": "JOIN",
"next": "done"
}

TRANSFORM​

format applies a Go text/template to an input object. regexp_extract pulls a capture group from a string.

{
"id": "format_body",
"op": "TRANSFORM",
"next": "send",
"out": { "set": "@.body" },
"transform": {
"type": "format",
"input": "$.payload",
"template": "Hello {{ $.input.name }}"
}
}

WAIT​

Pauses for a duration string (e.g. "5s", "10m", "1h").

{
"id": "pause",
"op": "WAIT",
"next": "after_pause",
"wait": { "duration": "5m" }
}

SPAWN​

Starts a subplan defined under config.subplans in parallel with the parent.

{
"id": "side_effect",
"op": "SPAWN",
"next": "continue_main",
"spawn": {
"plan_id": "audit_trail",
"input": { "note": "{{ $.reason }}" }
}
}

Rules that prevent silent failures​

  • The id field must equal the step’s key in the steps map.
  • The last step on a path should use "next": "".
  • Prefer globals with the @. prefix when a later step outside the same loop/branch needs the value (Variables and data).