Blocks

Call workflow

Run another workflow synchronously as a function call: the upstream payload is its input, its final output is this step's output. Failures and call depth.

Run another workflow as a function call: the upstream payload becomes the target's input, the run waits, and the target's final output becomes this step's output. The other half of the sub-workflow trigger.

What it does

The step creates a child run of the target against the target's active snapshot, with this step's input payload as the child's inputs, and then either waits for it or lets it go, depending on Mode. The child is a full run of its own: its own entry on the runs page, its own step history, its own approvals. It records which run called it and how deep in the chain it sits.

In wait mode the parent halts at this step and shows as waiting; its worker exits, and when the child reaches a terminal state the parent resumes with the child's output. Inside a loop body the child instead runs to completion within the iteration, because a loop can't pause mid-iteration; the result is the same from the body's point of view.

Config

The call workflow step in the Inspector One picker: the target workflow.

  • Target: the workflow to call. It must have a sub-workflow trigger and be active; the picker steers you toward workflows that qualify. The target is looked up through the run's workspace, so a workflow in another workspace doesn't resolve.
  • Mode: wait (the default) pauses here until the child finishes and uses its output; a failed child fails this step. detach starts the child and moves on at once; this step outputs the child's ids, and the child's failure does not cascade.

Output

In wait mode, whatever the called workflow's terminal step emitted. Document your sub-workflows' output contracts, because the caller is coupled to them. A "notify team" sub-workflow ending in a send-email step, for instance, returns that step's output:

{ "ok": true, "id": "<message id>", "to": ["ops@example.com"], "subject": "…" }

In detach mode the output is the handle to the child run:

{
  "detached": true,
  "execution_id": "…",
  "workflow_id": "…",
  "workflow_name": "Notify team"
}

{{ payload.execution_id }} can be stored or logged, but the parent never hears the child's result.

Verification

The verify kind is write_preview: invoking another workflow is a side effect, so the verification run never starts the child. It previews the call and gives downstream steps a simulated envelope (ok, result, and a test_note saying so). Run the parent for real to see the child's true output flow through.

Patterns

  • Shared tail: one "notify the team" workflow every other workflow calls; changing the channel is one edit.
  • Decomposition: a monster pipeline split into testable, reusable parts.
  • Fan-out router: a listener workflow receives every event and detaches one sub-workflow per event type. Each child has its own approvals and none blocks the others.
  • Agents can do this too: an AI step with a workflow tool decides when to call, under the same depth rule.

Gotchas

  • Failure cascades in wait mode: if the called workflow fails, this step fails. Set this step's on-error to continue and wire the error port (see error handling), or use detach, when the parent should survive.
  • The parent waits synchronously, so a slow sub-workflow is a slow parent. Call depth is bounded, so cycles stop at the limit rather than recursing forever: a top-level run is depth 0, and a run at depth 3 refuses to start another call.
  • A step that targets the workflow it lives in is refused, both by the canvas lint and at run time: the run would wait on itself forever.
  • The child's first step sees the payload only if the target's sub-workflow trigger has Pass payload through on. With it off, the child starts from {}.
  • An inactive target is a step error, not a silent skip. Activate the callee first.

Related