Child workflows and ContinueAsNew
A workflow can spawn child workflows — separate executions with their own histories — to isolate sub-units, and ContinueAsNew atomically restarts a long-running execution with the same ID but a fresh empty history, so an endless workflow never hits the history-size limit that would terminate it.
Child workflows and ContinueAsNew let ORDER #1001 compose and run forever with small histories. But running forever surfaces the deepest problem of all: a workflow started under today's code might still be sleeping a year from now, and by then you'll have deployed v2. When that old execution wakes and replays against new code, the recorded history won't match — and we know what that means. How do you change workflow code safely?
Scene 10
Child workflows and ContinueAsNew
- Watch
- Try it
- Predict
- Capture
ORDER #1001 ships from two warehouses now, so the order has two shipments. Instead of cramming both into one giant order workflow with one ever-growing history, the parent ORDER #1001 starts a SEPARATE execution for each shipment — each with its OWN little event history (the green lane inside each box). If WH-East's shipment fails, WH-West's is untouched, and neither history balloons. That separate, parent-started execution is a child workflow. Now look at the right panel: a different workflow is a subscription that loops 'wait, then bill' indefinitely in a SINGLE execution. Every iteration appends events to that one history, so its bar keeps climbing toward the red line. Watch where that line is.
Highlighted lines are the ones running in the diagram right now.
def run(order):children = []for shipment in order.shipments:# each child is a SEPARATE execution# with its OWN event historyh = start_child_workflow(ShipmentWorkflow, shipment,)children.append(h)# a failure in one child is isolatedawait gather(children)
def run(state):while True:await sleep(30 * DAYS) # TimerStarted/Firedbill(state.customer) # Activity eventsstate.cycle += 1# history keeps growing toward the hard limit:# warn ~10k events, terminate past ~50k / ~50MBif history.size() > MAX_EVENTS:terminate(self) # engine kills the run
def run(state):while True:await sleep(30 * DAYS)bill(state.customer)state.cycle += 1if history.size() > CHECKPOINT:# atomic: close this execution, open a fresh# one — SAME workflow ID, EMPTY history,# only a small summary carried forwardcontinue_as_new(summary=state)
Where this sits in Build a workflow engine (Temporal / Airflow / Cadence style)
Scene 10 of 13, in the Scale & undo act — Sagas compensate; children + ContinueAsNew.. Child workflows isolate sub-units with their own histories, and ContinueAsNew restarts an endless workflow with a fresh history but the same ID before it hits the limit.
Up next. An endless or long-sleeping workflow will outlive your code. When ORDER #1001, started under v1, wakes a year later and replays against v2 — which inserted a new step — the recorded history won't match the new step sequence, and replay diverges. Editing a live workflow's code is a breaking change. The safe path is to gate the change by version.
All 13 scenes in Build a workflow engine (Temporal / Airflow / Cadence style) · Every curriculum