> ## Documentation Index
> Fetch the complete documentation index at: https://graph.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Exit gates

> End a plan early with a success or error state

An `exit` step is a plan's escape hatch: a gate that ends execution early with an explicit **success** or **error** state — skipping every remaining step *and the solver*. Gates make plans CI-shaped: guard clauses, assertions, and judgment calls live in the plan instead of in shell scripting around it.

<Tip>
  Check-shaped plans (CI gates, validations, drift detection) should always end in explicit gated exits — error asserting the failure condition, success when there is nothing to flag — rather than leaving the verdict to solver prose. [The planner](/plans/the-planner) is instructed to draft them that way.
</Tip>

```yaml theme={null}
steps:
  - id: E0
    tool_name: linear__list_issues
    input: { priority: 1, limit: 50 }

  # Guard: nothing to do → clean success, zero further cost
  - id: E1
    tool_name: exit
    input:
      when: { value: "{{E0.issues.length}}", op: eq, to: 0 }
      status: success
      message: "No urgent issues — nothing to report."
      output: { count: 0 }

  # Assertion: urgent issues present → fail the pipeline
  - id: E2
    tool_name: exit
    input:
      when: { value: "{{E0.issues.length}}", op: gt, to: 5 }
      status: error
      message: "More than 5 urgent issues — paging on-call."
```

When a gate doesn't fire, its step result is `{passed: true, …}` and the plan continues.

## Logical gates: `when`

A single comparison — `value` (usually a template; [typed splice](/plans/template-language#typed-splice) keeps numbers numeric) against `to`:

| `op`                     | Meaning                                       |
| ------------------------ | --------------------------------------------- |
| `eq`, `ne`               | equality on any JSON value                    |
| `gt`, `lt`, `gte`, `lte` | numeric ordering                              |
| `empty`, `not_empty`     | arrays, strings, objects, null (`to` omitted) |
| `contains`               | substring, or array membership                |

Need OR? Use two exit steps. Need more? Use an inferred gate, or compute in a [user tool](/tools/user-defined) step first. And when the answer to a gate should be *running different steps* rather than ending the plan, use a [`decide` step](/plans/branching) — same gate grammar (spelled `if`), fork instead of stop; to keep *only some items of a list*, use a [`filter` step](/plans/selection) — same grammar again (spelled `where`), evaluated per item; to run steps *per item of a list*, use [`map`/`reduce`](/plans/iteration).

## Inferred gates: `infer`

A yes/no question judged by the [`judge` model role](/models/models-and-providers#roles) (falls back to `default` — point it at a cheap model):

```yaml theme={null}
  - id: E3
    tool_name: exit
    input:
      infer: |
        Does this status update indicate the project is blocked?

        {{E2.statusUpdates.0.body}}
      status: error
      message: "Project appears blocked."
```

The judgment is a forced-schema call returning `{verdict, reason}`; the gate fires only on a yes, the model's reason is appended to the message and carried in the envelope, and a no leaves `{passed: true, verdict: false, reason}` as the step result for downstream reference. One cheap inference, only when the gate is reached.

Add `model:` to override the model for this verdict — a `[models.named]` entry, a role name, or `default`. It defaults to the `judge` role. Use it to pin a specific gate to a cheaper or stronger model without changing the global `judge` role:

```yaml theme={null}
      infer: |
        Does this status update indicate the project is blocked?

        {{E2.statusUpdates.0.body}}
      model: fast
```

`model:` is ignored without `infer`.

For reusable or complex judgments, the composition form keeps reasoning as first-class data: a [prompt tool](/tools/user-defined#prompt--an-llm-call-as-a-tool) step returning a verdict, followed by a logical `exit when {{Ex.verdict}} eq true`.

In the [workbench](/workbench/plan-workbench), a fired gate is visible at a glance — everything it pre-empted shows `⊘` skipped:

<Frame caption="A fired success exit: the gate held, so the diff step and the solver never ran — and the pane says so.">
  <img src="https://mintcdn.com/graph/VaTetSEpy4ieswMa/images/workbench/exit-gate.svg?fit=max&auto=format&n=VaTetSEpy4ieswMa&q=85&s=a7de16be901d5eb5ea0ce65b4d06272d" alt="A fired success exit: the gate held, so the diff step and the solver never ran — and the pane says so." width="641" height="666" data-path="images/workbench/exit-gate.svg" />
</Frame>

## Exit semantics

| Surface           | success exit                                                   | error exit                                                                                  |
| ----------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `plan run`        | message on stderr, `output` map (if any) on stdout, **exit 0** | message on stderr, **exit 4** — distinct from `1` (infrastructure failure) so CI can branch |
| as a plan tool    | `{exited: true, status, message, output?}`                     | `is_error` tool result with the message                                                     |
| `--json` envelope | `"exit": {status, message, reason?, step}`                     | same                                                                                        |

`plan_and_execute`'s planner also has the exit tool, with a standing instruction to exit gracefully rather than fabricate results when data comes up empty.

Omitting both `when` and `infer` makes the exit unconditional — useful as the terminal step after a judgment chain.


## Related topics

- [Branching](/plans/branching.md)
- [CI checks](/cookbook/ci-checks.md)
- [Changelog](/changelog.md)
- [graph as an MCP server](/tools/mcp-server.md)
- [The planner](/plans/the-planner.md)
