Sub-Workflows¶
A sub_workflow step runs another workflow as a child of the current
run and waits for it to finish. Use it when a sequence of steps is
shared by several workflows (a standard approval chain, a mailbox
provisioning routine) and you would otherwise copy those steps into
every parent.
Authoring a sub-workflow step¶
- Drag Sub-Workflow from the Integrations group of the step palette onto the canvas, or drop it directly on a connector to splice it into an existing edge.
- Open the step's Configuration tab and pick the Child workflow from the searchable dropdown. The list shows every workflow you can read; the workflow you are currently editing is excluded (see Self-reference).
- Fill in the mappings described below.
The step has a single success outgoing edge plus the usual error
edge, so it behaves like any other non-branching step in the graph.
Input mappings¶
Once a child is selected, the panel renders one row per variable the child workflow declares. Each row asks for the parent-side value that feeds it:
| Child variable | Parent value you supply |
|---|---|
email |
requestor.email |
department |
targetUser.department |
The parent-side value is a variable name or a dotted path into the parent run's variables. Leave a row blank to pass nothing for that child variable — the child then falls back to its own default, or fails at start if the variable is required.
Because the rows come from the child's declared variables, you cannot map a value into an input the child does not have. If you add a variable to the child workflow later, reopen the parent step to see the new row.
Output mappings¶
Outputs flow the other way and are author-added, because a child run can produce variables from its steps that it never declared up front:
| Parent variable to write | Child value to read |
|---|---|
mailboxId |
createdId |
approvalOutcome |
approval_1.decision |
Each row writes one parent variable when the child run completes successfully. If the child fails or is cancelled, the engine returns before the output-mapping loop, so parent variables are left unchanged. A row with an empty parent name is invalid and blocks save — finish or delete it.
Three names are rejected as keys in either mapping: __proto__,
constructor, and prototype. Both input and output mappings become
property writes (childVars[…] / parent variables[…]), so those
names are refused at every layer rather than allowed to reach a write.
Self-reference and recursion¶
A sub-workflow step cannot point at the workflow that contains it. The designer omits the current workflow from the picker, and save, publish, dry-run validation, and configuration import all reject the combination independently — so a hand-edited export cannot smuggle one in.
That rule only catches the single-hop case. Workflow A calling B
calling A is invisible to any single workflow's validator, so the
engine enforces a runtime ceiling as well: a sub_workflow step
refuses to start a child run once the chain of parent runs is already
10 deep. The step fails with Sub-workflow nesting limit of 10
reached and routes through the step's error edge if one is declared.
Ten levels is a source constant (WorkflowEngine.MAX_SUB_WORKFLOW_DEPTH),
not an environment setting. If you hit it, the workflow almost
certainly has a cycle rather than a legitimately deep hierarchy.
Runtime behavior¶
- The child run is created with
parent_run_idset to the parent run, which is what the depth walk follows. - The parent step stays in progress until the child run reaches a terminal state; a failed child fails the parent step.
- A child that a caller can no longer read still runs — authorization is evaluated when the author selects the workflow, not on every execution.
- A stored config the validator would reject today (from a legacy row or an import that predates these rules) fails the step at start rather than being partially applied.
Related¶
- Fork/Join Branches — for running steps in parallel inside one workflow, rather than delegating to another workflow.
- Versioning & Deprecation — a parent references a workflow by id, so publishing a new version of the child does not repoint existing parents.