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.).
See Vibe coding and the flow authoring guide for complete JSON patterns.
Authoritative JSON shapes and edge cases (especially LOOP scheduling) are in the flow authoring guide.
Summary
op | Purpose | Config block |
|---|---|---|
CALL | Invoke a function | call.function_id, call.args |
AGENT | Run an LLM (optional tools) | agent — see LLM steps |
LOOP | Iterate an array | loop.over, loop.var, loop.step |
CONDITION | Continue only if true | condition.condition |
BRANCH | Start parallel paths | branch.parallel_paths |
JOIN | Wait for all branches | (no extra config) |
TRANSFORM | Template format or regex extract | transform |
WAIT | Pause wall-clock | wait.duration |
SPAWN | Run a subplan | spawn.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
idfield must equal the step’s key in thestepsmap. - 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).