Skip to main content

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).