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.