Skip to content

Block Types

Sequences are composed of blocks. Beyond simple steps, Orch8 provides composite blocks for iteration, parallelism, branching, error handling, child workflows, A/B testing, and cancellation control.

ForEach

Iterate over a collection in context.data, executing the body blocks once per item. The collection is snapshotted on first execution — mutations during iteration are ignored.

Definition

{
  "type": "for_each",
  "id": "process-users",
  "collection": "users",
  "item_var": "user",
  "max_iterations": 500,
  "body": [
    {
      "type": "step",
      "id": "send-welcome",
      "handler": "http_request",
      "params": {
        "url": "https://api.mail.com/send",
        "method": "POST",
        "body": {
          "to": "{{user.email}}",
          "template": "welcome"
        }
      }
    }
  ]
}

Parameters

FieldTypeDefaultDescription
collectionstringrequiredDotted path to array in context.data (e.g. "data.items")
item_varstring"item"Variable name bound to current item in each iteration
max_iterationsu321000Safety limit. Hard cap at 1,000,000 regardless of this value.
bodyBlock[]requiredBlocks executed per iteration. Can contain any block type.

Behavior

Collection Snapshot

On first tick, the collection is resolved and persisted as a snapshot. Later mutations to context.data do not affect iteration.

Item Binding

Each iteration merges { item_var: current_item } into context.data before executing body blocks.

Sequential Execution

Body blocks run in order within each iteration. Next iteration starts only after all body blocks complete.

Failure Propagation

If any body block fails, the ForEach node fails immediately. Remaining iterations are skipped.

Empty Collection

If the collection resolves to empty or doesn't exist, ForEach completes immediately without error.

Subtree Reset

Between iterations, body nodes reset to Pending. Worker tasks and composite markers are cleaned up; step outputs are preserved.

Output

// Marker persisted per iteration boundary
{
  "_index": 3,          // Current iteration (0-based)
  "_total": 10,         // Total items in snapshot
  "_item_var": "user",  // Bound variable name
  "_items": [...]       // Full collection snapshot
}

SubSequence

Spawn a child workflow instance from another sequence definition. The parent waits until the child completes, then continues with the child's outputs available.

Definition

{
  "type": "sub_sequence",
  "id": "run-enrichment",
  "sequence_name": "data-enrichment-pipeline",
  "version": null,
  "input": {
    "user_id": "{{context.data.user_id}}",
    "source": "onboarding"
  }
}

Parameters

FieldTypeDescription
sequence_namestringName of the child sequence (resolved by tenant + namespace)
versionint | nullSpecific version to use. Null = latest non-deprecated.
inputJSON objectInitial context.data for the child instance

Behavior

Child Creation

A new instance is created with the specified sequence, inheriting priority, timezone, and session_id from the parent.

Parent Waiting

Parent node transitions to Waiting. Uses zero scheduler resources while waiting for the child.

Child Completion

When child reaches Completed, all child outputs become the SubSequence block's output.

Child Failure

If child reaches Failed or Cancelled, the SubSequence node fails, which may trigger parent's retry or error handling.

External Wakeup

If the child's state is changed externally (via API), the parent is automatically woken to observe the new state.

Not Found

If the named sequence doesn't exist for the tenant, the node fails with a permanent error.

Output

// SubSequence output = array of all child block outputs
[
  { "block_id": "fetch-data", "output": { "status": 200, ... } },
  { "block_id": "transform", "output": { "records": 150 } }
]

Use Cases

  • Reusable pipelines — define once, invoke from multiple parent workflows
  • Isolation — child failures don't corrupt parent state
  • Independent retry — child can be retried from DLQ without affecting parent
  • Version pinning — lock child to a specific version while parent uses latest

Loop

Repeat body blocks while a condition evaluates to true. Useful for polling, retry patterns, or iterative refinement.

{
  "type": "loop",
  "id": "poll-status",
  "condition": "{{context.data.status != 'ready'}}",
  "max_iterations": 100,
  "body": [
    {
      "type": "step", "id": "check",
      "handler": "http_request",
      "params": { "url": "https://api.example.com/status" }
    },
    {
      "type": "step", "id": "wait",
      "handler": "sleep",
      "params": { "duration_ms": 5000 }
    }
  ]
}
FieldDefaultDescription
conditionrequiredExpression evaluated before each iteration. Loop continues while truthy.
max_iterations1000Safety limit to prevent infinite loops.

Parallel

Execute multiple branches concurrently. All branches must complete (or one must fail) before the parallel block resolves.

