Skip to main content

Orchestration Control Flow: Delay, Poll, and Loop

The control-flow nodes of the Orchestrations module pace, wait, repeat and branch a run without calling an LLM. You will build a sub-orchestration, a builtin tool for the poll node, and a main orchestration wiring delay → poll → loop → condition → transform, then inspect the per-node executions.

Everything is deterministic; no AI provider is required. For agent nodes see Orchestrate a Sonnet; the reference table maps every node type to where it is demonstrated.

Prerequisites​

export SOAT_BASE_URL=http://localhost:5047

Step 1 — Log in as admin​

Admin is the built-in superuser role (Users).

ADMIN_TOKEN=$(soat login-user --username admin --password Admin1234! | jq -r '.token')
export SOAT_TOKEN=$ADMIN_TOKEN

Step 2 — Create a project​

Every resource lives inside a project.

PROJECT_ID=$(soat create-project --name "Node Tour" | jq -r '.id')
echo "PROJECT_ID: $PROJECT_ID"

Step 3 — Create the per-item sub-orchestration​

The loop node runs a child orchestration once per collection item, injecting each element as item in the sub-run's input namespace. This child echoes it through a transform node, reading it with {"var": "input.item"}.

SUB_ORCH_ID=$(soat create-orchestration \
--project-id "$PROJECT_ID" \
--name "Process One Item" \
--nodes '[{"id":"echo","type":"transform","expression":{"var":"input.item"},"state_mapping":{"state.processed":{"var":"output.result"}}}]' \
--edges '[]' | jq -r '.id')
echo "SUB_ORCH_ID: $SUB_ORCH_ID"

Step 4 — Create the tool the poll node calls​

A poll node calls a Tool until a JSON Logic exit condition on its response holds. This builtin tool reads the project back via get-project; it is already readable, so the poll exits on the first attempt.

CHECK_TOOL_ID=$(soat create-tool \
--project-id "$PROJECT_ID" \
--name "read-project" \
--type "builtin" \
--description "Read this project so the poll node can check readiness" \
--actions '["get-project"]' \
--preset-parameters '{"project_id": "'"$PROJECT_ID"'"}' | jq -r '.id')
echo "CHECK_TOOL_ID: $CHECK_TOOL_ID"

Step 5 — Create the control-flow orchestration​

This orchestration chains the control-flow nodes:

  • delay — waits duration: suffix form (1s, 5m, 2h) or ISO 8601 (PT1S).
  • poll — calls the tool each attempt until exit_condition is truthy, bounded by interval + max_iterations; evaluated against run state plus response (latest tool result) and attempt. See Polling.
  • loop — runs orchestration_id (the same field sub_orchestration uses) once per element of collection, injected as item_variable. See Loops.
  • condition — emits a label; outgoing edges select a branch with condition: "<label>".
  • transform — terminal node on each branch.
ORCH_NODES='[
{"id":"pace","type":"delay","duration":"1s"},
{"id":"wait-ready","type":"poll","tool_id":"'"$CHECK_TOOL_ID"'","operation_id":"get-project","exit_condition":{"==":[{"var":"response.id"},"'"$PROJECT_ID"'"]},"interval":"1s","max_iterations":5,"state_mapping":{"state.project":{"var":"output.result"}}},
{"id":"process-each","type":"loop","orchestration_id":"'"$SUB_ORCH_ID"'","collection":"state.input.items","item_variable":"item","state_mapping":{"state.results":{"var":"output.results"}}},
{"id":"route","type":"condition","expression":{"if":[{"var":"results"},"processed","none"]}},
{"id":"summary-processed","type":"transform","expression":"Items were processed.","state_mapping":{"state.summary":{"var":"output.result"}}},
{"id":"summary-none","type":"transform","expression":"No items to process.","state_mapping":{"state.summary":{"var":"output.result"}}}
]'

ORCH_EDGES='[
{"from":"pace","to":"wait-ready"},
{"from":"wait-ready","to":"process-each"},
{"from":"process-each","to":"route"},
{"from":"route","to":"summary-processed","condition":"processed"},
{"from":"route","to":"summary-none","condition":"none"}
]'

ORCHESTRATION_ID=$(soat create-orchestration \
--project-id "$PROJECT_ID" \
--name "Control Flow Tour" \
--description "delay, poll, loop, condition" \
--nodes "$ORCH_NODES" \
--edges "$ORCH_EDGES" | jq -r '.id')
echo "ORCHESTRATION_ID: $ORCHESTRATION_ID"

Step 6 — Run it​

Start a run with a list of items; the condition routes to summary-processed because the results are non-empty.

RUN=$(soat start-orchestration-run \
--orchestration-id "$ORCHESTRATION_ID" \
--input '{"items":["alpha","beta","gamma"]}')

printf '%s\n' "$RUN" | jq '{status, output}'
RUN_ID=$(printf '%s\n' "$RUN" | jq -r '.id')
echo "RUN_ID: $RUN_ID"

Expected output:

{
"status": "completed",
"output": {
"summary-processed": {
"result": "Items were processed."
}
}
}

Step 7 — Inspect the per-node executions​

get-orchestration-run returns the run state; list-orchestration-run-node-executions returns one record per node attempt.

soat get-orchestration-run \
--orchestration-run-id "$RUN_ID" | jq '{status, state: {results: .state.results, summary: .state.summary}}'

soat list-orchestration-run-node-executions \
--orchestration-run-id "$RUN_ID" | jq '[.data[] | {node_id, node_type, status}]'

Look for:

  • state.results — one entry per item, the sub-orchestration's output.
  • state.summary — "Items were processed.", written by the selected branch.
  • the node executions — wait-ready completed after one attempt; summary-none is skipped (the condition routed away from it).

How It Works​

Full semantics: Orchestrations — Node Types. poll exhausts max_iterations by completing with condition_met: false unless fail_on_timeout: true; condition records the unselected branch as skipped.

To see the none branch, run again with --input '{"items":[]}': loop produces [], the condition evaluates to none, and summary-none runs.

Every node type​

All node types:

NodeWhere to see it
delayThis tutorial — Step 5
pollThis tutorial — Step 5 (Polling)
loopThis tutorial — Step 5 (Loops)
conditionThis tutorial; Conditional Branching
transformThis tutorial; Orchestrate a Sonnet
toolThis tutorial (the poll's builtin tool); Orchestrate a Sonnet
sub_orchestrationSame orchestration_id field the loop uses here — runs a child orchestration as a single step
agentOrchestrate a Sonnet, Multi-Agent Orchestration
knowledgeKnowledge — searches a knowledge source into state
humanOrchestrations — Node Types — pauses the run for external input
webhookOrchestrations — Node Types — emits or awaits an HTTP callback