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

# User-defined tools

> Wrap commands, queries, and prompts as first-class tools

Drop a YAML file in `./.graph/tools/` (or `~/.config/graph/tools/` for tools you want everywhere) and it becomes `user__<name>` — callable by the agent, referenced in plan steps, invocable from the CLI. Three kinds.

## `exec` — wrap any command

```yaml theme={null}
name: git_log
description: Recent git commits for a local repository path.
kind: exec
command: git
args:
  - "-C"
  - "{{input.repo}}"
  - "log"
  - "-n"
  - "{{input.count}}"
  - "--pretty=format:{\"hash\":\"%h\",\"subject\":\"%s\"},"
output: text                # or json: stdout parsed as JSON
read_only: true
input_schema:
  type: object
  required: [repo]
  properties:
    repo: { type: string, description: Absolute path to a git repository }
    count: { type: integer }
```

| Field          | Required | Notes                                                                                     |
| -------------- | -------- | ----------------------------------------------------------------------------------------- |
| `command`      | yes      | the executable                                                                            |
| `args`         | no       | templated from the input (`{{input.*}}` only — step references don't exist inside a tool) |
| `env`          | no       | extra environment; values support `${VAR}` from the parent environment                    |
| `cwd`          | no       | working directory for the process                                                         |
| `timeout_secs` | no       | kill-after budget; default 60                                                             |
| `output`       | no       | `text` (default: stdout wrapped as `{"text": …}`) or `json` (stdout parsed as JSON)       |

Unparseable `json` output, non-zero exit (with stderr captured), and timeouts all return as structured tool errors.

<Warning>
  Exec tools are **arbitrary code execution, by design** — you author them, graph runs them. Treat the tools directory like you treat your shell profile.
</Warning>

## `prompt` — an LLM call as a tool

```yaml theme={null}
name: summarize
description: Summarize any text into a gist and three keywords.
kind: prompt
prompt: |
  Summarize into a single-sentence gist and exactly three keywords:

  {{input.text}}
model: chat                  # a role name (chat, solver, …) or a [models.named] entry
output_schema:               # optional → enforced structured output
  type: object
  required: [gist, keywords]
  properties:
    gist: { type: string }
    keywords: { type: array, items: { type: string } }
```

With an `output_schema`, the result is validated JSON; without one, `{"text": …}`. Useful as a cheap sub-task inside plans — classify, extract, reword — with `model` controlling cost: a [role name](/models/models-and-providers#roles) (`chat` default, with the usual fallback to `default`) or a [named model](/models/models-and-providers#named-models) like `nano`. An unknown name fails the call with the configured names listed.

## `reshape` — project data into a new shape

```yaml theme={null}
name: pr_shape
description: Normalize PR metadata into our internal shape.
kind: reshape
read_only: true              # implied — reshape is always pure
shape:
  base_sha: "{{input.baseRefOid}}"   # rename a key
  pr: "{{input.number}}"             # exact tag keeps the number type
  title: "PR #{{input.number}}"      # mixed text renders to a string
input_schema:
  type: object
  properties:
    baseRefOid: { type: string }
    number: { type: integer }
```

A `reshape` tool renders its `shape` — a JSON tree whose leaf strings are templates — with the same [typed splice](/plans/template-language#typed-splice) as step inputs: an exact-tag leaf keeps the source value's type, mixed text interpolates to a string. No process, no LLM, no side effects, so it's `read_only` by default. It moves data — rename, pick, nest, flatten — but is [logic-less](/plans/template-language), so it can't derive values (sums, casing, conditionals); use `exec` for those.

The shape renders against the tool's own `input` root, so a fixed doc `shape` references its fields as `{{input.*}}`. Inside a plan the pipeline renders the step input first (against `item`, `E0…`, `input`), so a shape authored as `{{item.number}}` reaches the tool already resolved and passes through unchanged — one effective render either way, like a `map`'s `over`.

Set `caller_shape: true` instead of a fixed `shape` to take the shape from each call's `shape` input — the generic [`builtin__reshape`](/tools/builtins#builtin-reshape) path, where the planner authors the mapping per step referencing the surrounding step's roots. Fixed-shape leaves are validated at load time and rendered by the tool against its own `input`; a caller shape is rendered *once*, by the pipeline, as part of the calling step's input (a bad path fails that render), and the tool returns it verbatim — so substituted text containing `{{ … }}` is never re-parsed as a template. One of `shape` or `caller_shape` is required.

## Built-in packs

The same YAML format also powers [**built-in tools**](/tools/builtins): packs compiled into the binary and served under the `builtin__` namespace (enable with `[tools] packs = ["github"]`). To customize one, copy its YAML into a tools directory and reference your `user__` copy from plans.

## Shared behavior

* `input_schema` validates before dispatch — missing fields return actionable errors (in chat, the agent asks and retries).
* `output_schema`, when declared, feeds the planner's shape knowledge just like an MCP output schema.
* Names must match `[a-zA-Z0-9_-]+`; templates referencing anything but `{{input.*}}` are load-time errors.


## Related topics

- [Template language](/plans/template-language.md)
- [Core concepts](/getting-started/concepts.md)
- [Quickstart](/getting-started/quickstart.md)
- [Changelog](/changelog.md)
- [The tool catalog](/tools/overview.md)
