From 670fa717c7475bc3297a2b72ab51b54b0705c821 Mon Sep 17 00:00:00 2001 From: Ollie Agent Date: Sun, 16 Aug 2026 19:41:19 +0200 Subject: [PATCH] Consolidate remote architecture documentation --- doc/architecture-remote.md | 136 +++++++++++++ doc/architecture-toolsrv.md | 2 + doc/remote-execution.md | 391 ------------------------------------ doc/tool-registry.md | 116 ----------- 4 files changed, 138 insertions(+), 507 deletions(-) create mode 100644 doc/architecture-remote.md delete mode 100644 doc/remote-execution.md delete mode 100644 doc/tool-registry.md diff --git a/doc/architecture-remote.md b/doc/architecture-remote.md new file mode 100644 index 0000000..554e068 --- /dev/null +++ b/doc/architecture-remote.md @@ -0,0 +1,136 @@ +# Remote Toolsrv Architecture + +Ollie keeps the agent loop local and moves tool execution to a remote host when a session has a `remote` target. The remote boundary is the same authenticated 9P `toolsrv` protocol used for local execution. There is no separate `ollie-remote` binary, JSON-RPC protocol, or in-process `Runner` adapter. + +## Architecture + +```mermaid +flowchart LR + A[olliesrv agent loop] --> C[ToolsrvConn] + C -->|local Unix socket or SSH-forwarded Unix socket| T[toolsrv] + T --> R[remote registry] + T --> E[remote tool execution] + E --> S[remote sandbox] + E --> W[remote working directory] +``` + +The agent uses `ToolsrvConn` for both local and remote sessions. It lists tools, loads and unloads tools, calls tools, reads background-process output, and handles cancellation through the same 9P client methods. Only the process and socket setup differ. + +## Session configuration + +A session’s `remote` value is an SSH target. It may be supplied during session creation or as a session configuration value: + +```text +remote=user@devbox +``` + +The target may include a port in `host:port` form. `session.SetupToolServer` selects `SpawnRemote` when the target is non-empty. The session stores the target and reuses the resulting `ProcessKeeper` and connection when agents are created or reloaded. + +Remote execution therefore moves: + +- the toolsrv process; +- tool metadata and installed tool commands; +- the working directory used by tool calls; and +- sandbox enforcement. + +The following remain local: + +- the agent loop and conversation history; +- LLM backend calls and credentials; +- prompt assembly and agent configuration; +- session persistence and the local 9P control namespace; and +- GUI or other local interaction layers. + +## Startup and deployment + +`toolclient.SpawnRemote` performs the following sequence: + +1. Validate that the local Ollie configuration directory exists. +2. Create a local forwarded socket path and a temporary remote socket path. +3. Start `ssh` with local Unix-socket forwarding (`-L`). +4. Stream a gzipped tar archive of the local Ollie configuration directory to the remote shell. +5. Run a remote bootstrap script through `bash -s`. +6. Extract the archive into `${XDG_CACHE_HOME:-$HOME/.cache}/ollie`. +7. Set remote `XDG_CONFIG_HOME` and prepend the cached `bin` directory to `PATH`. +8. Start the cached `toolsrv` with the requested working directory, remote socket, session ID, and optional `--yolo` flag. +9. Wait for the toolsrv listening message and the local forwarded socket. +10. Return a managed `Process` whose socket path is local but whose server runs remotely. + +The transferred configuration supplies the remote tools directory and metadata. Remote discovery and metadata variants therefore describe the remote host’s available commands, files, operating system, architecture, and environment. + +The remote service is cached under `$XDG_CACHE_HOME/ollie` and is not installed system-wide. The bootstrap replaces the cache contents from the transferred archive for the session startup. + +## Authentication and transport + +The client connects to the forwarded local socket with `DialToolsrv`. The toolsrv server still performs the normal 9P `Tauth` exchange. The connection receives a secret and session token, and the client sends the session and agent identities used to scope registry state and process requests. + +SSH protects the forwarded socket transport. 9P authentication remains enabled on the toolsrv protocol. The remote path does not introduce a second application protocol. + +## Tool calls and results + +A remote tool call follows the normal toolsrv path: + +```text +agent + └─ ToolsrvConn.CallTool(ctx, name, args) + └─ authenticated 9P proc/new + └─ remote toolsrv registry lookup + └─ remote metadata command resolution + └─ remote sandboxed process + └─ structured ToolResult +``` + +Tool metadata is loaded from the remote configuration. The agent receives the remote catalog and renders the same tool definitions and documentation into its runtime. + +Background calls use `proc/new.bg`. Output and exit state remain available through the remote toolsrv process namespace. The client reads them through the forwarded connection. + +## Cancellation and recovery + +Cancellation closes the active 9P request fid. The remote toolsrv cancels its request context, and `exec.CommandContext` terminates the remote child process. Closing the SSH-backed connection or cancelling the session context also terminates the managed SSH process and remote toolsrv process. + +`ProcessKeeper` owns the current process and can respawn it when a new connection is needed. For a remote session, respawning repeats the SSH bootstrap, configuration transfer, remote toolsrv startup, and socket forwarding sequence. Agent reloads dial a fresh connection through the keeper while preserving the session-level infrastructure. + +A dropped SSH connection does not silently replay a tool call. The failed connection or tool request returns an error; the keeper can establish a replacement toolsrv process for subsequent operations. + +## Environment and prompt context + +The agent prompt is assembled locally, but environment facts describing the tool host must come from the remote process setup. `SetupToolServer` uses the remote process information when constructing the environment section. The current implementation reports the remote platform as Linux and does not yet detect the remote Git state in `SpawnRemote`; callers should not treat those values as full host discovery. + +The working directory passed to `SpawnRemote` is sent to the remote toolsrv `--cwd` argument. Tool commands execute relative to that remote directory, not the local caller’s filesystem. + +## Sandbox + +Sandbox policy is enforced where the command runs. A remote tool call uses the remote toolsrv sandbox and the remote host’s filesystem, network, environment, and Landlock capabilities. The `--yolo` session option passes through to the remote toolsrv and disables enforcement for explicit development use. + +The local bypass broker is not a remote execution engine. Bypass requests and policy handling remain part of the toolsrv/olliesrv integration described in [`architecture-toolsrv.md`](architecture-toolsrv.md); the command itself executes on the remote host. + +## Comparison with local execution + +| Concern | Local session | Remote session | +|---|---|---| +| Agent loop | Local | Local | +| Toolsrv process | Local child process | Remote child process under SSH | +| Client socket | Local Unix socket | Local forwarded Unix socket | +| 9P protocol | Authenticated | Authenticated | +| Tool registry | Local toolsrv | Remote toolsrv | +| Tool commands | Local host | Remote host | +| Working directory | Local path | Remote path | +| Sandbox | Local toolsrv policy | Remote toolsrv policy | +| Prompt assembly | Local | Local | +| LLM calls | Local | Local | + +Remote execution is useful when the project, compilers, dependencies, or hardware live on another host while the user interface and model connection remain local. + +## Implementation map + +| Responsibility | Implementation | +|---|---| +| Session selection and lifecycle | `cmd/olliesrv/internal/session/setup.go` | +| Local and remote process spawning | `cmd/olliesrv/internal/toolclient/spawn.go` | +| SSH bootstrap and archive transfer | `cmd/olliesrv/internal/toolclient/spawn.go` | +| Authenticated 9P client | `cmd/olliesrv/internal/toolclient/toolsrv.go` | +| Remote toolsrv service | `cmd/toolsrv/` | +| Registry and metadata | `cmd/toolsrv/internal/registry/`, `toolsrv/metadata/` | +| Execution and sandbox | `cmd/toolsrv/internal/exec/`, `cmd/toolsrv/internal/sandbox/` | + +The complete toolsrv service architecture is documented in [`architecture-toolsrv.md`](architecture-toolsrv.md). The agent-side integration is documented in [`architecture-core.md`](architecture-core.md). diff --git a/doc/architecture-toolsrv.md b/doc/architecture-toolsrv.md index e025fd3..6d99922 100644 --- a/doc/architecture-toolsrv.md +++ b/doc/architecture-toolsrv.md @@ -2,6 +2,8 @@ `toolsrv` is Ollie's separate tool-execution service. It exposes a 9P filesystem over a Unix socket. `olliesrv` connects through `ToolsrvConn`; remote deployments use the same client protocol over an SSH-forwarded Unix socket. +This document is authoritative for the registry, metadata, protocol, execution, and service architecture. + The service owns tool discovery, metadata resolution, per-agent loaded-tool state, process execution, output streaming, sandbox enforcement, bypass requests, and process lifecycle. ## Position in the runtime diff --git a/doc/remote-execution.md b/doc/remote-execution.md deleted file mode 100644 index 83dcffc..0000000 --- a/doc/remote-execution.md +++ /dev/null @@ -1,391 +0,0 @@ -# Remote Execution (Split-Brain) - -Local agent loop + remote tool execution over SSH. - -## Motivation - -The default ollie deployment runs everything on one machine: the agent loop (LLM calls, prompt assembly, event bus), tool execution (sandbox, bash, file I/O), and the interaction layer (D-Bus signals, GUI). This works for local development but breaks down when: - -- The codebase lives on a remote server (SSH host, cloud VM, container) -- The build environment requires specific hardware or OS -- You want local GUI responsiveness while operating on remote filesystems -- Network latency makes full-remote agents sluggish (per-token streaming over WAN) - -The split-brain model keeps the **brain local** (agent loop, LLM, D-Bus, GUI) and sends the **hands remote** (tool execution, sandbox, file access). This is the inverse of 9P remote mount: instead of bringing remote state to your local filesystem, you send local decisions to a remote executor. - -## Architecture - -```mermaid -flowchart TB - subgraph Local["Local Machine"] - SRV["olliesrv"] - AG["agent.Agent\n- LLM streaming\n- prompt assembly\n- event bus\n- compaction"] - DBUS["D-Bus adapter\n- SessionCreated\n- StateChanged\n- ChatUpdated"] - GUI["KDE GUI"] - end - - subgraph Remote["Remote Host (via SSH)"] - REMOTE_BIN["ollie-remote"] - EXEC["toolsrv.Server\n- landrun sandbox\n- shell execution\n- tool definitions\n- file I/O"] - CWD["Working directory:\n$HOME/project/"] - SANDCFG["Sandbox config:\nremote host's landrun"] - end - - AG -- "SSH / JSON-RPC" --> REMOTE_BIN - REMOTE_BIN --> EXEC - EXEC --> CWD - EXEC --> SANDCFG - DBUS -.->|"D-Bus signals"| GUI -``` - -## Split Point - -The natural split is at `toolsrv.Runner`. Today, `agent.Agent` calls `toolsrv.Server` in-process. The remote mode replaces this with a `RemoteServer` that forwards calls over an SSH-backed RPC channel: - -```go -// Local side -type RemoteServer struct { - conn *rpc.Conn // JSON-RPC over SSH stdin/stdout -} - -func (r *RemoteServer) CallTool(ctx context.Context, tool string, args json.RawMessage) (json.RawMessage, error) { - return r.conn.Call(ctx, "execute", tool, args) -} -``` - -The agent loop doesn't know or care whether execution is local or remote. The `NewToolServer` factory in session creation just returns a `RemoteServer` instead of a local `toolsrv.Server` when the session is configured for remote execution. - -## SSH Bootstrap - -Inspired by goq's approach (`toolserv/ssh.go`): - -1. Open SSH connection to target host -2. Send a bootstrap shell script over stdin -3. Bootstrap checks for cached `ollie-remote` binary (by SHA-256 hash) -4. If not cached: receive binary over stdin (gzipped + base64), install to `~/.cache/ollie/bin/` -5. Launch `ollie-remote` with the working directory as argument -6. Signal readiness over stderr -7. Switch to JSON-RPC over stdin/stdout - -```bash -# Bootstrap (simplified) -HASH="@@HASH@@" -BIN="$HOME/.cache/ollie/bin/ollie-remote-$HASH" -if [ -x "$BIN" ]; then - echo "GOQ_LOADER_START {\"need_download\":false}" >&2 -else - echo "GOQ_LOADER_START {\"need_download\":true}" >&2 - # ... receive and install binary ... - echo "GOQ_LOADER_READY" >&2 -fi -exec "$BIN" serve --cwd "@@CWD@@" -``` - -## ollie-remote Binary - -A minimal binary containing: - -- `toolsrv.Server` (the same one used locally) -- Sandbox enforcement (landrun) -- Tool script discovery from a bundled or remote-local `OLLIE_TOOLS_PATH` -- JSON-RPC server over stdin/stdout - -It does NOT contain: -- Agent loop -- LLM backends -- D-Bus anything -- Session management -- Prompt resolution - -```go -// cmd/ollie-remote/main.go -func main() { - cwd := flag.Arg(0) - srv := toolsrv.NewServer(cwd, toolsPath, sandboxConfig) - rpc.ServeStdio(srv) -} -``` - -## RPC Protocol - -JSON-RPC 2.0 over stdin/stdout (newline-delimited): - -```json -// Request (local → remote) -{"jsonrpc":"2.0","id":1,"method":"execute","params":{ - "steps":[{"code":"go build ./..."}], - "timeout":60, - "sandbox":"default" -}} - -// Response (remote → local) -{"jsonrpc":"2.0","id":1,"result":{ - "output":"...", - "exit_code":0 -}} - -// Streaming output (remote → local, notification) -{"jsonrpc":"2.0","method":"output","params":{ - "step":0,"data":"compiling..." -}} -``` - -Streaming output notifications allow the local agent to see partial results in real-time (for the event bus / ChatUpdated signals). - -## Session Configuration - -A session targeting a remote host: - -```bash -# Via 9P -printf 'cwd=%s\nremote=devbox\n' "$PWD" > $OLLIE/session/new - -# Via D-Bus -dbus-send ... org.ollie.SessionManager.CreateSession \ - string:"$HOME/project" string:"" string:"" string:"default" \ - string:"" string:"devbox" # remote host -``` - -Or as a session config key: -``` -remote=user@devbox.internal -``` - -The `remote` field is an SSH target. When set, the session manager: -1. Bootstraps `ollie-remote` on the target -2. Creates a `RemoteServer` connected over the SSH channel -3. Passes it to `agent.Agent` as the tool runner - -## Tool Definitions - -Two strategies for making tool definitions available remotely: - -1. **Embedded**: `ollie-remote` bundles the standard tools (file_read, file_grep, etc.) at compile time. Custom tools would need to be synced. - -2. **Sync-on-connect**: During bootstrap, rsync `OLLIE_TOOLS_PATH` to the remote `~/.cache/ollie/tools/`. This handles custom tools transparently. - -3. **Hybrid**: Standard tools embedded, custom tools synced on first use (detected by hash mismatch). - -Option 2 is simplest and matches goq's philosophy of "the remote is a mirror of local capabilities." - -## What Stays Local - -| Component | Location | Why | -|---|---|---| -| Agent loop | Local | Latency: LLM streaming is per-token | -| LLM backends | Local | API keys stay local, streaming stays low-latency | -| D-Bus signals | Local | GUI responsiveness | -| Memory | Local | Personal knowledge, not project-specific | -| Skills | Local | Read during prompt assembly, not execution | -| Prompts | Local | Assembled before the loop starts | -| Session persistence | Local | State management is a local concern | - -## What Goes Remote - -| Component | Location | Why | -|---|---|---| -| shell | Remote | Needs remote filesystem, build tools | -| Tool definitions | Remote | File I/O targets remote tree | -| Sandbox | Remote | Landrun enforces on the execution host | -| Working directory | Remote | The project lives there | - -## Comparison with 9P Remote Mount - -| | 9P Mount | Split-Brain | -|---|---|---| -| Agent runs on | Remote | Local | -| Tools execute on | Remote | Remote | -| GUI connects to | Remote (via 9P) | Local (D-Bus) | -| LLM calls from | Remote | Local | -| Latency profile | All remote | Only tool calls remote | -| Network requirement | Persistent 9P mount | SSH session per tool call batch | -| API keys | On remote host | On local machine | -| Use case | Observe/control remote agents | Develop on remote, interact locally | - -They complement each other. You might use split-brain for active development (local GUI, remote execution) and 9P mount for monitoring a fleet of headless agents. - -## Implementation Status - -All five phases are implemented: - -### Phase 1: ollie-remote binary ✓ -- `cmd/ollie-remote/` with `main.go` -- Wraps `toolsrv.Server` with JSON-RPC stdio interface -- Embeds `sandbox-remote.yaml` and tool definitions via `go:embed` -- RPC methods: `shell`, `list_tools`, `host_info`, `ping` - -### Phase 2: SSH bootstrap ✓ -- `toolsrv/bootstrap.sh` embedded template -- SHA-256 hash-based caching on the remote -- Binary transfer: gzip + base64 over stdin -- `Dial()` handles full lifecycle: bootstrap → transfer → exec → JSON-RPC - -### Phase 3: RemoteServer adapter ✓ -- `toolsrv/remote.go` implements `toolsrv.Runner` -- `ListTools()` calls remote `list_tools` RPC -- `CallTool()` passes tool name as RPC method (routes shell) -- `HostInfo` fetched after connect (platform, arch, is_git_repo) - -### Phase 4: Session integration ✓ -- `remote=user@host:port` config key in session creation -- Eager dial during session creation (gets HostInfo for prompts) -- `PromptEnvExtra` stored on agent for /agent reloads -- FUSE mount started directly for remote sessions (prompt resolution) - -### Phase 5: Tool definitions ✓ -- Embedded in ollie-remote binary at compile time -- `justfile` copies from `tools/` submodule before building -- Extracted to tmpdir on startup, cleaned up on exit -- `file_grep`/`file_glob` fall back to GNU grep/find when rg unavailable -- Hot-reload: detect local tool changes, re-sync - -## Sandbox on Remote - -The remote sandbox is simpler than local — `ollie-remote` only does execution, not session management, prompts, or D-Bus. - -### Configuration layering - -1. **Embedded default** — ships with `ollie-remote`, covers 90% of Linux hosts -2. **`.ollie-sandbox.yaml` in project root** — per-project overrides, version-controlled -3. **Auto-detection** — bootstrap detects NixOS, adjusts paths accordingly - -Convention over configuration: the common case requires zero setup. - -### Embedded default config - -```yaml -filesystem: - ro: - - "/etc" - - "/proc" - - "/sys" - - "/dev" - - "{HOME}/.ssh/known_hosts" - - "{HOME}/.gitconfig" - - "{HOME}/.netrc" - rox: - - "/usr" - - "/lib" - - "/lib64" - - "/bin" - - "/sbin" - - "{HOME}/go/bin" - - "{HOME}/.cargo/bin" - - "{HOME}/.local/bin" - - "{HOME}/.nvm" - - "{HOME}/.pyenv" - - "{OLLIE_TOOLS_PATH}" - rwx: - - "{CWD}" - - "{TMPDIR}" - - "{HOME}/.cache" - - "{HOME}/go" - - "{HOME}/.cargo" - - "{HOME}/.npm" - - "{HOME}/.cache/go-build" -env: - - PATH - - HOME - - TMPDIR - - GOPATH - - GOBIN - - SSH_AUTH_SOCK -network: - unrestricted: true -general: - best_effort: true -``` - -### Per-project override (`.ollie-sandbox.yaml`) - -Dropped in the project root, merged on top of the default: - -```yaml -# .ollie-sandbox.yaml — this project needs Docker -filesystem: - rw: - - "/var/run/docker.sock" - - "{HOME}/.docker" -env: - - DOCKER_HOST -``` - -### Why it's simpler than local - -| Local sandbox needs | Remote sandbox needs | -|---|---| -| OLLIE_* paths (10+) | Just OLLIE_TOOLS_PATH | -| D-Bus socket | No | -| Bypass socket | No | -| 9P mount paths | No | -| Session/transcript dirs | No | -| Editor integration | No | - -The remote config is ~20 lines vs ~170 locally. - -### Kernel compatibility - -- **Linux 5.13+**: Full Landlock enforcement -- **Older kernels**: `best_effort: true` degrades gracefully (unenforced rules skipped) -- **No Landlock at all**: `--yolo` flag skips sandboxing (explicit opt-in, logged as warning) - -`ollie-remote` ships `landrun` alongside itself in `~/.cache/ollie/bin/`. No system-wide install required. - -## Open Questions - -- **Bypass on remote**: `bypass: true` escapes the sandbox. On remote, this means escaping the remote sandbox. The bypass socket doesn't exist remotely. Options: skip bypass, or run a remote bypass adapter. -- **Detached processes**: Currently tracked locally. Remote detached processes need their own lifecycle. -- **Multiple remotes per session**: Useful? Or one remote per session is sufficient? -- **Fallback**: If SSH drops mid-execution, retry? Fail the tool call? The agent can handle tool errors gracefully already. -- **Session restore**: `remote=` is not persisted in PersistedSession. Restored sessions always come back as local. -- **Eager dial**: Session creation blocks on SSH connect. Should be async with lazy tool list. -- **Streaming output**: ✓ Resolved. See Streaming section below. - -## Lessons Learned - -1. **Prompt resolution is always local.** The prime script runs on the local machine. It must be a pure template renderer — no local detection of platform/git/cwd. -2. **PRIME_* env vars are the contract.** Callers populate PRIME_CWD, PRIME_PLATFORM, PRIME_IS_GIT_REPO and store them in `PromptEnvExtra` for /agent reloads. -3. **Sandbox config must overwrite on deploy.** Skip-if-exists means binary updates don't propagate config changes. Always write. -4. **Tool scripts must be self-contained.** They need fallbacks when rg/ripgrep isn't available (GNU grep, find). -5. **Embed over sync.** Embedding tools in the binary (go:embed) is simpler than rsync — single artifact, atomic deploy, no state management. -6. **Build from source, not installed copies.** The justfile must copy from the repo's tools/ submodule, not ~/.config/ollie/tools/. -7. **No new event roles.** Streaming uses the same `"tool"` role as final results. Empty content signals stream start; frontends need no changes. - -## Streaming - -Tool output streams in real-time over the SSH channel, visible to the user as it arrives. - -### Protocol - -Remote → local streaming uses JSON-RPC notifications (no `id` field): - -```json -{"jsonrpc":"2.0","result":{"data":"compiling...\n"}} -``` - -`remote.Server.CallTool` reads these in a loop, calling `toolsrv.StreamOutput(ctx, data)` for each, until the actual response (with matching `id`) arrives. - -### Agent Loop Integration - -The agent loop wraps the execution context with a streaming callback: - -```go -streamCtx := toolsrv.WithOutputStream(ctx, func(data string) { - emit(cfg, Event{Role: "tool", Name: tc.Name, Content: data}) -}) -``` - -On first chunk, an empty-content tool event is emitted to signal stream start. If streaming occurred, the final result emit is suppressed (the content was already delivered chunk-by-chunk). - -### Execute Server - -`limitedWriter` has a `stream func(string)` field, wired to `toolsrv.StreamFunc(ctx)`. Every `Write()` call flushes the chunk both to the output buffer (for the full result) and to the stream callback (for real-time display). - -### Chat Log - -`startEventLog` treats tool streaming like assistant streaming: -- Empty-content tool event → writes `[tool:name]\n` header, enters streaming mode -- Subsequent tool events → content appended directly (no repeated header) -- Non-tool event → closes the stream with a newline - -For non-streamed tools (fast results), the single tool event renders as before. \ No newline at end of file diff --git a/doc/tool-registry.md b/doc/tool-registry.md deleted file mode 100644 index 06718c7..0000000 --- a/doc/tool-registry.md +++ /dev/null @@ -1,116 +0,0 @@ -# Tool Registry - -`toolsrv` owns Ollie's tool registry. No tool registry is compiled into `olliesrv`. The registry is scoped per agent, so each agent can load and unload tools independently while sharing the session's `toolsrv` process. - -## Discovery - -The registry scans the configured tools directory for `*.meta` files: - -```text -$XDG_CONFIG_HOME/ollie/tools/ # default: ~/.config/ollie/tools/ -├── file_read.meta -├── file_read -└── custom_tool.meta -``` - -A valid metadata file is enough for a tool to appear in the catalog. The executable is resolved when the tool is invoked. Invalid metadata is skipped. A tool with variants is unavailable when no variant matches the host. - -The registry reads metadata, resolves the selected command, and passes tool-call arguments as JSON on standard input. - -## Agent operations - -```sh -# List tools currently loaded for an agent -ollie-9p read session/{session}/agent/{agent}/tools - -# Load a tool -echo tool_name | ollie-9p write session/{session}/agent/{agent}/tools - -# Unload a tool -echo tool_unload tool_name | ollie-9p write session/{session}/agent/{agent}/ctl - -# List every discoverable tool -echo tools_all | ollie-9p write session/{session}/agent/{agent}/ctl -``` - -`tool_load` and `tool_unload` are also callable agent operations. Loading updates the agent's tool preamble and increments its per-agent registry revision. `toolsrv.loaded` and `toolsrv.rev` expose the loaded set and revision through 9P. - -Agent profiles may specify startup loading with `autoLoad`. Discovery and loading are separate: a discovered tool is not callable until it is loaded. - -## Metadata - -A `.meta` file uses this structure: - -```json -{ - "description": "Find files by glob pattern.", - "prompt": "Full usage documentation injected when loaded.", - "args": { - "type": "object", - "required": ["pattern"], - "properties": {"pattern": {"type": "string"}} - }, - "tier": "cold", - "scope": "read", - "outputFormat": "text", - "cmd": "file_glob", - "resetsCounter": false -} -``` - -| Field | Meaning | -|---|---| -| `description` | Short catalog description. | -| `prompt` | Documentation added when the tool is loaded. | -| `args` | JSON Schema for tool arguments. Defaults to an empty object schema. | -| `tier` | Context retention tier. Defaults to `hot`. | -| `scope` | Tool scope metadata. | -| `outputFormat` | Output format metadata. | -| `cmd` | Command used to execute the tool. | -| `resetsCounter` | Marks tools that reset the relevant counter. | -| `variants` | Host-conditional metadata definitions. | - -`readOnly` and `bypass` are not current metadata fields. Sandbox and privilege behavior is configured by the execution service and its policies. - -## Command resolution - -For a tool named `name`, toolsrv resolves the command in this order: - -1. `$XDG_CONFIG_HOME/ollie/tools/name` if the local file exists. -2. The resolved `.meta` `cmd` field, returned as a shell command string. -3. `name` found through `$PATH`. - -A `.meta` file can describe a co-located script, a system executable, or a shell command. Metadata-only tools are valid when their `cmd` resolves to an executable command. - -## Variants - -The first variant whose conditions all match is merged over the top-level metadata. Variant values override non-empty top-level values. Supported match keys are: - -- `binary`: an absolute path or executable available through `$PATH`. -- `file`: an existing file or directory. -- `os`: the current `runtime.GOOS`. -- `arch`: the current `runtime.GOARCH`. -- `env`: a non-empty environment variable. -- `nenv`: an empty or unset environment variable. - -A condition value can be a string or an array; arrays use OR semantics. Multiple condition keys use AND semantics. If no variant matches, the tool is hidden. - -```json -{ - "description": "Read system logs.", - "variants": [ - {"match": {"binary": "journalctl"}, "cmd": "system_logs_journald", "scope": "read"}, - {"match": {"file": "/var/log/syslog"}, "cmd": "system_logs_syslog", "scope": "read"} - ] -} -``` - -Remote toolsrv deployments receive the metadata and resolve variants on the remote host. The catalog therefore reflects that host's capabilities. - -## Execution contract - -The selected command receives one JSON object on standard input. A successful tool writes its result to standard output and exits with status 0. A failed tool exits non-zero. Structured content may be returned as JSON when required by the tool's output format. - -Normal execution is sandboxed by toolsrv. The `--yolo` server option disables sandbox enforcement for explicit debugging or development. Bypass behavior is controlled by the separate bypass broker and sandbox policy. - -See [`doc/writing-tools.md`](writing-tools.md) for authoring metadata and [`doc/tool-definitions.md`](tool-definitions.md) for metadata-only command definitions.