Skip to content

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 readsInstant.now(), System.currentTimeMillis(). Different replays execute at different wall-clock times and will take different branches.
  • Random numbersRandom.nextInt(). Different replays generate different values.
  • UUID generationUUID.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 readsSystem.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 @StateField values (updated only via @SignalMethod or 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

Last updated · 433 distilled / 1,256 read