# Tools


kli ships the tools the agent calls during a turn. Two run code: `bash` runs a shell command, `eval` runs Common Lisp in the live image. The rest are the file surface: `read`, `find`, and `search` look at files; `write` and `edit` change them.

Every tool carries a capability gate. A gate is a capability name the tool requires before it runs. By default kli grants every capability, so every tool runs. The `capabilities` array in `settings.json` overrides that: an empty array denies all gated tools, and an array of capability names grants exactly those (see [Capabilities](/kli/config/capabilities)). Each tool below lists its gate. A call whose gate is not granted returns an error and runs nothing.

## bash

Run a shell command in a child process. The command runs through `sh -c`, with stdout and stderr captured separately.

Capability gate: `process/exec`.

| Parameter | Type | Required | Effect |
| --- | --- | --- | --- |
| `command` | string | yes | The shell command to run. |
| `directory` | string | no | Working directory for the command. Defaults to the process working directory. |
| `input` | string | no | Text fed to the command's stdin. Defaults to empty. |
| `shell` | string | no | Shell binary invoked with `-c`. Defaults to `sh`. |
| `timeout` | integer | no | Wall-clock limit in seconds. Defaults to 30, clamped to a maximum of 300. |

Output limits and behavior:

- A command that runs past its timeout is killed (its whole process group when possible) and the call returns an error naming the timeout.
- stdout and stderr are each capped at 1 MiB (1048576 characters). Output past the cap is dropped and the result notes the truncation.
- The exit code rides in the result details. A non-zero exit returns an error result.
- Interactive programs are rejected before the command runs. kli inspects the first effective command word (skipping `env`, `command`, `exec`, `time`, `sudo`, `doas`, leading assignments, and their options) and refuses these names: `vi`, `vim`, `nvim`, `nano`, `emacs`, `less`, `more`, `man`, `top`, `htop`, `watch`, `ssh`, `mosh`, `tmux`, `screen`. The rejection returns an error and runs nothing.

## eval

Read and evaluate Common Lisp forms in the running kli image. The forms run on a dedicated thread under the requested package. The value of each form, and anything the forms print, comes back in the result.

Capability gate: `image/eval`.

| Parameter | Type | Required | Effect |
| --- | --- | --- | --- |
| `form` | string | yes | Common Lisp source. All forms in the string are read and evaluated in order. |
| `package` | string | no | Package the forms read and run in. Defaults to `CL-USER`. An unknown package name is an error. |

Output limits and behavior:

- Evaluation has the same time budget as `bash`: 30 seconds by default, 300 maximum. A form running past the deadline is interrupted, and the result notes that the interrupted form may have left image state partially modified.
- Captured output is capped at 1 MiB (1048576 characters), counted and dropped at the stream so a printing loop cannot exhaust the heap.
- Printed values are bounded: `*print-length*` is 100, `*print-level*` is 20, and `*print-circle*` is on, so a large or circular value prints to a finite string.

## read

Read a file and return its lines with anchors. Each line comes back as `LINE:HH|content`, where `LINE` is the 1-based line number and `HH` is a two-hex-digit hash of the line's content. These anchors are what `edit` references.

Capability gate: `file/read`.

| Parameter | Type | Required | Effect |
| --- | --- | --- | --- |
| `path` | string | yes | File to read. |
| `start` | integer | no | First line of an inclusive 1-based range. A start past end-of-file is an error. |
| `end` | integer | no | Last line of the range. An end past end-of-file clamps to the last line. |
| `raw` | boolean | no | When true, return line content with no `LINE:HH|` prefix. Defaults to false. |

A read over a file larger than 2 MiB is an error, and the error names `search` as the way to inspect a file that large. An empty file reads as `(empty file)`. Reading a file records its current line hashes for the session, which is the read-before-edit state `edit` requires.

## find

List file paths matching a glob pattern. Returns paths only, no file content.

Capability gate: `file/read`.

| Parameter | Type | Required | Effect |
| --- | --- | --- | --- |
| `pattern` | string | yes | Glob pattern, for example `src/**/*.lisp`. `*` spans a run of characters within one path segment; a bare `**` segment spans any depth, including zero. |

