Parallel Execution

Run a small set of independent workflow branches concurrently, durably, and with one deterministic parent continuation.

Durable branches

Parallel is a structured fan-out/fan-in node. The parent pauses as awaiting_parallel; a durable coordinator admits isolated branch items up to the configured concurrency and resumes the parent exactly once.

json
{
  "type": "Parallel", "id": "enrich",
  "execution": { "mode": "durable", "max_concurrency": 2 },
  "branches": [
    { "id": "crm", "children": [{ "type": "Tool", "id": "lookup", "tool": "crm_lookup" }] },
    { "id": "profile", "children": [{ "type": "Code", "id": "score", "expression": "function () { return {}; }" }] }
  ]
}

Completed branch outputs are available under their branch IDs, for example {{enrich.crm.lookup}}. Branches read frozen parent state and cannot read sibling-local state.

Bulk child calls

WorkflowCall.for_each expands one pinned child workflow deployment across a parent array. The collection is evaluated once, each item receives isolated aliases while its input is resolved, and the parent resumes with results in source order.

json
{
  "type": "WorkflowCall", "id": "score-leads", "workflow_id": "lead-scorer",
  "for_each": { "items": "{{leads}}", "item": "lead", "index": "index" },
  "input": { "lead": "{{lead}}", "position": "{{index}}" },
  "join": { "mode": "all" },
  "failure": { "mode": "collect" },
  "execution": { "mode": "durable", "max_concurrency": 10 }
}

Bulk calls are always awaited. Durable batches create pinned child runs; ephemeral batches run eligible synchronous callees inline and never create child runs or fan-out rows. The result shape is { items: [{ index, status, output, error }], __join__: { total, succeeded, failed, cancelled } }. Retrying applies only to durable items.

The canonical bulk-contact-enrichment-100.json example generates a deterministic batch and admits ten pinned child calls at a time.

Joins and failures

SettingMeaning
allWait for every successful branch.
anyContinue after the first success; unstarted losers are cancelled.
nContinue after a configured number of successes.
fail_fastFail when the success quorum cannot be reached.
collect / skipOnly with all: wait for all items despite failures.

Inspecting runs

The dashboard exposes fan-out progress on the run. Operators can also use the management API, CLI, or MCP tools.

bash
chatterfly runs fanouts <run-id>
chatterfly runs retry-item <run-id> <fanout-id> <item-id> --confirm
chatterfly runs cancel-item <run-id> <fanout-id> <item-id> --confirm
chatterfly runs retry-items <run-id> <fanout-id> <item-id>... --confirm
chatterfly runs cancel-items <run-id> <fanout-id> <item-id>... --confirm

Retrying a failed or cancelled item restarts its branch from the frozen parent snapshot. Previous checkpoints, awaited-child references, output, and errors are cleared.

MCP offers get_run_fanouts and paginated get_run_fanout_items; pass its optional status filter to page active, completed, failed, or cancelled items before the cursor is applied. It also provides confirmation-gated single-item and explicit batch retry/cancel tools. Batch actions allow one to 100 items and reject the entire request when any item is ineligible.

Current limits

Warning: Durable Parallel supports synchronous branch work, Delay, Agent checkpoints, awaited child workflows, Input, Editor, forms, turn-based converse, and nested Parallel. Participant prompts are delivered serially per role. Live converse remains rejected.

Operators can pause new admissions while preserving queued items with tenant setting fanout_enabled: false, or pause selected workflows with fanout_disabled_workflow_ids. Blocked queues retry admission every minute and resume when the setting is re-enabled.

WorkflowCall.for_each supports up to 1,000 source items, 100 concurrent calls, 64 KiB of resolved input per item, and a 1 MiB aggregate result. execution.mode: "ephemeral" is limited to synchronous callees and requires explicit at-least-once acknowledgement because accepted API calls can repeat after a worker restart. Connection-backed provider calls are capped per process at 20 per provider and 5 per concrete connection; mutations without a provider idempotency header are rejected, and supported operations receive a stable item key unless the workflow supplies one. Its aggregate metrics can be suppressed with observability: "none", but it never persists live item progress. Its result can collect ordered envelopes or discard them after aggregation. See the workflow DSL reference for the broader node model.