Loop

The loop primitive has two modes: foreach for deterministic batch processing and react for AI agent reasoning. Both share the same type: "loop". The mode property determines behavior.


Foreach Mode

Process an array of items with configurable concurrency and failure handling. Foreach is designed for bounded inline work. Use it with custom-table steps or registered HTTP actions that you explicitly mark as inline.

json
{
  "type": "loop",
  "properties": {
    "mode": "foreach",
    "items_path": "@input.orders",
    "item_variable_name": "order",
    "actions_to_execute": [
      {
        "type": "custom-table",
        "properties": {
          "table": "order_work_queue",
          "operation": "upsert",
          "key_fields": ["order_id"],
          "keys": ["order_id", "customer_id", "amount", "status"],
          "values": ["@order.id", "@order.customer_id", "@order.total", "ready"]
        }
      }
    ],
    "max_concurrency": 10,
    "failure_strategy": "continue_on_error",
    "collect_results": true,
    "result_key": "queuedOrders"
  }
}

Foreach Properties

Property Type Required Description
mode "foreach" Yes Selects foreach mode
items_path @path / doc: Yes Array of items to iterate over. Accepts @path references or doc: uploaded documents (CSV/JSON resolve to arrays)
item_variable_name string Yes Variable name for the current item (accessible as @{name})
iteration string No "inline" (default) runs actions_to_execute for each item. "child_run" starts one workflow run per item instead. See Child-Run Iteration
actions_to_execute array Yes Inline-safe steps to run for each item, such as custom-table operations and HTTP actions explicitly marked inline: true
max_concurrency number No Maximum parallel executions (default: 5, max: 50)
failure_strategy string No "continue_on_error" (default) or "fail_fast"
collect_results boolean No Whether to gather results from all iterations (default: true)
result_key string No Context key for collected results

Item Access

Inside actions_to_execute, reference the current item using the variable name:

json
{
  "item_variable_name": "order",
  "actions_to_execute": [
    {
      "type": "custom-table",
      "properties": {
        "table": "order_work_queue",
        "operation": "write",
        "keys": ["order_id", "amount", "currency"],
        "values": ["@order.id", "@order.total", "@order.currency"]
      }
    }
  ]
}

Failure Strategies

continue_on_error (default): Failed items are recorded but processing continues. Use when one rejected row should not stop the rest of the batch.

fail_fast: If any item fails, the loop stops and the run fails. Use when all items must succeed.


Child-Run Iteration

A foreach loop with iteration: "child_run" starts one governed run of another workflow for each item, instead of running actions_to_execute inline. Each child is an ordinary workflow run with its own record, so it can pause for an approval or run an agent. The parent run waits with status waiting_on_children and continues from the next step once, when every item has an outcome.

json
{
  "type": "loop",
  "properties": {
    "mode": "foreach",
    "iteration": "child_run",
    "workflow": "review-exception-packet",
    "items_path": "@packets",
    "item_variable_name": "packet",
    "max_children": 50,
    "max_concurrency": 5,
    "max_child_attempts": 2,
    "stop_after_consecutive_failures": 3,
    "failure_strategy": "continue_on_error",
    "result_key": "packetReviews"
  }
}

Each child run receives its item as input under item_variable_name, so the child workflow above reads it as @input.packet or @packet.

Child-Run Properties

Property Type Required Description
iteration "child_run" Yes Selects child-run iteration. Do not set actions_to_execute
workflow string Yes Id or name of the workflow to run for each item
items_path @path Yes Array of items. Each item starts one child run
item_variable_name string Yes Input key the item is passed under
max_children number No Most items the step accepts (default: 50, max: 200)
max_concurrency number No Children launched and running at once (default: 5, max: 50). A child paused for a person does not hold a slot, and a child resumed by a decision continues without waiting for one, so right after many decisions more children than this can briefly finish their remaining steps
child_timeout_ms number No Deadline for each child, counted from its launch (min: 60000, max: 604800000, seven days). Default: the longest expiry_seconds among the child workflow's approvals plus two hours, or two hours when the child workflow has no approval
max_child_attempts number No Most attempts per item, counting the first. Only a failed child is retried (default: 2, max: 3)
stop_after_consecutive_failures number No Stop launching after this many failed attempts in a row (default: 3, max: 10)
failure_strategy string No "continue_on_error" (default) or "fail_fast"
result_key string No Context key for the summary (default: childRunResults)

timeout_ms and collect_results apply to inline iteration only.

Every PbotApproval in the child workflow, including approvals in branches, must declare expiry_seconds and on_expiry: "reject" (Expiry) so no child can wait forever. The child workflow cannot contain a child-run loop of its own.

Outcomes

Each item ends with one outcome:

  • completed: the child run completed. applied and conflict are completed runs whose last custom-table transaction was applied, or met a conflict.
  • rejected: a person rejected an approval in the child.
  • expired: an approval in the child expired without a decision.
  • failed: the child failed for any other reason, after its retries.
  • timed_out: the child was cancelled at child_timeout_ms or at the batch deadline.
  • cancelled: the child was cancelled some other way.
  • not_started: the child never launched because the batch stopped.

Only failed is retried, and only failed counts toward stop_after_consecutive_failures. A rejection or an expiry is a business result for that item, so any number of them in a row never stops the batch.

When every item has an outcome, the step stores a summary under result_key:

