Tool policy
One file per repository decides what the model may call, where, and whether to ask.
Tools are off until the directory ahi tui starts in has an ahi.tools.yaml with an approve rule. Only tools compiled into ahi exist; the file selects and bounds them and never defines or fetches one.
Start from a shipped policy
ahi tools examples # names the shipped policies
ahi tools examples show commands
ahi tools init --write # writes read-all into the working directory
ahi tools list # shows the file in place, painted by outcome
init prints first and writes only with --write; an existing file is replaced only with --write --force. In a terminal every rule is painted green for approve, gray for ask, and red for deny, the same colors the chat uses for diffs. The output of both commands is reproduced in Shipped policies below.
The rules
version: 1
default: deny
rules:
- id: no-env
action: tool_call
match: {tool: read_file, path: .env}
effect: deny
- id: read-tree
action: tool_call
match: {tool: read_file}
effect: approve
- id: edit-sources
action: file_write
match: {tool: edit_file, path: src}
effect: approve
ask: true
- id: cargo-test
action: command
match: {argv: [cargo, test]}
effect: approve
toolchain_env: [CARGO_TARGET_DIR]
Three action kinds govern the seven tools:
| Action | Tools | Matched on |
|---|---|---|
tool_call | read_file, list_dir, find_files, search_files | A path prefix |
file_write | edit_file, write_file | A path prefix, and which of the two |
command | run_command | A token-by-token argv prefix and a working-directory prefix |
A matching deny beats any approval. ask: true prompts in chat for each call: y once, s for the session, n deny. Only tools an approve rule names are advertised to the model. Model output and tool results never create a grant.
What the boundary is
Every builtin tool works inside the working directory. Paths are relative to it; absolute paths, .., and symbolic links are refused before any read, write, or command, and run_command starts only in a directory inside it. The policy file, the hook configuration, .gitignore, and .git can never be written by a tool.
This is not a sandbox. A program approved by a command rule runs on the host with the operator's access: it can follow symbolic links, read files above the working directory, and run other programs. Approve commands accordingly, or run ahi tui inside ahi dev run for a container boundary.
Shipped policies
Seven policies ship inside the binary. ahi tools examples names them; ahi tools examples show prints each one with every rule painted by its outcome: approve hands the call to the model, ask prompts in chat first, deny refuses it. A rule's outcome is its effect, with ask: true turning an approval into a prompt.
ahi tools examples
Shipped tool policies (examples/tools, embedded in ahi). Each is a complete ahi.tools.yaml.
read-all read, list, find, and search the whole working directory; root .env denied; no prompts (default)
ask-every-read every read, listing, find, and search in the working directory asks in chat
sources-only list the whole working directory; read, find, and search only src and docs
list-only list directories in the working directory only; reads are denied
edit-sources read the whole working directory; edit under src and tests after a prompt; create under tests
commands read the whole working directory; cargo check and test run; other cargo asks; publish and install denied
deny-all valid, no approve rule: no tool is advertised
Every builtin tool works inside the directory `ahi tui` starts in: paths are relative to it, and absolute paths, `..`, and symbolic links are refused.
This is not a sandbox. A program approved by a command rule runs on the host with the operator's access: it can follow symbolic links, read files above the working directory, and run other programs. Approve commands accordingly, or run `ahi tui` inside `ahi dev run` for a container boundary.
`ahi tools examples show <name>` prints one.
`ahi tools examples show` prints all.
`ahi tools init --example <name> --write` copies one into the working directory.ahi tools examples show
approve ask deny the model gets the call, is asked in chat first, or is refused
read-all examples/tools/read-all.yaml
# Read, list, find, and search anywhere in the working tree; no prompts. The
# root .env is denied to read_file. Rules match a path prefix, so a nested
# .env or .env.local is not covered, and search_files skips it only when
# .gitignore does.
version: 1
default: deny
rules:
- id: no-env
action: tool_call
match: {tool: read_file, path: .env}
effect: deny
- id: list-tree
action: tool_call
match: {tool: list_dir}
effect: approve
- id: find-tree
action: tool_call
match: {tool: find_files}
effect: approve
- id: search-tree
action: tool_call
match: {tool: search_files}
effect: approve
- id: read-tree
action: tool_call
match: {tool: read_file}
effect: approve ask-every-read examples/tools/ask-every-read.yaml
# Every read, listing, find, and search shows an approval prompt in chat.
version: 1
default: deny
rules:
- id: ask-list
action: tool_call
match: {tool: list_dir}
effect: approve
ask: true
- id: ask-read
action: tool_call
match: {tool: read_file}
effect: approve
ask: true
- id: ask-find
action: tool_call
match: {tool: find_files}
effect: approve
ask: true
- id: ask-search
action: tool_call
match: {tool: search_files}
effect: approve
ask: true sources-only examples/tools/sources-only.yaml
# List anywhere; read, find, and search only under src and docs. Deny rules
# win over approvals. The .env and target denies are root paths.
version: 1
default: deny
rules:
- id: no-env
action: tool_call
match: {tool: read_file, path: .env}
effect: deny
- id: no-build-output
action: tool_call
match: {tool: read_file, path: target}
effect: deny
- id: list-tree
action: tool_call
match: {tool: list_dir}
effect: approve
- id: read-sources
action: tool_call
match: {tool: read_file, path: src}
effect: approve
- id: read-docs
action: tool_call
match: {tool: read_file, path: docs}
effect: approve
- id: find-sources
action: tool_call
match: {tool: find_files, path: src}
effect: approve
- id: find-docs
action: tool_call
match: {tool: find_files, path: docs}
effect: approve
- id: search-sources
action: tool_call
match: {tool: search_files, path: src}
effect: approve
- id: search-docs
action: tool_call
match: {tool: search_files, path: docs}
effect: approve list-only examples/tools/list-only.yaml
# The model may list directories but every read is denied.
version: 1
default: deny
rules:
- id: list-tree
action: tool_call
match: {tool: list_dir}
effect: approve edit-sources examples/tools/edit-sources.yaml
# Read anywhere; edit existing files under src and tests after a prompt;
# create new files under tests without one. Policy files, hook
# configuration, .gitignore, and .git are never writable whatever is here.
# The .env and Cargo.lock rules name root paths; rules match a path prefix.
version: 1
default: deny
rules:
- id: no-env
action: tool_call
match: {tool: read_file, path: .env}
effect: deny
- id: read-tree
action: tool_call
match: {tool: read_file}
effect: approve
- id: list-tree
action: tool_call
match: {tool: list_dir}
effect: approve
- id: find-tree
action: tool_call
match: {tool: find_files}
effect: approve
- id: search-tree
action: tool_call
match: {tool: search_files}
effect: approve
- id: edit-sources
action: file_write
match: {tool: edit_file, path: src}
effect: approve
ask: true
- id: edit-tests
action: file_write
match: {tool: edit_file, path: tests}
effect: approve
ask: true
- id: new-tests
action: file_write
match: {tool: write_file, path: tests}
effect: approve
- id: no-lockfile
action: file_write
match: {path: Cargo.lock}
effect: deny commands examples/tools/commands.yaml
# Reads anywhere except the root .env; cargo check and test run without a
# prompt; any other cargo subcommand asks; cargo publish and cargo install
# are denied; any sh command whose working directory is scripts asks (an
# argv prefix cannot name a file, so `sh -c` is included). Commands run on
# the host with the hook runner's scrubbed environment plus the named
# toolchain variables.
version: 1
default: deny
rules:
- id: no-env
action: tool_call
match: {tool: read_file, path: .env}
effect: deny
- id: read-tree
action: tool_call
match: {tool: read_file}
effect: approve
- id: list-tree
action: tool_call
match: {tool: list_dir}
effect: approve
- id: find-tree
action: tool_call
match: {tool: find_files}
effect: approve
- id: search-tree
action: tool_call
match: {tool: search_files}
effect: approve
- id: cargo-check
action: command
match: {argv: [cargo, check]}
effect: approve
toolchain_env: [CARGO_TARGET_DIR]
- id: cargo-test
action: command
match: {argv: [cargo, test]}
effect: approve
toolchain_env: [CARGO_TARGET_DIR]
- id: cargo-other
action: command
match: {argv: [cargo]}
effect: approve
ask: true
- id: no-publish
action: command
match: {argv: [cargo, publish]}
effect: deny
- id: no-install
action: command
match: {argv: [cargo, install]}
effect: deny
- id: repo-scripts
action: command
match: {argv: [sh], path: scripts}
effect: approve
ask: true deny-all examples/tools/deny-all.yaml
# Valid policy with no approve rule: no tool is advertised to the model.
version: 1
default: deny
rules:
- id: no-reads
action: tool_call
match: {tool: read_file}
effect: deny
- id: no-lists
action: tool_call
match: {tool: list_dir}
effect: deny Captured from ahi 0.14.1. Comments and the header stay plain; each rule is painted by its outcome, the same colours the chat screen uses for diffs.