Tools reference
This reference is for people who write or maintain tool definitions for Opair. If you just want to know how tools work, see the tools guide instead.
- How tools are defined
- Tool fields
- Handler fields
- Exec handlers
- Builtin handlers
- Permissions
- Availability
- Validation rules
How tools are defined
One JSON file per tool,
in the tools directory
(<user config dir>/opair/tools,
overridable with OPAIR_TOOLS_DIR).
Every .json file in that directory is a tool.
Unknown fields are rejected.
Files are re-read when they change on disk,
although a brand new tool needs a restart
before it can be called.
The file name is the snake_case form
of the tool's name:
readProjectFile lives in read_project_file.json.
A file that fails validation prevents Opair starting, so a typo can't silently drop a tool.
Which loops can call a given tool
is decided by that loop's toolWhitelist.
Most tools declare a handler block
and are fully described by their JSON.
A few have their handler implemented in Go instead,
notably askQuestion
and the sub-agent entry points (plan, reviewDiff and friends).
Their JSON omits the handler block
but is otherwise the same.
Example, a tool that runs go test:
{
"internal": false,
"schema": {
"function": {
"description": "Run `go test` from the project root to execute Go tests. All go test args are supported except `-coverprofile`, `-exec` and `-toolexec`.",
"name": "goTest",
"parameters": {
"properties": {
"args": {
"description": "Arguments for go test.",
"items": { "type": "string" },
"type": "array"
}
},
"required": [],
"type": "object"
},
"strict": true
},
"type": "function"
},
"writes": "artifacts",
"handler": {
"denyArgs": ["-coverprofile", "-exec", "-toolexec"],
"type": "exec",
"bin": "go",
"subcommand": "test"
}
}
Tool fields
| Field | Required | Meaning |
|---|---|---|
schema |
yes | The tool definition sent to the LLM: name, description, parameters and strict, which is passed through to the provider. |
handler |
no | What the tool does (see Exec handlers and Builtin handlers). |
reads |
no | Read permission category (see Permissions). |
writes |
no | Write permission category (see Permissions). |
exec |
no | Set to "untrusted" for tools that can execute third-party code. |
internal |
no | Set to true to withhold the tool from the agent. |
slashCommand |
no | Expose the tool as a slash command, e.g. "review-diff" for /review-diff. |
requiresBin |
no | Binary the tool depends on, for tools that have no handler.bin of their own. |
Handler fields
| Field | Applies to | Meaning |
|---|---|---|
type |
all | "exec" or a builtin type (see Builtin handlers). Required. |
bin |
exec | Binary to run. Required for exec. |
subcommand |
exec | Fixed subcommand, e.g. "test" runs <bin> test <args>. Multi-word subcommands are split on whitespace. |
env |
exec | Extra environment variables for the command. |
allowArgs |
exec | Prefix whitelist for model-supplied arguments. |
denyArgs |
exec | Prefix denylist; wins over allowArgs. |
scope |
builtin | Where path arguments resolve (see Builtin handlers). |
includeHidden |
builtin (findInProject) |
Search dotfiles as well as ordinary project files. |
includeIgnored |
builtin (findInProject) |
Search gitignored files as well as ordinary project files. |
summary |
exec | How results are summarised in the status area. Values: commandEcho, gitDiff, gitLog, gitStatus, grep, lineCount:<noun>. Empty prints the command output. |
Exec handlers
Exec tools run an external command
built from bin, an optional subcommand
and an args array supplied by the model.
The command runs in the project root.
There's no shell involved;
arguments are passed to the binary directly.
Argument filtering uses prefix matching.
When denyArgs is non-empty,
any argument with a denied prefix is rejected.
When allowArgs is non-empty,
any argument without an allowed prefix is rejected.
If both are set, allowArgs is ignored.
Builtin handlers
Builtin types map to file and directory operations implemented in Go. No external binary is involved.
| Type | Scopes | Notes |
|---|---|---|
listDirectory |
project, external |
|
readFile, readFileBytes, readFileLines |
all | |
findInProject |
none | Takes includeHidden and includeIgnored instead of a scope. |
createFile, editFile, deleteFile, chmodFile |
project, project-dot, project-ignore |
|
replaceInProject |
project |
Scope values:
-
project: ordinary project files, relative to the project root. Dotfiles and gitignored files are rejected. -
project-dot: dotfiles within the project directory structure. -
project-ignore: gitignored files. Requires a git repository. -
external: absolute paths outside the project.
Permissions
Tools are classified by what they read, what they write and, for exec tools, how trustworthy their inputs are.
Read categories:
none: reads nothing that needs approval.project,project-dot,project-ignore: the corresponding class of project files.git: git state, e.g. diffs, history, status and search.github: GitHub operations, viagh.any: everything.
Write categories:
none: writes nothing that needs approval.artifacts: build outputs, caches and similar. The default destination for compiler and package-manager tools.project,project-dot,project-ignore: the corresponding class of project files.git: git state. No shipped tool declares a git write and no shipped loop pre-approves one, so git writes always prompt under default configuration. The category exists so user-defined tools can declare their writes honestly instead of masquerading as an auto-approved one.github: GitHub state.any: everything.
exec: "untrusted" marks tools
whose inputs can drive the execution of code
that is not already part of the project
(npm, npx, pnpm, yarn, mise, rustup, conan).
Untrusted tools always prompt,
regardless of their write category.
How gating combines:
- A tool runs without prompting
when its read category is covered
by the loop's
approveReadslist and its write category (if any) byapproveWrites.anycovers everything. - Some session approvals are available: project writes can be auto-approved for the remainder of a session.
- Gating fails closed: an undeterminable category prompts.
For builtin handlers,
the read and write categories are derived
from the handler type and scope.
A declared reads or writes that disagrees
is rejected at load.
Exec tools just declare their categories;
declare them honestly.
MCP tools live in their own permission space. See the MCP reference.
Availability
At startup, Opair probes $PATH
for every binary referenced by the tools on disk.
Tools whose binary is missing
are never exposed to the agent.
Install the toolchain, then start a new session.
A tool that fails at runtime
because its binary has vanished
degrades for the rest of the session.
Validation rules
- An exec handler must set
binand must not setscopeorincludeHidden. - A builtin handler must not set
bin,subcommand,env,allowArgs,denyArgsorsummary. findInProjectmust not setscopeand must not setincludeHiddenandincludeIgnoredtogether.- Every other builtin must set
scope. - A declared
readsorwritesthat contradicts what the handler actually does is rejected. requiresBinmust matchhandler.binif both are set.