Types & Safety Semantics
The isPure, isIdempotent and reversible flags on function blocks are the foundation of the engine’s transaction model. They determine how the engine handles failures, retries, and rollback.
Safety Properties
| Property | Meaning | Example |
|---|---|---|
isPure: true |
No side effects | Collecting show version, reading facts, declaring CMS updates |
isIdempotent: true |
Same input → same result, safe to re-run | Writing a complete config (not appending), setting a description |
Both false |
Side effects that may not be safely repeated | Incrementing a counter, appending to a log, rebooting a device |
reversible: true |
A rollback() undoes the block’s side effects |
Restoring a configuration snapshot |
Pure
A pure function block has no side effects: it changes nothing on the device or in any external system. If a pure function block fails, nothing has changed – the world is exactly as it was before.
Declared CMS changes do not count as side effects. A block that returns dbUpdates only declares them; the engine, not the block, writes them to the CMS, so a block whose only effect is its dbUpdates is pure.
The engine uses this to classify failures: if a workflow only ran pure steps before failing, the failure is FAILED_SAFE. A pure step is never rolled back.
Idempotent
An idempotent function block can be safely executed multiple times with the same input. The result is the same whether it runs once or ten times. This is a weaker guarantee than pure (the function block does have side effects), but the side effects are predictable and repeatable.
A failed job of a pure or idempotent function block is retried automatically, see Automatic Retry.
Side effects of one attempt
The flags describe the function block. A single attempt can say more: its result may report sideEffects: false, on a success or a failure, when that attempt changed nothing. A step whose every attempt said so is left out of the rollback, and a failure that says so is retried even for a block that is neither pure nor idempotent. A pure block cannot report sideEffects: true: the Worker SDK turns such a result into a failure, and the engine stores it as an invalid_result failure. Details in Steps that changed nothing.
Transaction Classification
The engine classifies a failure from the jobs that actually reached a worker, not from run-level flags:
| The failed run executed… | On failure → |
|---|---|
Only pure steps, or non-pure steps whose every attempt reported sideEffects: false |
FAILED_SAFE |
Other non-pure steps, all reversible |
ROLLBACK, then FAILED_SAFE or FAILED_UNSAFE |
| Other non-pure steps, at least one irreversible | FAILED_UNSAFE |
Automatic retries happen before this: a failed job of a pure or idempotent block, or a failure that reported sideEffects: false, is retried up to three times before its step fails.
isPureExecution and isIdempotentExecution decide nothing
The engine tracks two run-level flags while it executes: isPureExecution (every step was
pure) and isIdempotentExecution (every step was pure or idempotent). It logs them, but
neither drives the classification or the retries above.
The flags are set by the function block developer
The engine trusts the isPure, isIdempotent and reversible flags declared during
registration, and the sideEffects an attempt reports. If a function block claims to be
pure, or an attempt claims it changed nothing, but it did modify the device, the engine will
incorrectly classify failures as FAILED_SAFE. Accurate declarations are essential for the
transaction model to work correctly. isPure and reversible are frozen per exact version,
see Registration.
Automatic Retry
A failed job of a function block registered as isPure or isIdempotent, or a failure that reported sideEffects: false, is retried up to three times; a worker can opt a deterministic failure out with retryable: false. Details in Retry & Rollback. The step-level retryConfig is not consumed yet.
Best Practices for Workflow Authors
Know your function blocks:
: Check the isPure and isIdempotent flags of the function blocks you use. This tells you what the engine will do if something goes wrong.
Order for safety:
: Put pure steps (data collection, validation) before configuration steps. If the workflow fails during the pure phase, it is automatically FAILED_SAFE.
Design idempotent configs: : When writing configuration function blocks, prefer declarative (replace entire config section) over imperative (append line). Declarative configs are naturally idempotent.
Use FAILED_SAFE as a signal:
: If your workflows frequently end as FAILED_SAFE, that means your pure steps, or checks that ran before anything was sent, are failing (likely connectivity issues) but nothing harmful happened. Investigate the root cause but don’t panic.
See also:
- Pure and Idempotent (Worker SDK) – How to declare and reason about
is_pure/is_idempotentin Python - Workflow as a Transaction – How purity and idempotency drive the engine’s transaction model