json
{
  "status": "needs_attention",
  "item_count": 3,
  "counts": { "completed": 1, "rejected": 1, "expired": 1 },
  "unfinished": [],
  "needs_person": [2],
  "outcomes": [
    { "item_index": 0, "outcome": "completed", "child_run_id": "run-3f2b1c9e-8d4a-4c1e-9b7a-2e6f5d4c3b2a", "attempts": 1 },
    { "item_index": 1, "outcome": "rejected", "child_run_id": "run-7a1d0e44-52c3-4b8f-a0e6-91c2d3f4e5a6", "attempts": 1 },
    { "item_index": 2, "outcome": "expired", "child_run_id": "run-c0ffee12-3456-4789-8abc-def012345678", "attempts": 1 }
  ],
  "outcomes_truncated": false
}
  • status is completed when every item ended completed, applied, or rejected, and needs_attention otherwise. needs_attention is a business result, not an error: the run continues to the next step, which can branch on it.
  • unfinished lists items that ended failed, timed_out, cancelled, or not_started. needs_person lists items that ended expired or conflict. Items are listed by index, or by packet_id when the item is an object that has one.
  • halted is present when the batch stopped early: consecutive_failures, fail_fast, or batch_deadline.
  • outcomes has one entry per item, with its latest child run id and the number of attempts.

Each child run records its parent. GET /runs/:runId/status on a child returns parent_run_id, parent_step_index, and parent_item_index.

What Happens When

  • There are more items than max_children. Nothing launches. The step completes with status: "needs_attention", refused: "max_children", item_count, and max_children.
  • The batch could exceed the organization's LLM token ceiling. Before launching, the step multiplies the number of items by the total token budget of the agent steps in the child workflow (each step's llm_budget.max_total_tokens, or the default agent budget when it sets none). Above the ceiling (default: 50,000,000 tokens), nothing launches and the result is needs_attention with refused: "batch_llm_ceiling", batch_llm_tokens, and batch_llm_ceiling.
  • The organization is at its limit of active children. By default an organization runs at most 200 active child runs at once, across all parents. Paused children do not count, and launches another waiting parent has planned do; a parent that has ended holds no capacity. Parents plan their launches one at a time per organization, so two parents never spend the same capacity. Launches wait for capacity.
  • The item list is empty. The step completes at once with status: "completed" and item_count: 0.
  • A child fails. The item gets a new child run until max_child_attempts is used. When failed attempts in a row reach stop_after_consecutive_failures, nothing more launches, running children finish, and the remaining items are not_started.
  • failure_strategy is "fail_fast". The first item that ends failed stops the batch: running children are cancelled, the remaining items are not_started, and the step and the run fail. The run sends workflow_run_failed with reason child_run_fail_fast.
  • A child runs past child_timeout_ms. It is cancelled and ends timed_out. Time spent paused for a person counts.
  • The batch deadline passes. The deadline is child_timeout_ms plus two hours from the step's start. Running children are cancelled (timed_out), the remaining items are not_started, and halted is batch_deadline.
  • The parent run is cancelled. Its unfinished child runs are cancelled too, including a child that was still starting, and its unfinished items are settled as cancelled.
  • The step cannot start. The step and the run fail before anything launches, and the step result carries one of these error codes:
    • CHILD_RUN_ITEMS_INVALID: items_path does not resolve to an array
    • CHILD_RUN_WORKFLOW_REQUIRED: workflow is missing or empty
    • CHILD_RUN_WORKFLOW_NOT_FOUND: no workflow with that id or name exists in the organization
    • CHILD_RUN_SELF_REFERENCE: the loop names its own workflow
    • CHILD_RUN_NESTING: the child workflow contains a child-run loop
    • CHILD_RUN_APPROVAL_EXPIRY_REQUIRED: an approval in the child workflow does not declare both expiry_seconds and on_expiry: "reject"

React Mode

Run an AI agent that reasons step-by-step toward an objective. See Agents for full details.

json
{
  "type": "loop",
  "properties": {
    "mode": "react",
    "objective": "Investigate this expense report. Check policy compliance, verify receipts, recommend approval or rejection.",
    "tools": [
      { "type": "action", "name": "lookup_employee" },
      { "type": "action", "name": "check_expense_policy" },
      { "type": "action", "name": "verify_receipt" }
    ],
    "model": "gpt-4",
    "max_iterations": 15,
    "timeout_ms": 300000,
    "temperature": 0.7,
    "on_stuck": {
      "action": "escalate",
      "iterations": 3
    },
    "include_reasoning_trace": true,
    "result_key": "expenseDecision"
  }
}

React Properties

Property Type Required Description
mode "react" Yes Selects react mode
objective string Yes What the agent should accomplish. Supply the required facts directly, through a doc: reference, or through a declared tool
tools array Yes Available tools as typed declarations or action-name strings. See Tool Declarations
model string No LLM model to use (default: configured in environment)
max_iterations number No Maximum think-act-observe cycles (default: 10)
timeout_ms number No Maximum execution time in milliseconds (default: 300000)
temperature number No LLM temperature from 0 to 2 (default: 0.7)
on_stuck object No Recovery when agent loops without progress
on_stuck.iterations number No Repeated iterations before triggering (default: 3)
on_stuck.action string No "fail", "escalate", or "retry_with_hint"
on_stuck.hint string No Guidance text for retry_with_hint
include_reasoning_trace boolean No Include the reasoning trace in the loop result (default: true)
result_key string No Context key for the agent's final answer

When to Use Which Mode

Use Foreach When Use React When
You know exactly what to do with each item The task requires judgment or reasoning
Processing is deterministic The approach depends on intermediate results
Items are independent of each other The agent needs to decide what to do next
You need parallel processing You need natural language understanding

Composing batch work and agent review

actions_to_execute cannot contain another loop. For a governed batch investigation, persist the candidate rows with foreach, then let one top-level ReAct step read them through a fixed custom-table action. If every item needs a separate agent run, put the ReAct step in a child workflow and run it per item with child-run iteration, or start one bounded agent execution per item from your application and put that item's facts or document reference in its objective.

→ Next: Approval