--- title: "Node Outcomes" description: "The four stage outcomes and the attributes that control them" --- Every node execution produces an **outcome** that drives edge routing, retry logic, and goal gate checks. This page defines the four externally visible outcomes and the attributes that influence them. ## The four outcomes | Outcome | Meaning | |---|---| | `succeeded` | The handler completed normally | | `failed` | The handler encountered an unrecoverable error | | `partially_succeeded` | The handler did not fully succeed but produced usable results — typically from retries exhausted with `allow_partial=true` | | `skipped` | The node was not executed (e.g. a branch not taken in a parallel fan-out) | Retry intent is internal to the engine. It triggers re-execution inside the retry loop and is never visible in edge `condition` expressions. Retryable failures emit `stage.retrying` events while the node is still active, then finish as one of the four outcomes above. ## How handlers produce outcomes Each node type has its own rules for which outcomes it can return: | Handler | Produces | Conditions | |---|---|---| | **Command** | `succeeded`, `failed` | `succeeded` when exit code is 0; `failed` otherwise | | **Agent / Prompt** | `succeeded`, `failed`, `partially_succeeded`, `skipped` | Defaults to `succeeded`. The LLM can set any outcome via a [routing directive](/agents/outputs#routing-directives) JSON object in its response. Backend errors request retry when retryable or finish as `failed`. | | **Parallel** | `succeeded`, `partially_succeeded`, `failed` | Waits for every branch. `succeeded` when all branches succeed, `failed` when all branches fail, and `partially_succeeded` for mixed or partial results. A static fan-out with no branches remains partial; a valid `for_each` source with zero items succeeds. | | **Human** | `succeeded` | Always succeeds — the user's selection becomes a routing signal via `preferred_label` | | **Conditional** | `succeeded` | Always succeeds — routing is handled by the engine's edge selection | | **Start / Exit / Wait** | `succeeded` | Always succeed | ## Retry loop When a handler returns a retryable failure, the engine enters the retry loop. If retry attempts remain (per the node's [retry policy](/execution/failures#retry-policies)), the handler re-executes after a backoff delay. If attempts are exhausted, the final outcome depends on `allow_partial`: ``` ┌─────────────┐ │ Run handler │ └──────┬──────┘ │ ▼ ┌──────────┐ succeeded / failed / │Outcome? │─── partially_succeeded / skipped ──▶ Done (use as-is) └────┬─────┘ │ retryable failure ▼ ┌──────────────┐ yes ┌──────────────┐ │ Attempts │─────────▶│ Backoff + │──┐ │ remain? │ │ re-execute │ │ └──────┬───────┘ └──────────────┘ │ │ no │ ▼ ┌──────────┘ ┌──────────────┐ │ │allow_partial?│ │ (loops back to └──────┬───┬───┘ │ "Run handler") yes │ │ no │ ▼ ▼ partially_succeeded failed ``` Handler errors follow the same loop: retryable errors (transient infrastructure) re-execute if attempts remain; non-retryable errors (authentication, bad config) fail immediately without consuming retry attempts. ## `allow_partial` When `allow_partial=true` and the retry loop exhausts all attempts on a retryable failure, the outcome is promoted to `partially_succeeded` instead of `failed`. This lets the workflow continue past nodes that could not fully succeed. | Attribute | Type | Default | |---|---|---| | `allow_partial` | Boolean | `false` | ```dot implement [ label="Implement", retry_policy="standard", allow_partial=true, prompt="Implement the feature." ] ``` In this example, if the agent returns a retryable failure and all 5 standard-policy attempts are used, the node finishes with `partially_succeeded` rather than failing the run. See [Retry policies](/execution/failures#retry-policies) for the available presets and backoff settings. ## Succeed on failure When a node's effective `on_failure` policy is `succeed`, a `failed` outcome with no explicit recovery route is promoted to `succeeded`. This is applied after the retry loop, so retries still happen normally — only the final outcome changes. The original failure details stay on the `stage.completed` event and in the checkpoint, and the outcome's notes record the promotion. | Attribute | Type | Default | |---|---|---| | `on_failure` | String | inherits the graph-level `on_failure` (default `route`) | ```dot scan [ label="Scan", shape=parallelogram, on_failure="succeed", script="find . -name '*.log' | head -20" ] ``` Use `on_failure="succeed"` for nodes whose failure should never block the workflow — optional scans, best-effort cleanup steps, or informational commands where the output matters more than the exit code. An explicit `condition="outcome=failed"` edge still takes priority; the promotion applies only when no explicit route matches. The policy applies only to `failed` and leaves `partially_succeeded` unchanged. See [Failed-node routing policy](/workflows/transitions#failed-node-routing-policy) for the full set of policies. `auto_status=true` is the deprecated spelling of `on_failure="succeed"`. Fabro still accepts it as an alias, and validation reports an `auto_status_deprecated` warning with the replacement. Unlike the old attribute, the alias no longer promotes `partially_succeeded` outcomes. ## Goal gate interaction Node outcomes and workflow status are separate. A node can finish with `failed` and the workflow can still reach `exit` and complete successfully if routing handles that outcome. Mark a node with `goal_gate=true` when its failure must make the workflow fail. Nodes marked with `goal_gate=true` are checked when the workflow reaches the exit node. A goal gate is satisfied if its last outcome was `succeeded` **or** `partially_succeeded`. Any other outcome (`failed`, `skipped`) causes the workflow to fail, even though execution reached the exit. This means `allow_partial=true` on a goal gate node lets the gate pass even if the node exhausted its retries — the promoted `partially_succeeded` outcome counts as passing. Likewise, a `succeeded` outcome promoted by `on_failure="succeed"` satisfies the gate. See [Goal gates](/execution/failures#goal-gates) for retry target resolution and failure behavior. ## Outcome in edge conditions The four externally-visible statuses can be used in edge `condition` expressions via the `outcome` key: ```dot gate -> deploy [label="Pass", condition="outcome=succeeded"] gate -> fix [label="Fix", condition="outcome=failed"] gate -> review [label="Partial", condition="outcome=partially_succeeded"] gate -> skip [label="Skipped", condition="outcome=skipped"] // Common pattern: treat partial as passing gate -> deploy [condition="outcome=succeeded || outcome=partially_succeeded"] gate -> fix [condition="outcome=failed"] ``` See [Transitions](/workflows/transitions) for the full edge selection logic and operator reference.