> ## 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.

# Agent steps

> A bounded tool-calling loop inside a plan step, with guaranteed structured output

An `agent` step runs a bounded tool-calling loop and returns **structured JSON conforming to a schema you declare** — or says it couldn't, via `final: false`. It is a functional component, not a conversation: a prompt goes in, a validated object comes out.

Use it for the one thing plans genuinely can't express — a task where *which* tools to call, and how many times, depends on what earlier calls returned. Everything else belongs in ordinary steps, where dataflow is typed and inference-free.

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

  - id: E1
    tool_name: agent
    input:
      prompt: |
        Review these issues and identify which are blocked, and why:
        {{E0.issues}}
      tools: ["linear__*"]                 # patterns, resolved at validate time
      max_iterations: 5
      output_schema:
        type: object
        required: [blocked]
        properties:
          blocked:
            type: array
            items:
              type: object
              properties:
                id: { type: string }
                reason: { type: string }
```

## Input

| Field            | Required | Meaning                                                                                                       |
| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `prompt`         | yes      | The task. Renders against prior results like any step input.                                                  |
| `output_schema`  | yes      | JSON Schema for the result. Must be `type: object`.                                                           |
| `tools`          | no       | Names or wildcard patterns to expose. Omit for the whole catalog; `[]` is a validation error, not "no tools". |
| `max_iterations` | no       | Inference rounds. Default 8.                                                                                  |
| `model`          | no       | Model role or [named model](/reference/configuration). Default: the `chat` role.                              |
| `system_prompt`  | no       | Extra guidance appended to the built-in system prompt. Renders against the scope, like `prompt`.              |

A round is **one model call**, including the one that produces the final answer — so `max_iterations: 1` can answer from the prompt alone but can never call a tool *and* answer. A malformed answer (schema miss, or no text and no tool call) also costs a round; retries and provider failover do not.

Field names are snake\_case, like every other part of a [plan file](/reference/plan-schema). The camelCase spellings (`outputSchema`, `maxIterations`, `systemPrompt`) still load, but any authoring command that rewrites the file normalizes them.

`output_schema`'s *value* is yours: graph never rewrites the property names inside the schema, so an agent can be held to a camelCase contract if that is what its consumer expects.

The whole input is checked at **load time**, wherever the step appears — top level or inside a `decide`/`map`/`reduce` body: unknown fields, a non-string prompt, `output_schema` that isn't valid object-typed JSON Schema, a `max_iterations` under 1, an empty `tools`, and template references that point forward or at nothing. A malformed agent step never reaches the run.

## Result

```json theme={null}
{
  "output":       { "blocked": [ … ] },
  "iterations":   3,
  "tools_called": [ { "tool": "linear__list_issues", "round": 1 } ],
  "final":        true
}
```

Later steps reference `{{E1.output.blocked}}` — `output` is the schema-conforming payload; the rest is provenance.

`final` is `false` when the iteration budget ran out before the agent produced conforming output. In that case `output` is `{}` and **does not** conform to `output_schema` — the guarantee is "conforming output, or `final: false`", never a fabricated result. A later step reaching into `output` then fails as a bad path, so **check it**, or gate on it:

```yaml theme={null}
  - id: E2
    tool_name: exit
    input:
      when: { value: "{{E1.final}}", op: eq, to: false }
      status: error
      message: "agent could not finish within its budget"
```

## Tool selection

`tools` accepts exact names and `*` wildcards anywhere: `linear__*`, `*__search`, `linear__list_*`, or `*` for everything.

Patterns are **resolved against the catalog at validate time**, so a plan naming tools that cannot load fails before it runs — like any other step tool name:

```
step E1: `tools` pattern 'linear__*' needs MCP server 'linear',
which is not configured under [mcp.linear]
```

`builtin__*`, `user__*`, and `plan__*` resolve exactly. MCP patterns resolve at the *server* level only, because listing a server's tools means connecting to it; the individual tool is still checked at dispatch. A pattern that matches nothing at run time is an error naming that pattern — a typo shrinks the catalogue silently otherwise.

`plan_and_execute` is **never** available inside an agent: nested planning loops have no coherent cost boundary. It is not advertised, and a model that asks for it anyway gets a tool error rather than a nested planner run. `plan__*` tools are available and work normally, so compose with plans instead.

## What it costs

An agent step is the most expensive thing in the pipeline: **one inference per round**, plus its tool calls. A `max_iterations: 8` agent can cost 8 inferences where an ordinary step costs zero. Reach for `map` with a per-item inference before reaching for an agent — see [iteration](/plans/iteration).

## Inside control-step bodies

`agent` is a legal body step for [`decide`](/plans/branching), [`map`, and `reduce`](/plans/iteration), and the body scope reaches its prompt:

```yaml theme={null}
  - id: E1
    tool_name: map
    input:
      over: "{{E0.incidents}}"
      concurrency: 4
      do:
        tool_name: agent
        input:
          prompt: "Diagnose incident {{item.id}}: {{item.summary}}"
          tools: ["grafana__*", "user__git_log"]
          output_schema:
            type: object
            properties:
              cause: { type: string }
```

`{{item}}`, `{{index}}`, and `{{accumulator}}` all resolve. Note the multiplier: this runs one agent *per item*.

The other control steps (`exit`, `decide`, `map`, `reduce`) still cannot nest in a body — call a `plan__*` for that.

## Errors

* **Tool failures return into the loop** as error results, so the agent can explain or route around them. They do not fail the step.
* **Output that doesn't match `output_schema`** gets one `repair`-role fix-up pass; if that fails, the error goes back to the agent and consumes a round.
* **Transient LLM errors** retry with backoff and never consume a round.
* **A gate abort** during an inner tool call is a hard stop, exactly as elsewhere — see [errors and replanning](/plans/errors-and-replanning).
* **Empty data** while rendering `prompt` or `system_prompt` degrades rather than failing, consistent with every other step — at the top level and inside a body alike.

Every tool call an agent makes goes through the same dispatch path as any other step, so gates, events, the shape cache, and plan-cycle detection all apply at agent depth too. Inner calls report a nested step path — `E1/agent.2/linear__list_issues` at the top level, `E1/do.3/agent.2/linear__list_issues` for an agent in a map body — so a breakpoint on the agent step pauses each call it makes, and concurrent map items stay distinguishable.


## Related topics

- [Ask steps](/plans/ask-step.md)
- [Plan workbench](/workbench/plan-workbench.md)
- [Changelog](/changelog.md)
- [Core concepts](/getting-started/concepts.md)
- [Errors & replanning](/plans/errors-and-replanning.md)
