CONCEPT Cited by 1 source
Workflow determinism requirement¶
Definition¶
The workflow determinism requirement is the invariant that a workflow method, given the same inputs, checkpointed action results, and state fields, must make the same decisions and call actions in the same order on every replay. Without it, replay-based durability (see concepts/workflow-replay-from-checkpointed-actions) cannot reconstruct the workflow's state: the engine would replay the method, take a different branch, try to short-circuit an action that was never executed on the original run, or fail to short-circuit one that was, corrupting the workflow.
Every replay-based workflow engine — Temporal, Cadence, Skipper — imposes this as a correctness constraint. Skipper's disclosure names it as "one key constraint" of its model. (Source: sources/2026-04-28-airbnb-skipper-building-airbnbs-embedded-workflow-engine.)
The Skipper framing¶
"Replay imposes one key constraint: workflow methods must be deterministic. Given the same inputs, checkpointed action results, and state fields, the workflow must make the same decisions and call actions in the same order. All side effects, such as API calls, time-dependent logic, and randomness, belong in actions, never in the workflow method directly." (Source: sources/2026-04-28-airbnb-skipper-building-airbnbs-embedded-workflow-engine.)
Common determinism violations¶
What cannot appear in a workflow method body:
- Clock reads —
Instant.now(),System.currentTimeMillis(). Different replays execute at different wall-clock times and will take different branches. - Random numbers —
Random.nextInt(). Different replays generate different values. - UUID generation —
UUID.randomUUID(). Same problem as randomness. - Direct API calls — network calls to HTTP or gRPC services. Each replay would make a fresh call; results would vary, and the engine can't short-circuit them.
- Direct DB reads of unpinned state — reading a row whose value changes between replays gives different results per replay.
- Thread-dependent iteration — parallel iteration over a collection whose ordering is non-deterministic (e.g. hash- map iteration in some JVMs).
- Environment reads —
System.getenv()values that may differ across hosts / deploys.
All of these must be moved inside an action method. The action executes once, its result is checkpointed, and the workflow body reads the (deterministic) checkpointed value on every replay.
Safe primitives inside the workflow method¶
What the workflow method can do deterministically:
- Read its own input parameters (pinned at workflow start).
- Read
@StateFieldvalues (updated only via@SignalMethodor action returns). - Read results of already-completed actions (returned from
actions.someMethod()calls, checkpointed on success). - Call
waitUntil { condition }— the engine handles the hibernation. - Call new actions — the engine sequences and checkpoints them.
- Use language control flow (
if,when, loops) over pinned values.
Why this is unintuitive for new developers¶
The post names operator-experience friction:
"The replay model requires deterministic workflow methods, which can be unintuitive for developers new to the pattern." (Source: sources/2026-04-28-airbnb-skipper-building-airbnbs-embedded-workflow-engine.)
The confusion arises because ordinary Kotlin / Java methods routinely do exactly what workflow methods can't: read the clock, generate UUIDs, call APIs. The pattern requires developers to internalise a new rule ("side effects live in actions, orchestration lives in the workflow") that isn't enforced by the type system — only by the annotation contract and runtime replay behaviour.
Skipper mitigates this partially by making action-calling ergonomic (typed interface, no codegen) so the syntactic cost of moving something into an action is low. But a determinism bug in a workflow method typically doesn't surface until the workflow crashes and replays, possibly long after deploy.
Relationship to idempotency¶
Determinism (workflow method property) and idempotency (action property) are distinct but paired:
- Determinism lets the engine short-circuit already-completed actions during replay by returning their stored results.
- Idempotency lets the engine safely re-execute an action that was actually called but crashed before its checkpoint was committed (the at-least-once edge case).
A correct replay-based workflow system requires both. Airbnb names both in the tradeoff section of the Skipper post.
Workflow evolution as a special case¶
Changing a workflow's structure — adding a step, reordering actions, renaming a state field — can break in-flight workflows because replay of already-started instances would take different branches or call different actions than the original run. This is the "evolution complexity" tradeoff the post names:
"Changing a workflow's structure can break in-flight workflows. Teams need versioning strategies for workflow evolution." (Source: sources/2026-04-28-airbnb-skipper-building-airbnbs-embedded-workflow-engine.)
The Skipper team's versioning patterns ("create new method versions, migrate traffic, deprecate old versions") are a pragmatic workaround. Automated-compatibility-checking tooling is the post's most-wanted improvement.
Enforcement posture¶
Neither Skipper nor Temporal can fully enforce determinism at compile time (Java/Kotlin type systems don't capture "pure function of pinned inputs"). Enforcement relies on:
- Documentation / team convention — the rule is named, but not mechanically checked.
- Runtime replay divergence detection — the engine can sometimes detect divergence by comparing the replay's action sequence against the stored checkpoints (Temporal does this more aggressively than Skipper's post describes).
- Review discipline — code review catches obvious
determinism violations (direct
Instant.now()in workflow body).
The post doesn't disclose Skipper's enforcement mechanism in detail; it's named as an acknowledged-ergonomic-gap area.
Seen in¶
- sources/2026-04-28-airbnb-skipper-building-airbnbs-embedded-workflow-engine — canonical wiki disclosure of the determinism requirement for an embedded workflow engine. Skipper names it as "one key constraint" of the replay model; actions hold all side effects, workflow methods hold orchestration only. Named as unintuitive for new developers — one of the explicit tradeoffs of the embedded / replay-based approach.
Related¶
- concepts/workflow-replay-from-checkpointed-actions — the mechanism that requires this invariant.
- concepts/durable-execution — parent property.
- concepts/embedded-workflow-engine — the shape that typically imposes this invariant.
- concepts/idempotent-operations — the action-side invariant that composes with this workflow-side one.
- concepts/at-least-once-delivery — the reason idempotency is needed alongside determinism.
- systems/airbnb-skipper — canonical instance.
- systems/temporal — sibling system with the same invariant (even more strictly enforced at runtime).
- patterns/workflow-primitives-as-annotated-classes — the ergonomic shape that keeps actions syntactically close to the workflow method so the move-side-effects-to-actions rule stays cheap.