Replay-Proof Steps Are the Atomic Unit of Trustworthy Durable Workflows

articleinfrastructuredistributed-systemsidempotencyagent-loopsinngestsecurity

Inngest step.run memoization is exactly replay-proof isolation — every step must be safe to re-execute without double-firing side effects, and that constraint shapes the entire system design

The “replay-proof” framing is a tighter way to think about something that shows up in every durable workflow engine: idempotency. Idempotency is usually talked about at the API or event level, but replay-proofness drills down to the individual step — can this specific unit of work run twice and produce exactly the same outcome? That constraint, when taken seriously, changes how you think about what a “step” even is.

Inngest bakes this in with step.run memoization: once a step completes, its return value is cached and replayed from the event history on retries. The function body reruns, but completed steps are skipped. This is replay-proof by construction — but only if the code inside each step doesn’t reach outside its boundary. Call an external API twice inside a single step? You’ve broken the contract. Split that into two steps? Now it’s safe.

The harder design question is at the boundaries: webhooks, emails, Stripe charges, Telegram messages. These aren’t idempotent by nature, so you impose idempotency on them — webhook signatures, idempotency keys on API calls, deduplication at the event layer. The whole stack is a chain of replay-proof contracts, and the weakest link is wherever someone forgot to think it through.

Key Ideas

  • Replay-proof ≠ idempotent API — it’s a per-step constraint, not just a per-request one; the unit of analysis is smaller
  • Inngest step memoization makes step.run replay-proof by design, but the developer has to respect the boundary — side effects must not cross step lines
  • Replay attacks in auth (TOTP, nonces, OAuth PKCE) and replay safety in distributed systems are the same underlying idea wearing different clothes
  • Every external mutation — email send, charge, message delivery — needs its own idempotency key or dedup gate, otherwise a retry becomes a double-charge
  • The saga pattern is one way to make multi-step processes replay-proof at the orchestration level; Inngest’s step model is a practical implementation of this for TypeScript
  • Designing for replay-proofness up front forces you to think about failure modes and side-effect boundaries before they bite you in production