How to compose flows with subflows¶
IronFlow supports two ways to nest flows (also called nested flows). If you are coming from Prefect’s run_deployment / subflow APIs, start here—IronFlow’s surface is smaller and intentional.
Pick the mechanism that matches where the child must run.
| Goal | Mechanism | What to write |
|---|---|---|
| Run a child in the same process and wait for its return value | Inline (M1) | Call the child @flow like a normal function: child(...) |
| Run a child on a deployment / work pool (possibly another worker) | Deployment-backed (M2) | deployment_ref("name").submit(...).result() |
Normative limits live in the Compatibility matrix. Conceptual background: Flows · Prefect mapping: Prefect → IronFlow.
Prerequisites¶
from prefect_compat import flow, task, set_control_plane, InMemoryControlPlane, deployment_ref, wait- A control plane registered with
set_control_plane(in-process or via the self-hosted server). - For M2 only: a deployment whose
flow_namematches the child flow, and a worker claiming that deployment’s work pool (local worker thread orironflow worker start). See How to create and update deployments and How to deploy with the CLI.
Mechanism 1 — Blocking inline¶
Call another @flow from inside a parent flow. The parent blocks until the child returns. IronFlow records a linked child flow run (execution_mode=inline) with parent/root/depth metadata.
from prefect_compat import flow, set_control_plane, InMemoryControlPlane, task
set_control_plane(InMemoryControlPlane())
@task
def add_one(n: int) -> int:
return n + 1
@flow
def child_inline(n: int) -> int:
return add_one.submit(n).result()
@flow
def parent_inline() -> int:
# Mechanism 1: direct call — same process, linked child run
return child_inline(10)
When to use: shared process, low latency, no separate work pool. Nesting works: an inline child may call further inline children or submit deployment-backed subflows.
In the UI: open the parent run → DAG tab. Inline children appear as inline_subflow nodes (dashed border). The run detail page lists child flow runs; open a child for its own DAG.
Mechanism 2 — Deployment-backed subflow as task¶
Resolve a deployment by unique name (or UUID) and submit. IronFlow creates a surrogate parent task (kind=subflow), enqueues a deployment run, and returns a SubflowFuture.
from prefect_compat import (
flow,
set_control_plane,
InMemoryControlPlane,
deployment_ref,
wait,
)
plane = InMemoryControlPlane()
set_control_plane(plane)
@flow
def child_deploy(n: int = 0) -> int:
return 42 + n
# Register the callable for a local worker, then create a deployment once.
# plane.create_deployment(name="child-deploy", flow_name="child_deploy", ...)
# Start a worker that claims that deployment's work pool.
@flow
def parent_deploy() -> int:
fut = deployment_ref("child-deploy").submit(n=1)
return fut.result() # wait for child deployment run + flow result
Wait for a subflow, then continue¶
wait_for accepts SubflowFuture alongside ordinary task futures:
@flow
def parent_with_downstream() -> int:
fut = deployment_ref("child-deploy").submit(n=3)
# Block until the subflow finishes before this task runs
out = add_one.submit(fut, wait_for=[fut]).result()
return out
You can also wait([fut]) explicitly.
Fire-and-forget¶
Under the default final_state="wait_all", an undeclared submit() still counts toward parent completion. Opt out with detach=True (or @flow(final_state="explicit")):
@flow
def parent_fire_and_forget() -> str:
deployment_ref("child-deploy").submit(n=1, detach=True)
return "parent_done" # returns without waiting for the child
The child still runs when a worker claims the deployment run.
When to use: child must run on another pool/worker, or you want enqueue + claim semantics. Nested M2 works: a child deployment flow may deployment_ref(...).submit(...) further children (multi-worker recursive chains are supported).
In the UI: parent DAG shows a subflow_task node (solid border / gold styling). Child flow runs appear under the parent’s Child flow runs list with execution_mode=deployment.
Choosing M1 vs M2¶
| Question | Prefer |
|---|---|
| Same process OK? | M1 inline |
| Need a different work pool / worker? | M2 deployment_ref |
Need wait_for / fire-and-forget on the parent graph? |
M2 (surrogate task) |
| Lowest latency nesting? | M1 |
You can mix both in one parent (inline call + deployment submit).
Cancel propagation¶
Cancelling a parent flow run cancels active deployment-backed children linked to that parent. Inline children in the same process observe cancellation through the shared control plane. Details: Compatibility matrix.
Common mistakes¶
| Symptom | Likely cause |
|---|---|
deployment_ref(...).submit() outside a @flow |
Submit requires an active parent flow run. |
| Submit hangs forever | No worker claiming the child’s work pool, or deployment missing/paused. |
deployment not found |
Create the deployment (API/CLI) with the exact name you pass to deployment_ref. |
Sibling unlinked runs when calling child(...) |
Ensure you use @flow on the child and a shared control plane; IronFlow links inline children automatically when called from an active parent flow. |
Related pages¶
- Flows —
@flow, hooks, and subflow overview. - DAG and forecast —
inline_subflow/subflow_tasknode kinds. - Deployments · Deploy with CLI — create child deployments and workers.
- Port from Prefect — import and subset checklist.
- Compatibility matrix — supported subset and out-of-scope items.