## 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`: ```bash # 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`: ```json { "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`): ```json { "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`). ### 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. ```json { "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/writing-tools.md`](doc/writing-tools.md) for the full variant specification.