Skip to main content

Determinism

View Markdown

A flow body must be deterministic: given the same input and the same answers from the engine, it makes the same calls in the same order.

Why

The orchestrator does not checkpoint memory. It records events: task scheduled, task completed with this result, timer started, timer fired, signal received. When a worker picks up a run that another worker started, it runs the body from the top and, at each scope call, feeds it the recorded answer instead of doing the work again. The body reaches the first call that has no answer yet, and real execution continues from there.

That only works if the body asks the same questions in the same order every time.

What breaks it

Do notDo instead
System.currentTimeMillis(), Instant.now()flow.now
Random, UUID.randomUUID() in the bodygenerate in a task, or derive from the input
HTTP, database, file I/O in the bodya task
threads, coroutines, suspend in the bodyflow.startTask for fan-out
branching on anything outside input, task results, signals and engine timemove the read into a task

Versioning

Two builds of the same flow type on one queue means a run can start on one build and continue on the other. The orchestrator compares the sequence of commands during replay, not their parameters: a changed timer duration goes unnoticed, a reordered task call does not. Keep one build per queue, and drain runs before removing a step from a flow that has long-lived runs in flight. A versioning guide for in-flight changes is on the way.

Remember: replay is the whole trick. Determinism is its price.