Symlinks to files match; symlinked directories are not descended. At most 1000 paths are reported, sorted, with any overflow counted in the output. The walk stops and returns a partial result if it visits 100000 entries or runs longer than 10 seconds; the stop is surfaced in the text.

## search

Search file contents with a regular expression across one file or a glob. Match lines come back as `*LINE:HH|content`, surrounding context lines as ` LINE:HH|content` (space-prefixed). Each matching file is read whole, so its anchors are recorded and ready for `edit` with no separate `read`.

Capability gate: `file/read`.

| Parameter | Type | Required | Effect |
| --- | --- | --- | --- |
| `pattern` | string | yes | Regular expression matched against each line. |
| `path` | string | yes | A single file or a glob, for example `src/**/*.lisp`. |
| `context` | integer | no | Number of surrounding lines to show around each match. Defaults to 0. |

Files that fail to read as text are skipped. Files over 2 MiB are skipped, with the count surfaced in the result. The result is capped at 100 matching files and 1000 rendered match lines; the same 100000-entry and 10-second walk bounds as `find` apply.

## write

Write content to a file, replacing it whole. Missing parent directories are created. The file's prior content rides in the result details so the change can render as a diff.

Capability gate: `file/write`.

| Parameter | Type | Required | Effect |
| --- | --- | --- | --- |
| `path` | string | yes | File to write. |
| `content` | string | yes | Full new content of the file. |

## edit

Apply a hashline patch to one or more files, or accept a pending Common Lisp repair preview. A patch is whole-or-nothing: every anchor is validated against current disk content before any file is written, and a single stale or unread anchor rejects the entire patch. No file is partially modified.

Capability gate: `file/edit`.

| Parameter | Type | Required | Effect |
| --- | --- | --- | --- |
| `input` | string | mode-specific | A hashline patch (grammar below). Required unless accepting a repair preview. |
| `repair` | string | no | Common Lisp repair policy for unbalanced patch output: `safe` (default), `preview`, or `reject`. |
| `accept_repair` | string | mode-specific | Candidate id returned by a repair preview. Required only when accepting a preview. |
| `path` | string | with `accept_repair` | Target path for the candidate; used for authority gating and stale-content checks. |

A patch is one or more sections, each opened by a `@@ PATH` header and followed by ops:

| Line | Effect |
| --- | --- |
| `@@ PATH` | Section header. One or more sections per patch. |
| `+ LINE:HH` | Insert the following payload after the anchored line. |
| `+` | Insert payload at end of file (bare `+`). |
| `< LINE:HH` | Insert the following payload before the anchored line. |
| `<` | Insert payload at beginning of file (bare `<`). |
| `- A:HH..B:HH` | Delete the inclusive line range. |
| `= A:HH..B:HH` | Replace the range with the payload; no payload deletes the range. |
| `~content` | A payload line. Everything after `~` is verbatim. |

Anchor validation:

- The target file must have been read this session (by `read` or by a `search` that matched it). An unread file rejects the patch with a prompt to read it first.
- Each `LINE:HH` anchor is re-hashed against current disk content at edit time. A hash that no longer matches is stale and rejects the whole patch instead of overwriting changed content; the error names the file and line to re-read.
- Overlapping range ops, and an insert that falls inside a range op, are rejected, and the error names both spans.

On success the result reports each file as `Edited PATH (+added -removed)` and prints fresh `LINE:HH|content` anchors for the changed regions. Those anchors are valid for a follow-up edit with no intervening read. Edited files end with a trailing newline.

For Common Lisp source, `edit` checks the patched file for balanced delimiters before writing. Balanced output writes normally. Unbalanced output is repaired in memory according to `repair`: `reject` errors without writing, `preview` returns a candidate without writing, and `safe` writes only repairs classified as local to the edited range with a stable top-level form count. Unsafe repairs return a preview and must be accepted with `accept_repair` plus `path`; acceptance rechecks that the file has not changed since the preview.
