Determinism
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 not | Do instead |
|---|---|
System.currentTimeMillis(), Instant.now() | flow.now |
Random, UUID.randomUUID() in the body | generate in a task, or derive from the input |
| HTTP, database, file I/O in the body | a task |
threads, coroutines, suspend in the body | flow.startTask for fan-out |
| branching on anything outside input, task results, signals and engine time | move 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.