The Execution Status Model
Every workflow execution moves through a small, well-defined set of statuses. The model is worth studying because two of its distinctions — who can set a status, and transient versus permanent failure — encode the platform's whole stance on staying honest about what the engine is doing.
The statuses
workflow_execution_status_enum defines six:
| Status | Meaning | Set by | Retryable |
|---|---|---|---|
queued | created, webhook not yet dispatched | core, at creation | no |
running | webhook dispatched; engine executing | core, after dispatch | no |
completed | success; result handled, workspace advanced | core, after result handler | no |
failed_transient | external/transient failure | core (on /terminate), reconciler, or operator | yes |
failed_permanent | platform-side failure | core (dispatch failure or handler throw) | no |
cancelled | reserved for future use | — | no |
Transient vs. permanent — the key distinction
The two failure states aren't severity levels; they're diagnoses. failed_transient means the problem is outside the platform — an external service error, a crash, a guardrail self-termination, a missed callback, an operator decision. The snapshots are valid, so a retry might succeed. failed_permanent means the problem is platform-side — the webhook couldn't be dispatched, or the plugin's result handler threw — so retrying with the same snapshots would fail identically. Only failed_transient is retryable, and a retry deliberately replays the original snapshots rather than re-resolving config.
Who sets a terminal state, and why it matters
Notice that three actors can drive an execution to failed_transient: the engine (via the /terminate callback), an operator (manual termination), and the reconciler (catching stale runs). This is the visible face of the platform's Truthfulness Invariant: it must never mark an execution terminal while the engine may still be running. It's also why the reconciler only ever produces failed_transient and never completed — a genuine completion requires the workflow's result payload, which arrives only via the /complete callback, so the platform cannot fabricate success on the engine's behalf. The status model, in other words, is designed so the platform's record can be behind the engine's reality but never ahead of it.