Build a workflow engine (Temporal / Airflow / Cadence style) (13 scenes)
Scene 03 · Replay: re-run the code against the history
To resume, the engine re-runs your code from the top and hands back the recorded results instead of redoing them — which only works if the code is deterministic.
Previously
We have a durable history; replay re-runs the code against it, handing back recorded results so ORDER #1001 fast-forwards to step 2 without re-charging. But that only works if the code is deterministic — and charging a card, calling a payment API, sending an email are all non-deterministic side effects. Where do those live so replay doesn't repeat them?
Scene 03
Replay: re-run the code against the history
Diagram
The strip is ORDER #1001's event history. The scrub head drags through it: grayed steps are 'replayed' — the engine hands your code the recorded result instead of re-running the step (so ChargeCard is NOT re-charged). The first un-recorded step glows 'executing for real'. 'Determinism' means the re-run must issue the exact same steps as last time; if you inject Math.random(), the replay branches differently, diverges from history, and the engine raises a 'non-determinism error' and freezes the workflow.
↓ replay re-runs the code from step 0
You restart after the crash with the durable history from last scene, but a record isn't a running program. So the engine does something clever: it re-runs your workflow function from the very top — and for every step that already has a recorded result, it hands your code that recorded value INSTEAD of doing the step again. So ChargeCard returns its recorded "ok" without touching the card a second time. The head fast-forwards step by step until it reaches the first step with no recorded result yet — ReserveInventory — which now runs for real. This re-run-against-history move is called **replay**: re-executing the code from the start, substituting each step's recorded result, to fast-forward back to exactly where the crash hit. Drag the scrub head across the strip and watch the gray 'replayed' steps fast-forward to the live one.
Implementation
Engine.replay
re-run the workflow from step 0, folding over history
1def replay(workflow_fn, history):2 cursor = 0 # position in recorded history3 run(workflow_fn):4 on command issued by code:5 cmd = next command from code6 apply(cmd, history, cursor)7 cursor += 18 # history exhausted -> caught up; go live9 resume_live_execution()
Engine.apply
for each step: hand back the recorded result, or run it live
1def apply(cmd, history, cursor):2 if cursor < len(history):3 event = history[cursor]4 assert_matches(cmd, event) # determinism gate5 # NOT re-executed: ChargeCard is not re-charged6 return event.recorded_result7 else:8 # first un-recorded step -> execute for real9 result = execute_activity(cmd)10 history.append(record(cmd, result))11 return result
Engine.assert_matches
the determinism check: re-issued command must equal history
1def assert_matches(cmd, event):2 # the re-run must issue the SAME command it did before3 if cmd == event.command:4 return # reconciled, continue replay5 # Math.random()/clock returned a different value,6 # so the code branched off the recorded path7 raise NonDeterminismError(8 expected = event.command,9 got = cmd,10 ) # workflow freezes -- no safe merge
Not sure what to ask? Tap a question — the staff engineer answers in the chat panel.