Every task run and container (DAG or Loop scope) in Aether moves through a well-defined state machine. The engine is the sole writer ofDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/BabySid/aether/llms.txt
Use this file to discover all available pages before exploring further.
Phase values — executors return integer exit codes, and the engine translates those codes into phases. This separation keeps the state machine coherent: no executor can express engine-level concerns like “skip” or “cancel,” and no outside actor can set a phase that contradicts the engine’s view of the world.
The state machine
All nine phases
Created
The task has been persisted to the store but the engine has not yet committed to run it. Dependencies may be unsatisfied, or a concurrency limit may be in effect.
Ready
The engine has committed to execute this task. For a leaf task:
Broker.Dispatch() has been called. For a container (DAG/Loop): its first child leaf has been dispatched.Running
Execution has actually begun. For a leaf task:
OnTaskStarted has been called by the broker. For a container: its first child leaf has transitioned to Running.Suspended
The executor returned
ExecCodeSuspended. The task is paused, waiting for an external Engine.Resume() call. Only leaf tasks can enter this state.Succeeded
The task completed successfully (
ExecCodeSucceeded → 0).Failed
The task completed with a business failure (
ExecCodeFailed → 2). This signals an expected, application-level negative outcome rather than a system error.Error
A system-level error occurred (
ExecCodeError → 3): executor panic, OOM, container killed, network failure, or similar.Timeout
The task exceeded its deadline (
ExecCodeTimeout → 4). In local mode the broker sets this when the task context expires; in distributed mode the engine’s timeout watcher may set it directly.Skipped
The task’s
when condition evaluated to false. Set exclusively by the engine — no executor exit code maps to this phase.Cancelled
The workflow or task was stopped by a user-initiated
Engine.Cancel() call. Set exclusively by the engine.Terminal phases
Phase.IsTerminal() returns true for the following six phases:
ExecCode → Phase mapping
Executor plugins return anExecOutputs struct whose Code field is an integer constant:
| ExecCode | Value | Maps to Phase |
|---|---|---|
ExecCodeSucceeded | 0 | PhaseSucceeded |
ExecCodeSuspended | 1 | PhaseSuspended |
ExecCodeFailed | 2 | PhaseFailed |
ExecCodeError | 3 | PhaseError |
ExecCodeTimeout | 4 | PhaseTimeout |
PhaseSkipped and PhaseCancelled never appear in ExecOutputs — they are set directly by the engine and have no corresponding ExecCode.
PhaseSkipped vs PhaseCancelled
These two terminal phases are often confused. The distinction is important:- PhaseSkipped
- PhaseCancelled
Condition-driven, automatic. A task enters
PhaseSkipped when its when expression evaluates to false at dispatch time. The engine skips the task silently; downstream tasks that depend on it are unblocked as if it had succeeded. This is intended for conditional branching within a DAG.Users cannot set PhaseSkipped via phaseConditions. Attempting to do so is a no-op — the engine reserves this transition.PhaseSuspended and Resume
PhaseSuspended is a special non-terminal, non-running state available exclusively to leaf tasks. When an executor returns ExecCodeSuspended, the task is paused in place with partial outputs accumulated:
Executor suspends
The executor returns
ExecCodeSuspended along with any partial outputs produced so far.Task enters PhaseSuspended
The engine writes
PhaseSuspended to the task run and triggers the onSuspend hook if configured.Outputs are merged
The engine merges the new payload into the task’s existing outputs (last-writer-wins per parameter).
PhaseSuspended models human-approval gates, external callback patterns, and long-running interactive tasks. Only leaf tasks can enter this state; DAG and Loop containers cannot suspend.phaseConditions: user-defined phase overrides
ThephaseConditions field on a template or DAG task lets you write expressions that override the engine’s default phase mapping. This is useful when a task uses a non-standard exit code convention or when business logic needs to reclassify an error as a failure (or vice versa):
succeeded first, then failed, then error) and uses the phase whose condition returns true first.
Container phase propagation
DAG and Loop containers derive their own phase from their children:DAG phase propagation
DAG phase propagation
A DAG container transitions to
PhaseSucceeded when all its tasks reach a terminal phase and none is in a failure state that would propagate. If any task reaches PhaseFailed, PhaseError, or PhaseTimeout without a matching continueOn policy, the container itself transitions to the same phase. Tasks that were not yet dispatched transition to PhaseCancelled.Loop phase propagation
Loop phase propagation
A Loop container tracks the terminal phase of each iteration. The aggregate phase follows the same rules as DAG: if any iteration fails without a
continueOn policy, the loop fails. The aggregate strategy controls how outputs are merged, but not phase propagation.Nested containers
Nested containers
Because containers share the same state machine as leaf tasks, nesting is fully recursive. An inner DAG that fails propagates
PhaseFailed to its parent task node, which the outer DAG processes the same way it would any other failed task. The advanceScope() scheduling loop walks upward through the scope tree until the root scope reaches a terminal phase.