Build a workflow engine (Temporal / Airflow / Cadence style) (13 scenes)
Scene 10 · Child workflows and 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.
Previously

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
Diagram
LEFT: ORDER #1001 split across warehouses — the parent starts 'child workflows' (each a separate execution with its OWN history lane), isolating failures and keeping each history small. RIGHT: an endless loop's history bar filling toward a red 'max events (~50k)' line — past it, the engine terminates the execution. 'ContinueAsNew' is the fix: at a safe checkpoint it atomically restarts the workflow with the SAME ID but a fresh empty history, carrying only a small summary forward, so the logical workflow runs forever while each execution stays small.
COMPOSITIONparent fans into one child per warehouseORDER #1001 (parent)starts & awaits childrenShipmentWorkflow · WH-Eastchild workflow · own history9 eventsShipmentWorkflow · WH-Westchild workflow · own history7 eventsUNBOUNDED RUNone execution, history growing as the loop runswarn ~10kmax ~50k800 evevent historyLOOP ITERATIONSnoneCONTINUE-AS-NEWOFFLeft: one child workflow per warehouse keeps each history small and isolates failures. Right: an endless loop in ON…
child workflow: a separate execution per shipment, with its own small history
one history per child → a failure in one is isolated from the other
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.
Implementation
OrderWorkflow.run (parent)
one child execution per shipment — separate histories
1def run(order):
2 children = []
3 for shipment in order.shipments:
4 # each child is a SEPARATE execution
5 # with its OWN event history
6 h = start_child_workflow(
7 ShipmentWorkflow, shipment,
8 )
9 children.append(h)
10 # a failure in one child is isolated
11 await gather(children)
SubscriptionWorkflow.run (one execution)
every iteration appends events to one growing history
1def run(state):
2 while True:
3 await sleep(30 * DAYS) # TimerStarted/Fired
4 bill(state.customer) # Activity events
5 state.cycle += 1
6 # history keeps growing toward the hard limit:
7 # warn ~10k events, terminate past ~50k / ~50MB
8 if history.size() > MAX_EVENTS:
9 terminate(self) # engine kills the run
SubscriptionWorkflow.run (with ContinueAsNew)
at a checkpoint, restart with same ID and empty history
1def run(state):
2 while True:
3 await sleep(30 * DAYS)
4 bill(state.customer)
5 state.cycle += 1
6 if history.size() > CHECKPOINT:
7 # atomic: close this execution, open a fresh
8 # one — SAME workflow ID, EMPTY history,
9 # only a small summary carried forward
10 continue_as_new(summary=state)
Not sure what to ask? Tap a question — the staff engineer answers in the chat panel.