ollie/doc/architecture-tools.md

213 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Tools Architecture and Authoring
A tool is declared by a `.meta` file. It may also have an accompanying executable, but neither form is required in every case:
- **Metadata plus binary/script:** place `<name>.meta` beside an executable named `<name>`.
- **Metadata only:** set `cmd` in `<name>.meta` to a shell command. The registry executes that command directly.
Both approaches are valid. The `.meta` file is always the source of discovery, model-facing documentation, and the JSON argument schema.
The registry reads the metadata, presents the tool to the model, and executes the resolved command when called. No integration with the Ollie source tree is required. Put the `.meta` file in `$OLLIE_TOOLS_PATH`; the tool is available on the next discovery or session start.
## Quick start
### Metadata plus an executable
```text
~/.config/ollie/tools/
├── my_tool # executable in any language
└── my_tool.meta # JSON metadata
```
### Metadata-only tool
```json
{
"description": "Extract a value from JSON.",
"prompt": "## json_value\n\nRead `.path` from JSON input.",
"cmd": "input=$(cat); echo \"$input\" | jq -r '.path'",
"args": {
"type": "object",
"required": ["path"],
"properties": {"path": {"type": "string"}}
}
}
```
A metadata-only tool needs no script or binary. Its `cmd` is run through the shell with the tool-call JSON on standard input.
## The `.meta` sidecar
The `.meta` file is the sole source of discovery and schema. The registry never inspects an accompanying executable to infer its interface.
```json
{
"description": "One-liner shown in tool listings.",
"prompt": "## my_tool\n\nFull documentation shown when loaded.",
"args": {
"type": "object",
"required": ["path"],
"properties": {
"path": {"type": "string", "description": "File path"}
}
},
"tier": "hot",
"cmd": "my_tool"
}
```
| Field | Type | Purpose |
|---|---|---|
| `description` | string | One-liner shown in the tool listing. |
| `prompt` | string | Documentation injected when the tool is loaded. |
| `keywords` | array of strings | Additional user-language phrases used only for semantic discovery. Include synonyms, task verbs, file types, domains, and representative requests. |
| `args` | JSON Schema | Input schema exposed to the model. |
| `tier` | `hot`, `warm`, or `cold` | Context retention tier. |
| `cmd` | string | Shell command or executable path/name. |
| `variants` | array | Conditional definitions for heterogeneous hosts. |
| `scope` | string | Tool scope metadata. |
| `outputFormat` | string | Output format metadata. |
| `resetsCounter` | bool | Marks tools that reset the relevant counter. |
## Tool and skill discovery
Tool discovery remains metadata-driven: `toolsrv` scans `.meta` files and manages
which tools are loaded for an agent. Ollie adds a separate semantic ranking
layer before prompt assembly. The ranking text combines `description`, `prompt`,
and optional `keywords`. Keywords are discovery-only vocabulary: they should
contain the words users use to request the operation, including synonyms,
workflow phrases, file types, and domain terms. They are not shown as a second
model-facing schema and do not change execution. See [`architecture-embedding.md`](architecture-embedding.md)
for the embedding model, index lifecycle, matching thresholds, and failure
behavior.
## Command resolution
When the registry resolves a tool named `name`, it checks:
1. `$OLLIE_TOOLS_PATH/name` — a co-located executable or script.
2. The resolved `.meta` `cmd` field — an absolute path, bare executable name, or shell command.
3. `name` through `$PATH`.
A co-located executable takes precedence over `cmd`. Metadata-only tools use the `cmd` value directly through the shell. This supports scripts, compiled binaries, wrappers, system commands, and pipelines.
## Variants
The first variant whose conditions all match determines the tool’s schema, documentation, and command. If no variant matches, the tool is hidden.
| Key | Semantics | Example |
|---|---|---|
| `binary` | Binary exists in `$PATH` or at an absolute path. | `journalctl` |
| `file` | File or directory exists. | `/var/log/syslog` |
| `os` | Matches `runtime.GOOS`. | `linux` |
| `arch` | Matches `runtime.GOARCH`. | `amd64` |
| `env` | Environment variable is non-empty. | `DISPLAY` |
| `nenv` | Environment variable is empty or unset. | `SSH_CONNECTION` |
All keys in one `match` object use AND logic. An array value uses OR logic. Variant fields override top-level fields; unset fields inherit. If a remote toolsrv receives the metadata, it evaluates variants against the remote host.
```json
{
"description": "Read system logs.",
"variants": [
{"match": {"binary": "journalctl"}, "cmd": "system_logs_journald"},
{"match": {"file": "/var/log/syslog"}, "cmd": "system_logs_syslog"}
]
}
```
## Input and output contract
Both tool forms use the same contract:
- Input is one JSON object on standard input.
- Successful output is written to standard output and exits with status 0.
- Errors may be written to standard output or standard error and exit non-zero.
- Structured output may be returned as `{"content": [...]}` JSON.
The dispatcher does not require flags, argv conventions, or language-specific integration.
### Executable example
```python
#!/usr/bin/env python3
import json
import sys
args = json.load(sys.stdin)
print(f"result: {args['path']}")
```
### Metadata-only example
```json
{
"description": "Search with ripgrep.",
"cmd": "input=$(cat); p=$(echo \"$input\" | jq -r '.pattern'); rg \"$p\"",
"args": {
"type": "object",
"required": ["pattern"],
"properties": {"pattern": {"type": "string"}}
}
}
```
For subcommand-style tools, parse the JSON with `jq` and construct the command explicitly. Avoid unsafe `eval` when arguments can be passed as arrays or quoted shell parameters.
## Sandboxing and bypass
Toolsrv owns sandbox enforcement and the bypass approval path. See [`architecture-toolsrv.md`](architecture-toolsrv.md#sandbox). Tool metadata declares the command interface; it does not define the complete security policy.
## Environment
Tools may receive:
| Variable | Purpose |
|---|---|
| `OLLIE_TOOLS_PATH` | Tool metadata and executable directory. |
| `OLLIE_BYPASS_SOCKET` | Bypass broker socket. |
| `PWD` | Session working directory. |
## Installation and testing
Copy a `.meta` file and, when using the binary approach, its executable to the tools directory:
```sh
cp my_tool.meta ~/.config/ollie/tools/
cp my_tool ~/.config/ollie/tools/
chmod +x ~/.config/ollie/tools/my_tool
```
A metadata-only tool needs only the first command. Test its command contract with JSON on standard input:
```sh
echo '{"pattern":"TODO"}' | bash -c 'YOUR_CMD'
```
Agent profiles load tools at startup with `autoLoad`. Each profile specifies
the tools it needs — there is no lazy loading or automatic tool discovery at
call time:
```json
{
"autoLoad": ["shell", "file_read", "file_edit", "file_grep", "file_glob"]
}
```
To add a tool to a running agent, use the `ctl` file:
```text
client_9p(op="rdwr", path="session/$OLLIE_SESSION_ID/agent/$OLLIE_UNAME/ctl", data="tool_load <name>")
```
After loading, the agent refreshes its model-facing schemas when the toolsrv
registry revision changes. See [`architecture-toolsrv.md`](architecture-toolsrv.md)
for discovery, loading, registry, and execution architecture.
## Repository examples
The repository includes executable and metadata-backed tools under `data/tools/`, including filesystem, GUI, LSP, and memory tools. Compiled tools are valid alongside scripts and metadata-only definitions.
See [`architecture.md`](architecture.md#deliberately-unimplemented-agent-patterns) for the design rationale, including why Ollie does not implement native MCP, embedded tool frameworks, workflow engines, parallel frontend control planes, or competing memory stores.