Durable primitives
Every Flow primitive follows one rule. Workflow code returns a command with stable identity, the engine commits an event, then code replays and reads the result from history. Runtime code does not maintain a hidden cursor.
Identity comes before execution
IDs should describe a business stage. Avoid transient positions, random values, or current time. For collection batches, sort the input first and generate fixed-width ordinal IDs.
Steps
A step is the external side-effect boundary. run_workflow() decides whether to schedule it, while run_step() performs network, database, file, or tool access.
Step status and output live in the snapshot. After success commits, replay only reads the result. If the process fails after the external call but before the commit, the step may execute again.
Atomic batches
schedule_steps() declares independent steps together. The engine validates unique IDs and stable parameters for the whole batch before advancing members. Results retain request order rather than completion order.
A batch cannot express dependencies among members. If a later stage needs earlier outputs, wait for the first batch to become durable and schedule the second batch on the next replay.
Retries
Retry policy belongs to the step command. Flow supports no retry, fixed delay, and capped exponential backoff.
Exponential policy derives deterministic jitter from immutable run, step, and attempt identity. A restart cannot change the chosen UTC retry deadline. Exhaustion fails the run by default. continue_workflow_on_failure() lets workflow code read step_failed() and choose compensation or fallback.
Timer waits
wait_until() stores an absolute UTC instant, not an in-process timer.
Becoming due only means that the run is eligible to resume. The scheduler may deliver more than one task. Resume checks current wait status and converges safely.
Signals and hooks
A signal is a named message addressed by run ID. A hook is an external callback addressed by token. Both suspend the run and replay only after payload commit. See Signals and hooks for declaration, deduplication, and disposal rules.
Progress
Progress supports control-plane inspection and should not drive workflow branches. Workflow code may return ctx.record_progress(), while the host may call engine.record_progress().
The same progress_id and content may be redelivered. Use a new identity, such as a page number or monotonic business sequence, for each updated value.
Child-operation links
An externally managed job does not need to become a first-class child workflow. Store a stable association through ChildOperationReference, then manage its lifecycle with steps, signals, or hooks.
This reference provides durable linkage only. It does not propagate cancellation. Use a first-class child workflow when Flow owns the child run.
Continuation
continue_as_new() creates a successor with new input and closes the current history segment. It fits polling, batch import, and long-running recurring work.
Each segment has its own event stream, and the successor inherits the complete workflow definition. continuation_chain() reads every segment from the root. Continuation bounds replay length without rewriting old history.
Composition guidance
A common production path combines primitives in this order.
- Create an external request in a step.
- Wait for its result through a hook or signal.
- Add a timer wait for the business deadline.
- Handle cancellation through cleanup steps.
- Expose operations through progress and observers.
- Continue as new after a planned history threshold.
Recovery remains clear when every stage has its own stable identity. Combining several responsibilities in one step obscures retry, audit, and compensation boundaries together.