{
  "type": "parallel",
  "id": "enrich-data",
  "branches": [
    [
      { "type": "step", "id": "geo-lookup", "handler": "http_request",
        "params": { "url": "https://geo.api/lookup?ip={{context.data.ip}}" } }
    ],
    [
      { "type": "step", "id": "credit-check", "handler": "http_request",
        "params": { "url": "https://credit.api/check?user={{context.data.user_id}}" } }
    ],
    [
      { "type": "step", "id": "fraud-score", "handler": "http_request",
        "params": { "url": "https://fraud.api/score" } }
    ]
  ]
}

All branches execute simultaneously. If any branch fails, the parallel block fails. All branch outputs are collected and available to subsequent steps.

Race

Execute multiple branches concurrently. The first branch to complete wins — remaining branches are cancelled.

{
  "type": "race",
  "id": "fastest-provider",
  "branches": [
    [
      { "type": "step", "id": "provider-a", "handler": "http_request",
        "params": { "url": "https://provider-a.com/data" } }
    ],
    [
      { "type": "step", "id": "provider-b", "handler": "http_request",
        "params": { "url": "https://provider-b.com/data" } }
    ]
  ]
}

Use Race for timeout patterns (race business logic against a sleep step), redundant calls to multiple providers, or speculative execution.

Router

Conditional branching based on expressions. Evaluates conditions in order and executes the first matching branch.

{
  "type": "router",
  "id": "route-by-tier",
  "routes": [
    {
      "condition": "{{context.data.plan == 'enterprise'}}",
      "blocks": [
        { "type": "step", "id": "dedicated-support", "handler": "http_request",
          "params": { "url": "https://support.internal/enterprise" } }
      ]
    },
    {
      "condition": "{{context.data.plan == 'pro'}}",
      "blocks": [
        { "type": "step", "id": "priority-queue", "handler": "http_request",
          "params": { "url": "https://support.internal/priority" } }
      ]
    }
  ],
  "default": [
    { "type": "step", "id": "standard-queue", "handler": "http_request",
      "params": { "url": "https://support.internal/standard" } }
  ]
}

If no condition matches and no default is provided, the router completes with no output. The default branch acts as an else clause.

TryCatch

Error handling with try, catch, and finally branches. The catch branch receives error context. The finally branch always executes regardless of success or failure.

{
  "type": "try_catch",
  "id": "safe-payment",
  "try_block": [
    { "type": "step", "id": "charge", "handler": "http_request",
      "params": { "url": "https://api.stripe.com/v1/charges", "method": "POST" } }
  ],
  "catch_block": [
    { "type": "step", "id": "alert-team", "handler": "http_request",
      "params": { "url": "https://slack.webhook/alerts", "method": "POST",
        "body": { "text": "Payment failed for {{context.data.user_id}}" } } }
  ],
  "finally_block": [
    { "type": "step", "id": "audit-log", "handler": "http_request",
      "params": { "url": "https://audit.internal/log", "method": "POST" } }
  ]
}

Cancellation safety: Steps in thefinally branch are automatically non-cancellable. They execute even if a cancel signal arrives during the try or catch phase.

ABSplit

Route execution to one of N weighted variants. Used for A/B testing workflow paths, canary deployments, or gradual rollouts.

{
  "type": "ab_split",
  "id": "onboarding-test",
  "variants": [
    {
      "name": "control",
      "weight": 70,
      "blocks": [
        { "type": "step", "id": "classic-onboard", "handler": "http_request",
          "params": { "url": "https://api.internal/onboard/v1" } }
      ]
    },
    {
      "name": "variant_a",
      "weight": 30,
      "blocks": [
        { "type": "step", "id": "new-onboard", "handler": "http_request",
          "params": { "url": "https://api.internal/onboard/v2" } }
      ]
    }
  ]
}
FieldDescription
nameVariant identifier (for tracking/reporting)
weightRelative weight. Ratio determines probability (e.g., 70:30 = 70% control, 30% variant)
blocksBlocks executed for this variant

Selection is deterministic per instance (based on hashing). The same instance always routes to the same variant if re-executed.

CancellationScope

Protect a group of blocks from external cancel signals. Blocks inside a cancellation scope continue to completion even when the instance receives a cancel signal.

{
  "type": "cancellation_scope",
  "id": "critical-section",
  "blocks": [
    { "type": "step", "id": "debit-account", "handler": "http_request",
      "params": { "url": "https://bank.api/debit" } },
    { "type": "step", "id": "credit-recipient", "handler": "http_request",
      "params": { "url": "https://bank.api/credit" } },
    { "type": "step", "id": "send-receipt", "handler": "http_request",
      "params": { "url": "https://email.api/receipt" } }
  ]
}

Use CancellationScope for financial transactions, multi-step operations that must be atomic, or cleanup sequences that should never be interrupted.

Three ways to protect from cancellation:

  • 1. CancellationScope block — protects a group
  • 2. Per-step cancellable: false flag — protects one step
  • 3. finally branch in TryCatch — always non-cancellable