The Event History Is the Program
July 20, 2026
The win for this lesson: you'll be able to say exactly what happens when a worker crashes mid-workflow, and predict — from first principles — what code is and isn't allowed inside a workflow.
You already know the pitch: write ordinary code, and Temporal makes it survive crashes. This lesson is about the trick behind that pitch. It's one mechanism, and once you have it, most of Temporal's rules stop being rules to memorise and become consequences you can derive.
There is no magic snapshot
The natural guess is that Temporal checkpoints your process — freezes memory, saves it somewhere. It doesn't. Nothing about your running process is saved at all.
Instead, the Temporal Service keeps an append-only log for each workflow run, called the Event History. It records what happened — "workflow started with these arguments", "activity Charge completed and returned this value" — never your variables or your stack.
Your code and that log interact through a strict split:
- Your workflow code emits Commands — "please schedule this activity", "start a 30-day timer".
- The Temporal Service turns outcomes into Events and appends them to the history.
Here's the workflow from your notes, in Go this time:
func OrderWorkflow(ctx workflow.Context, orderID string) error {
ao := workflow.ActivityOptions{StartToCloseTimeout: time.Minute}
ctx = workflow.WithActivityOptions(ctx, ao)
if err := workflow.ExecuteActivity(ctx, Charge, orderID).Get(ctx, nil); err != nil {
return err
}
if err := workflow.ExecuteActivity(ctx, Ship, orderID).Get(ctx, nil); err != nil {
return err
}
return workflow.ExecuteActivity(ctx, Notify, orderID).Get(ctx, nil)
}One important reframe: the Temporal Service never runs this code. Your process — a Worker — runs it, after picking up tasks from a task queue. The service only stores history and hands out tasks. By the time Charge has finished, the history holds, roughly:
1 WorkflowExecutionStarted (input: "order-123")
2 ActivityTaskScheduled (Charge)
3 ActivityTaskCompleted (result of Charge)Now the worker dies
Say the process is killed after Charge completes but before Ship runs. All in-memory state — ctx, local variables, the fact that we were "between lines 7 and 10" — is gone.
A new worker picks up the workflow and does something that sounds absurd: it runs OrderWorkflow again, from the top. This is Replay.
But replay is not re-doing. When execution reaches ExecuteActivity(ctx, Charge, …), the SDK checks the history, finds ActivityTaskCompleted for Charge, and — without running the activity — hands back the recorded result instantly. The code marches forward, fed answers from the log, until it reaches the first thing the history doesn't have an answer for: Ship. That's the frontier. Normal execution resumes there, and your customer is not charged twice.
The state wasn't saved. It was reconstructed by re-deciding everything, with the past's answers pinned. The event history, plus your code, is the program state.
Two consequences you can now derive
1. Workflow code must be deterministic. Replay only works if your code, given the same history, makes the same decisions in the same order every time. If it called time.Now() or rand.Intn() directly, the replayed run could branch differently from the original, the commands wouldn't line up with the log, and Temporal would have to declare the replay invalid. So inside workflows: no wall-clock time, no randomness, no network calls, no ordinary goroutines. The SDK gives you replay-safe stand-ins (workflow.Now, workflow.Sleep, workflow.Go).
2. Activities exist because the world can't be replayed. Charging a card is not deterministic and must not run twice. So anything that touches the outside world goes in an activity: it runs once, its result is recorded in history, and replay reuses the record. Activities are where non-determinism is quarantined — which is why they're allowed to do everything workflows can't.
That's the whole shape of Temporal: a deterministic decision-maker (workflow) replaying against a log, delegating every real-world effect to run-once, record-the-result functions (activities).
Check yourself
Answer from memory before peeking back up — the effort is what makes it stick.
A worker crashes after Charge completes but before Ship starts. A new worker picks up the workflow. What happens to Charge?
Why must workflow code be deterministic?
Which of these belongs in an activity rather than in workflow code?
Go deeper
Primary source: the Event History walkthrough with the Go SDK — it walks this exact mechanism with real event listings. Read it now, while the model is fresh; it should feel like confirmation rather than new material.
Also worth your time: SE Radio 596 with Maxim Fateev, Temporal's co-creator, on why it's designed this way. Terms live in the glossary.
Next lesson: hands-on — run the dev server and your first Go workflow, and watch this lesson's event history appear for real in the Web UI (Learn Temporal's Go path).
Anything unclear — why goroutines are banned, what "the commands wouldn't line up" concretely means, how timers survive a crash — ask me. I'm your teacher for this course, and the follow-up questions are where the learning compounds.