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

# CLI reference

> Every command and flag

Global: `-v/-vv/-vvv` raises log verbosity (stderr); `GRAPH_LOG` accepts a tracing filter string. Exception: `graph workbench` logs to `<data_dir>/workbench.log` instead of stderr (the TUI owns the terminal; `[workbench].log_path` or `GRAPH_WORKBENCH_LOG` overrides the path) — see [Plan workbench](/workbench/plan-workbench#debug-logging).

## `graph ask`

One agent turn.

```
graph ask "<message>" [--thread [<id>]] [--json] [--no-stream]
```

| Flag              | Effect                                                                   |
| ----------------- | ------------------------------------------------------------------------ |
| `--thread` (bare) | continue the most recent thread                                          |
| `--thread <id>`   | continue a specific thread                                               |
| `--json`          | emit `{content, tool_calls_made, usage, thread_id}` instead of streaming |
| `--no-stream`     | print the final answer without token streaming                           |
| piped stdin       | appended to the message as input context                                 |

Put `--thread` after the message (it takes an optional value).

## `graph chat`

Interactive REPL. `--thread [<id>]` as above. Slash commands: `/thread`, `/state`, `/quit`.

## `graph workbench`

```
graph workbench plan [<name>|<path>]     # alias: graph wb plan
```

Full-screen TUI for drafting, inspecting, and test-running plans — see [Plan workbench](/workbench/plan-workbench). Opens blank (draft via chat), or with a plan resolved like `plan validate` (file path or identifier). Requires an interactive terminal on stdin and stdout.

## `graph plan`

```
graph plan list [--json]
graph plan show <name|path> [--json]
graph plan validate <name|path> [--json]
graph plan run <name> [<json>|@file|-] [--input k=v]... [--json]
```

`validate` checks structure (templates, references, ids) and resolves every step tool against the loadable catalog — packs, user tools, plans (composition followed transitively), and configured MCP servers (server-level; MCP tool names are only verifiable by connecting). Problems exit `1`; a file whose `requires_servers` declares an unconfigured server validates with a note (portable, just not runnable here). `run` performs the same resolution and refuses to start when it fails. Naming a plan hidden by unconfigured `requires_servers` errors with the missing servers.

`run` exit codes: `0` ok, `3` missing/invalid inputs (schema printed to stderr), `1` failure (including unresolvable tools). Solver output streams to stdout; output-mode plans print JSON; silent plans print nothing.

Per the [streams contract](/reference/scripting-contract#streams), stdout carries only deliverables — `list`'s rows, `show`'s YAML, and `--json` envelopes. Verdicts and hints go to stderr, so `plan validate` prints nothing to stdout on success and its exit code is the answer; `plan list` on an empty catalog leaves stdout empty rather than emitting a hint into it.

`show` and `validate` are deliberately lenient about *which* plans they will open: a plan that is invalid, or hidden by unconfigured `requires_servers`, is exactly the plan you need to inspect, so both resolve it from disk rather than requiring it to be in the runnable catalog. `list` only shows the runnable catalog; `list --json` also reports what was `skipped` (failed to load, with the reason) and `hidden` (with the missing servers).

## Authoring plans

The commands below create and edit plan files. They are stateless — each one resolves a plan, applies a single edit, and writes the YAML back — so there is no draft session and no undo (use version control). Only `draft` costs inference; everything else works with no provider credentials and no network.

```
graph plan new <identifier> [--name <n>] [--description <d>] [--output <path>] [--json]
graph plan draft <goal> [--from <name|path>] [--output <path>] [--stdout] [--json]
graph plan set <name|path> <attribute> <value>... [--json]
graph plan unset <name|path> <attribute> [--json]
graph plan step add <name|path> <id> <tool> <json|@file|-> [--reasoning <r>] [--before <id> | --after <id>] [--json]
graph plan step update <name|path> <id> <attribute> <value> [--json]
graph plan step rename <name|path> <id> <new-id> [--json]
graph plan step unset <name|path> <id> <attribute> [--json]
graph plan step rm <name|path> <id> [--json]
```

`new` scaffolds a plan with no steps. It is intentionally invalid on creation (`plan has no steps`) — an edit is only refused if it makes things *worse*, so a scaffold stays editable and a plan can be built up step by step without ever invoking the planner.

`draft` runs the planner over your tool catalog, one validated step at a time; if drafting exhausts its retries on a step, the valid prefix is saved and reported under `salvaged` so you can finish it with `step add` instead of redrafting. `--stdout` prints the YAML and writes nothing.

**Drafting happens once, at the start.** It replaces every step, so it is not a way to correct a plan — redrafting to fix one thing discards everything that was already right. `--from` exists to draft into a plan's *identity* (its identifier, name, description, and input schema), which is how you get a well-named plan without renaming afterwards:

```bash theme={null}
graph plan new commit_digest --description "Summarize commits between two refs"
graph plan draft "<goal>" --from commit_digest
```

Once a draft has steps worth keeping, every correction goes through `set`, `step add`, `step update`, `step rename`, and `step rm`. Each applies one intent, is validated atomically, and is refused if it would break the plan.

### Attributes

One attribute per call, named exactly as it appears in the plan file. See [Plan schema](/reference/plan-schema) for what each one means.

| `plan set <attribute>`              | Value                                                                   |
| ----------------------------------- | ----------------------------------------------------------------------- |
| `name`, `description`, `identifier` | One string                                                              |
| `exemplars`, `requires_servers`     | One or more strings                                                     |
| `input_schema`                      | A JSON Schema object: inline JSON, `@file`, or `-`                      |
| `solver`                            | `{query_to_answer, system_prompt?, data}`: inline JSON, `@file`, or `-` |
| `output`                            | A template map: inline JSON, `@file`, or `-`                            |

| `plan step update <attribute>` | Value                                                                       |
| ------------------------------ | --------------------------------------------------------------------------- |
| `tool`                         | A tool name, or a control step (`exit`, `agent`, `decide`, `map`, `reduce`) |
| `input`                        | The step's whole input object: inline JSON, `@file`, or `-`                 |
| `reasoning`                    | One string                                                                  |

`solver` and `output` are the two finish modes and are mutually exclusive, so setting either clears the other; `unset solver` / `unset output` leaves a silent plan. `unset` refuses the required fields (`identifier`, `name`, `description`, and a step's `tool`/`input`) — change those with `set` instead.

`step rename` is separate from `step update` because it is not a plain field write: it also rewrites every downstream `{{<old-id>.…}}` reference in later steps, the output map, and the solver templates.

### What gets rejected

Every edit runs through the same guard the [plan workbench](/workbench/plan-workbench) uses: an edit is rejected only if it introduces a validation problem that wasn't already there, and a rejected edit leaves the file untouched. Problems that already existed never block an edit (otherwise repairing a broken plan would be impossible) — they come back as `preExistingProblems` on the success result.

So `graph plan step rm demo E1` fails when a later step still references `{{E1.…}}`, naming the template that would dangle, while `graph plan set demo description '…'` succeeds on a plan that is invalid for unrelated reasons.

Exit codes: `0` applied, `1` rejected. With `--json` the envelope is printed to stdout *before* the non-zero exit, so a caller always gets the structured problem list. Without it, a one-line result goes to stderr and stdout stays empty — see the [scripting contract](/reference/scripting-contract).

Changing `identifier` writes a *new* file (`<plans dir>/<new>.yaml`) and leaves the original in place, reporting `renamedFrom`; removing the old file is left to you, so a rename can never silently destroy a plan.

An edit reserializes the whole file, so cosmetic YAML is normalized: flow mappings (`input: { a: b }`) become block mappings, folded scalars (`>`) become literal ones (`|`), and comments are dropped. The plan itself is unchanged — commentary belongs in `description` and each step's `reasoning`, which are real fields the agent also reads.

## `graph tools`

```
graph tools list [--json]
graph tools show <name> [--json]
graph tools test <name> [<json>|@file|-] [--input k=v]...
```

Operates on the full catalog: MCP (`server__`), user (`user__`), bundled packs (`builtin__`), plan (`plan__`), and `plan_and_execute`. `list` groups the catalog by source — names and one-line descriptions, `plan_and_execute` under `(core)`.

`list --json` emits the flat catalog instead, each entry carrying the **namespaced** name a plan step has to use, its `source`, the full `description` (untruncated — it is the routing signal), `readOnly`, and `hasOutputSchema`, plus a per-source roll-up. That is the surface for a script or an [agent authoring a plan](/plans/authoring#authoring-from-the-command-line); per-tool schemas stay on `tools show`.

`show --json` emits one tool's whole definition — `inputSchema` included, which is what you need to write a step's input object — with every key always present, `null` when absent. Unknown tool names stay ordinary errors (stderr, exit `1`); there is no envelope for them. See the [scripting contract](/reference/scripting-contract#the-tool-catalog).

`test --json` wraps the call: `{tool, isError, result}`. A tool that *reports* an error still exits `0` — the call completed and its payload is the result worth reading, so `isError` carries the distinction the exit code does not. Without `--json` the tool's own result is the deliverable, unwrapped.

## `graph threads`

```
graph threads list [--json]
graph threads show <id> [--state] [--json]
graph threads rm <id> [--json]
```

## `graph mcp`

```
graph mcp list [--json]              # configured servers
graph mcp tools [server] [--json]    # discovered tools
graph mcp test <server> [--json]     # connect, count tools and declared output schemas
graph mcp serve --dir <path>         # serve graph's plans to an agent over MCP (stdio)
```

graph is an MCP *client*: each `[mcp.<name>]` entry becomes a set of `<name>__*` tools — see [MCP servers](/tools/mcp-servers). It is also an MCP *server*: `graph mcp serve` exposes every plan in the catalog as a callable tool, plus the plan authoring commands, so another agent can build and run graph plans. See [graph as an MCP server](/tools/mcp-server).

## `graph shapes`

```
graph shapes list [--json]
graph shapes show <tool> [--json]
```

Inspect the [shape cache](/tools/shape-cache): `list` prints tool names and seen-counts, `show` prints one tool's cached schema and example. The same data lives as plain files under `<data_dir>/shapes/`.

## `graph config`

```
graph config show    # merged effective config
graph config init    # write a commented starter (./.graph/; --global for ~/.config/graph/)
graph config path    # config file locations and which exist
```

`init` refuses to overwrite an existing config; `--force` overwrites it.

The starter ends with a [`[prompts]` section](/reference/configuration#prompts) carrying the built-in chat and workbench system prompts, written out verbatim so they can be tuned in place — delete a field to fall back to the shipped default.


## Related topics

- [Configuration](/reference/configuration.md)
- [graph as an MCP server](/tools/mcp-server.md)
- [Ask steps](/plans/ask-step.md)
- [User-defined tools](/tools/user-defined.md)
- [Automation recipes](/cookbook/automation.md)
