ollie/doc/WRITING_TOOLS.md

4.9 KiB

Writing Ollie Tools

Tools are executables that extend ollie's capabilities. They are loaded lazily via tool_load and become first-class callable functions with JSON schemas.

Tool Discovery

Tools live in $OLLIE_TOOLS_PATH (default: ~/.config/ollie/tools/). Any file with a corresponding .meta sidecar is a tool. The tool name is the filename (without .meta).

Tool Metadata

Each tool has a .meta JSON sidecar file alongside the executable. This is the sole source of metadata — the registry reads only .meta files.

{
  "description": "Does something useful.",
  "prompt": "## my_tool\n\nDoes something useful.\n\n**Args**: `path` (required), `verbose` (optional)\n\n```\nmy_tool(path=\"/foo\", verbose=true)\n```",
  "args": {
    "type": "object",
    "required": ["path"],
    "properties": {
      "path": {"type": "string", "description": "File path"},
      "verbose": {"type": "boolean", "description": "Enable verbose output"}
    }
  },
  "tier": "cold",
  "readOnly": true
}
Field Type Purpose
description string One-liner shown in the tool listing (system prompt)
prompt string Full documentation injected when the tool is loaded
args JSON Schema Input schema exposed to the model
tier "hot"|"warm"|"cold" Context retention tier (default: hot)
readOnly bool Safe for parallel execution with other read tools

Input Protocol

Tools receive their arguments as a JSON object on stdin. The dispatcher pipes the model's tool call arguments directly — no positional argument translation.

Python tools

#!/usr/bin/env python3
import os, sys
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
from _lib.args import parse_args

args = parse_args()
path = args.require("path")       # exits with error if missing
verbose = args.get_bool("verbose") # False if missing
count = args.get_int("count", 10)  # default 10
items = args.get_list("items")     # [] if missing

The _lib/args.py module provides:

  • args.require(key) — get required string arg (exits on missing)
  • args.get(key, default) — get optional string arg
  • args.get_bool(key, default) — get boolean arg
  • args.get_int(key, default) — get integer arg
  • args.get_list(key) — get list arg
  • args.raw() — get the raw parsed dict

Bash tools

#!/usr/bin/env bash
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"

path=$(arg_require "path")         # exits with error if missing
verbose=$(arg_bool "verbose")      # "false" if missing
mode=$(arg_get "mode" "default")   # "default" if missing
raw=$(arg_json "config")           # raw JSON value

The _lib/args.sh module provides:

  • arg_require key — get required arg (exits on missing)
  • arg_get key [default] — get optional arg
  • arg_bool key [default] — get boolean ("true"/"false")
  • arg_json key — get raw JSON value
  • arg_raw — get the full stdin JSON

Go tools

Compiled Go binaries work the same way — read JSON from stdin, write result to stdout. No _lib dependency needed; use encoding/json directly.

Sandboxing

Every tool runs inside a Landlock sandbox by default. Elevation is the escape hatch — when the agent passes "elevated": true, the tool runs unsandboxed (but still as the current user). This means by default:

  • Filesystem access is restricted by the sandbox profile (default: ~/.config/ollie/sandbox/default.yaml)
  • Network access may be restricted depending on the profile
  • System calls are filtered by Landlock rules

Elevation

If your tool needs to run outside the sandbox (e.g., access paths the sandbox restricts), it must request elevation explicitly. The agent passes "elevated": true in the tool call, which the dispatcher extracts before piping the remaining args to the tool.

From the tool's perspective, elevation is transparent — it simply runs outside the Landlock sandbox (but still as the current user, not root). The tool script itself does not need to handle elevation logic.

Output Protocol

Tools communicate results through stdout/stderr. The output is returned as a text content block.

Success: print the result to stdout and exit 0.

Error: print error information to stdout/stderr and exit non-zero. The dispatcher wraps the error.

Structured output: if a tool returns {"content": [...]} JSON, it is passed through as-is.

Environment Variables

Tools receive these environment variables:

Variable Purpose
OLLIE_TOOLS_PATH Path to the tools directory
OLLIE_ELEVATE_SOCKET Unix socket for elevation broker
PWD / working directory Session's current working directory

Examples

See the tools in data/tools/ for real examples:

  • file_read — reads files with line numbers
  • file_edit — fuzzy text replacement
  • file_grep — ripgrep wrapper
  • gui_* — KDE desktop automation
  • lsp_* — language server protocol clients