# end node

Marks a terminal state of the workflow and records the run's final
`outcome`. Every workflow needs at least one `end` node; most non-trivial
workflows have several: one per distinct way the run can finish.

## Purpose

Use `end` to give each terminal path a meaningful, queryable outcome. The
`outcome` you set here is what shows up in run history, what dashboards
aggregate over, and what callers (CLI, web UI, MCP, REST) inspect to know
whether the run "succeeded".

## YAML schema

```yaml
states:
  done:
    type: end # discriminator
    label: Done # required, shown in UI / logs
    outcome: success # required: success | failure | cancelled
    message: Reviewer quorum agreed # optional, free text
```

`end` nodes have **no `on` map**. They are terminal by construction.

## Configuration

| Field     | Type                                        | Required | Default | Meaning                                                 |
| --------- | ------------------------------------------- | -------- | ------- | ------------------------------------------------------- |
| `type`    | `'end'`                                     | yes      | —       | Discriminator.                                          |
| `label`   | string                                      | yes      | —       | Human-readable name for the terminal state.             |
| `outcome` | `'success'` \| `'failure'` \| `'cancelled'` | yes      | —       | Recorded on the run; drives history and aggregations.   |
| `message` | string                                      | no       | —       | Optional context shown alongside the outcome in the UI. |

A workflow may have any number of `end` nodes: one per distinct terminal
path. It is conventional (but not required) to have at least one
`success` end and at least one `failure` end.

## Outputs

| Key       | Type   | Notes                                   |
| --------- | ------ | --------------------------------------- |
| `outcome` | string | Echo of the configured outcome.         |
| `message` | string | Echo of the configured message, if any. |

These are visible in run inspection but, since `end` is terminal, no node
will read from them downstream.

## Transitions

None. `end` is terminal, so it has no `on` map, and the runner finalizes the
run as soon as an `end` node completes.

## Worked example

```yaml
name: ai-judge
version: 1
description: Single-agent gate with separate ends for yes and no.
initial: ask
states:
  ask:
    type: ai_judge
    label: Is the sky blue?
    model: claude-haiku-4-5
    prompt: |
      Is the daytime sky on a clear day blue? Answer with the JSON shape
      requested below.
    validator:
      kind: boolean
    on:
      'true': yes-end
      'false': no-end

  yes-end:
    type: end
    label: Yes
    outcome: success
    message: The judge agreed.

  no-end:
    type: end
    label: No
    outcome: failure
    message: The judge disagreed.
```

## Common pitfalls

- **Reusing one `end` for every path.** Routing every transition to a
  single `done` end loses the distinction between successful and failed
  runs in history. Use multiple ends with different outcomes.
- **Using the wrong outcome.** `cancelled` is for explicitly aborted runs
  (e.g. a `human` reject path that genuinely shouldn't count as a
  failure). Don't reach for it to soften the look of a failed run. Metrics
  and dashboards depend on the distinction.
- **Adding an `on:` map.** Schema rejects it. `end` is terminal by
  construction.
- **Forgetting an `end` entirely.** Nothing requires a workflow to reach an
  `end` node: a state with no `on` map at all also stops the run (recorded
  as `completed`), but with no `outcome` written. Give every terminal path
  an explicit `end` node so run history shows why it finished.
- **Treating `message` as structured data.** `message` is free text shown
  in the UI. If you need structured information about the outcome, write
  it from an upstream node into the run's variables instead.
