ollie/doc/resources/tool-registry.md

3.8 KiB

Tool registry: zero built-in tools

No tools are compiled into the Go binary. Every tool — including shell and reasoning_think — is an external executable loaded dynamically through the 9P filesystem. Tool definitions, schemas, and execution logic live entirely in the filesystem.

Loading tools

Tools are loaded by writing their name to session/{sname}/agent/{aname}/tools:

# List currently loaded tools
ollie-9p read session/{sname}/agent/{aname}/tools

# Load a tool — becomes a native callable function
echo file_glob | ollie-9p write session/{sname}/agent/{aname}/tools

# Global catalog: all discoverable tools on disk
ollie-9p read tools

The tool_load tool is itself a bash script that does exactly this — it wraps the 9P write in a callable function so the LLM can load tools during a turn.

Agent autoLoad configuration

Agent configs declare which tools to load at startup via autoLoad:

{
  "autoLoad": ["file_read", "file_edit", "file_glob", "file_grep", "file_write", "tool_load"]
}

Each agent profile (default, copilot, explorer, librarian, navigator, taskmanager, theo) has its own autoLoad list. The allowTools field provides a secondary security gate.

Declarative registration

Tools are executables in ~/.config/ollie/tools/. Each one has a .meta sidecar file declaring its JSON schema, documentation, concurrency class, tier, and privilege requirements. The registry reads .meta files — it never inspects the executable itself.

This is declarative tool registration: write a .meta file, drop an executable in $OLLIE_TOOLS_PATH, and the tool is live. No server restart, no config file, no code change.

The .meta file (file_glob.meta):

{
  "description": "Find files by glob pattern, sorted by mtime (newest first).",
  "args": {"type":"object","properties":{"pattern":{"type":"string"}},"required":["pattern"]},
  "tier": "cold",
  "readOnly": true
}

Everything is sandboxed by default via Landlock. Tools that need to escape request bypass explicitly ("bypass": true).

Privileged tools: sudo

Tools that need root privileges declare "sudo": true in their .meta. The dispatch chain becomes a two-gate sequence: bypass approval (escape sandbox) → credential prompt → sudo -S execution. The tool itself has no knowledge of sudo — the privilege wrapping is entirely in the dispatch layer.

{
  "description": "Read system logs (dmesg).",
  "sudo": true,
  "cmd": "system_logs_dmesg",
  "args": {"type":"object","properties":{"lines":{"type":"string"}}},
  "readOnly": true
}

Credentials are prompted via kdialog/zenity on desktop, or forwarded over SSH for remote execution. See doc/resources/writing-tools.md for details.

Host-conditional variants

A single .meta file can describe a tool that works differently on different hosts. Variants are gated by match conditions (binary, file, os, arch, env, nenv). The first matching variant determines the tool's schema, documentation, and executable. If no variant matches, the tool is hidden from the registry — it doesn't exist on this host.

{
  "description": "Read system logs.",
  "variants": [
    {
      "match": {"binary": "journalctl"},
      "cmd": "system_logs_journald",
      "args": {"type":"object","properties":{"unit":{"type":"string"},"since":{"type":"string"}}}
    },
    {
      "match": {"file": "/var/log/syslog"},
      "cmd": "system_logs_syslog",
      "args": {"type":"object","properties":{"lines":{"type":"string"}}}
    }
  ]
}

On a systemd host the model sees unit filtering and time ranges. On Alpine it sees tail + grep. On a host with neither, the tool doesn't appear. One .meta deploys everywhere. See doc/resources/writing-tools.md for the full variant specification.