How-to guide
Branch, loop and run in parallel
Route with if_else and switch, run branches concurrently, iterate with foreach and repeat_while, and use loopback safely.
Prerequisites
- A basic workflow ([Build a workflow](build-a-workflow.md))
If / else#
An if_else evaluates its branches top to bottom and takes the first match. A branch with "condition": null is the else branch. Each branch declares its own next, so the activity has no next of its own.
{
"id": "a3",
"type": "if_else",
"inputs": [ ... ],
"branches": [
{
"branchId": "b1",
"label": "High value VIP",
"condition": { "type": "expression", "body": "finalValue > 1000 && isVip === true" },
"next": "a4"
},
{ "branchId": "b_else", "label": "Standard", "condition": null, "next": "a5" }
]
}
Switch#
switch resolves a variable path and routes to the matching case. Combined with an llm_decision, it is the main pattern for AI-driven routing.
{
"id": "a_sw",
"type": "switch",
"switch": {
"on": "a_llm.output.decision",
"cases": [
{ "value": "low", "next": "a_approve" },
{ "value": "high", "next": "a_decline" }
],
"default": "a_review"
}
}
Parallel branches#
Branches run concurrently. Each branch holds an inline activities array, and outputs are referenced by activity id directly (not namespaced by branch). The activity's own next runs after every branch has finished.
{
"id": "a_checks",
"type": "parallel",
"parallel": {
"branches": [
{ "id": "b_credit", "name": "Credit check", "activities": [ { "id": "a_credit", "type": "js_script", "script": { "body": "output.approved = true;" } } ] },
{ "id": "b_stock", "name": "Stock check", "activities": [ { "id": "a_stock", "type": "js_script", "script": { "body": "output.inStock = true;" } } ] }
]
},
"outputs": [
{ "name": "creditApproved", "from": "a_credit.output.approved" },
{ "name": "inStock", "from": "a_stock.output.inStock" }
],
"next": "a_next"
}
A parallel activity needs two or more branches. Use merge when you need to combine results with a strategy (combine, array, coalesce, sum, avg).
Loops#
foreachiterates over an array (itemsis a path such asa_receive_order.output.lineItems), running itsbodyactivities for each item withitemVariablebound. Its outputs areitemCountandresults.repeat_whilerepeats itsbodywhile aconditionholds, up tomaxIterations. Its outputs areiterationCountandresults.
{ "repeat_while": { "condition": "context.get('globals.status') !== 'done'", "maxIterations": 1000, "body": [] } }
Loopback connections#
You can draw a connection back to an earlier activity. The rule is that every cycle must contain at least one if_else or switch, because that is where the exit condition lives. Three layers enforce it:
- In Studio, the canvas refuses a back-edge that would create a cycle without a branching node.
- At load time, cycle detection rejects an invalid workflow with a validation error such as: "Loopback connections require a branching condition (if_else or switch) in the cycle."
- At run time, a visit counter stops any activity that runs more than
maxLoopIterationstimes (default 1000, override per workflow). This guards against an exit condition that never fires.
{ "workflowId": "wf_poll", "maxLoopIterations": 10 }
Waiting#
Use delay (seconds or milliseconds) for timed or SLA-driven pauses, and approval or external_task to wait for people or other systems. See Add approvals and external tasks.
Expected result
- Execution follows the intended branch, branches run concurrently, and loops end