Opair

The coding harness that eschews autonomy

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

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:

Permissions

Tools are classified by what they read, what they write and, for exec tools, how trustworthy their inputs are.

Read categories:

Write categories:

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:

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