SubFlow
A SubFlow is another Flow execution under the parent Flow.
SubFlow has two main uses:
- Move complicated logic into a smaller Flow. This makes business logic easier to maintain and keeps its hierarchy clear.
- Fan out work. One Flow execution cannot concurrently execute too many operations, but it can delegate work to multiple SubFlows. Each SubFlow can run at the same time, so the parent can reach much higher concurrency.
Start and wait for a SubFlow
The target Flow must be registered on the Worker, and its start Step must accept the input passed to SubFlow. WaitFor starts the child. Execute reads its result after the child completes.
class SubFlowParentStep(Step[int]):
def __init__(self, target: Flow[int]) -> None:
self.target = target
self.options = SubFlowOptions(
timeout=timedelta(hours=1),
timeout_policy=FlowTimeoutPolicy.CANCEL,
)
def wait_for(self, context: Context, input: int) -> Wait:
return Wait.until(SubFlow.run(self.target, input, self.options))
def execute(self, context: Context, input: int) -> StepDecision:
result = SubFlow.get_condition_results(context)
output = result.single_output(int)
return graceful_complete(f"{SubFlow.get_flow_id(context)}|{output}")
Example: examples/python/dex_examples/primitives/subflow/parent_flow.py
Use SubFlows with other Conditions
Use allOf to make the parent wait for all SubFlows to complete.
Use anyOf when the parent does not need to wait for all SubFlows. A SubFlow keeps running after WaitFor has finished waiting. Execute can still get the SubFlow ID to stop it, or pass the SubFlow ID to another Step to decide how to handle it.
SubFlowOptions and SubFlowReusePolicy
SubFlowOptions are the equivalent of StartFlowOptions on the StartFlow API. Most options are the same, except for SubFlowReusePolicy.
Dex Server generates a SubFlow ID from the parent Flow ID, Step execution ID, and condition index. When a parent reuses a Flow ID with IDReusePolicy, or time travels, it can enter the same Step execution and try to start the same SubFlow ID again. SubFlowReusePolicy controls what happens in that case.
The default RESTART_IF_PREVIOUS_EXITS_ABNORMALLY is usually what a parent wants: continue waiting for a running child, use a successful child result, and restart a child that ended abnormally.
| Existing child Flow | ATTACH | RESTART_IF_PREVIOUS_EXITS_ABNORMALLY | ALWAYS_RESTART |
|---|---|---|---|
| No existing Flow | Start | Start | Start |
| Running | Attach | Attach | Terminate and start |
| Completed | Return its result | Return its result | Start |
| Failed, canceled, timed out, or terminated | Return its result | Start | Start |
Dex Server first compares the Request ID it generated for this child start. A matching Request ID always attaches to a running child or returns its terminal result, regardless of ReusePolicy. This makes a retry safe when Dex Server accepted the original start but the parent did not receive the response. For a different Request ID, Dex Server applies the selected ReusePolicy from the table.