# Toolsrv Architecture `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. The agent-side client and runtime integration are covered by [`architecture-core.md`](architecture-core.md) and [`architecture-prompting.md`](architecture-prompting.md). 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 ```mermaid flowchart LR A[Agent runtime] --> C[ToolsrvConn] C -->|authenticated 9P| S[toolsrv Server] S --> R[per-agent Registry] S --> P[Process State] P --> M[Metadata resolver] P --> E[Tool executor] E --> X[Native Landlock sandbox helper] E --> B[Bypass broker] E --> T[Installed tool command] ``` `toolsrv` is not an in-process library used by the agent loop. The client and server communicate through the 9P namespace. The server is independently deployable and can execute tools on a remote host. ## Startup and shutdown The command is started as: ```text toolsrv serve --cwd --listen [--yolo] [--no-auth] [--idle-timeout duration] ``` Startup sequence: 1. Require the `serve` subcommand and a `--listen` path. 2. Expand `~` in `--cwd`. 3. Create the log sink and call `util.EnsureEnv()`. 4. Start a short-lived child helper from the toolsrv binary for each restricted tool execution. The helper applies the configured Landlock ruleset directly with Linux system calls, then `exec`s the tool command. 5. Create the per-agent registry and the server state. 6. Apply `--yolo` to the server and process state. 7. Build the 9P tree from `server.Spec`. 8. Install signal cancellation, process cleanup, optional idle monitoring, and process garbage collection. 9. Remove a stale socket and listen on the Unix socket. 10. Accept connections and serve each connection concurrently. Shutdown cancels the run context, closes the listener, kills all tracked processes, removes the socket, flushes logs, and exits. A SIGINT or SIGTERM initiates the same cancellation path. The idle monitor exits before the first connection after a 60-second startup grace period, or after the configured idle timeout once all connections close. ## Server state `server.Server` owns authentication, the tool registry, the yolo flag, and a `server.State` process manager. ```go type Server struct { secret string token string registry *registry.Registry yolo bool Fs *State } ``` `NewServer` creates a state object with no fixed working directory. Working directory, environment, session ID, and agent ID are supplied through each process request and its associated agent connection. `BuildTree` passes the server into `virtfs.BuildTree(Spec(server))`. Namespace handlers capture the server and process state directly; there is no second RPC dispatcher. ## Authentication The client authenticates through the 9P `Tauth` exchange. The first authenticated client supplies the shared secret; the server stores it and returns a random session token. Later clients must provide the same secret and receive the existing token. A different secret is rejected. `--no-auth` disables this check for debugging. It is not the normal deployment mode. The token is required in `proc/new` payloads and binds tool requests to the authenticated toolsrv instance. The Ollie client generates a secret for the first connection, saves it for reconnect, authenticates as `agent`, and attaches the authenticated fid. The client also sends an agent ID with tool-list and process requests so registry state stays scoped per agent. ## 9P namespace ```text / ├── ctl ├── tools ├── all ├── info ├── tools_rev ├── bypass/ │ ├── pending │ └── resolve └── proc/ ├── list ├── new ├── new.bg └── {pid}/ ├── out ├── wait ├── stat └── ctl ``` ### Control and discovery | Path | Operation | Behavior | |---|---|---| | `ctl` | write | `load ` or `unload ` | | `tools` | read/write | write an agent ID, then read its loaded tool list as JSON | | `all` | read | list all host-available tools and descriptions | | `info` | read | return `platform` and `arch` | | `tools_rev` | read/write | write an agent ID, then read that agent’s loaded-tool registry revision | `tools` reads the registry for the supplied agent ID. `all` calls metadata discovery directly and is not the per-agent loaded set. ### Process namespace | Path | Operation | Behavior | |---|---|---| | `proc/list` | read/write | list processes, optionally filtered by agent ID | | `proc/new` | read/write | execute a tool and block for a structured result | | `proc/new.bg` | read/write | start a background tool and return its PID | | `proc/{pid}/out` | read | read accumulated process output | | `proc/{pid}/wait` | blocking read | wait for exit and return the exit code | | `proc/{pid}/stat` | read | return process state, runtime, and tool metadata | | `proc/{pid}/ctl` | write | signal or dismiss a process | Closing a request fid cancels the request context. This is the cancellation path used when an agent interrupts a blocking tool call. ### Bypass namespace | Path | Operation | Behavior | |---|---|---| | `bypass/pending` | blocking read | return the next pending approval request as JSON | | `bypass/resolve` | write JSON | approve or reject a request by ID | The bypass broker is separate from normal sandbox execution. A tool asking for bypass creates a pending request containing its ID, command, working directory, and environment. An authorized observer resolves it through the namespace. ## Tool metadata and discovery Tool metadata format, executable versus metadata-only tools, command resolution, and host variants are documented in [`architecture-tools.md`](architecture-tools.md). The toolsrv registry consumes that metadata and evaluates variants on the host where the service runs. ## Per-agent registry `registry.Registry` stores loaded tools and revision counters by agent ID: ```go type Registry struct { agents map[string]map[string]protocol.ToolInfo revisions map[string]uint64 } ``` The registry is deliberately separate from discovery. Discovery describes every tool available on the host. Loading makes a tool callable for one agent. `Load` resolves current metadata and increments that agent's revision. `Unload` removes the tool and increments the revision. `Loaded` returns tools sorted by name. `Lookup` resolves one loaded tool. The agent client reads `tools_rev` after tool rounds and refreshes its loaded-tool schemas only when the agent-scoped revision changes. A zero revision means the revision endpoint is unavailable, so the client falls back to refreshing. The registry does not inspect executable contents. ## Request payloads and results `proc/new` receives newline-separated `key=value` fields. The client sends at least: ```text token= tool= agent= = ``` Values escape newlines and backslashes. The server parses the payload with `protocol.ParsePayload`, verifies the token, identifies the tool, and converts the remaining fields to JSON arguments before execution. The synchronous result is a `protocol.ToolResult`: ```json { "content": [{"type": "text", "text": "tool output"}], "isError": false } ``` Content can also include image data with a media type. Errors are returned as structured results with `isError: true` where possible. ## Process lifecycle `State` tracks all process objects. Each `Proc` records its ID, command, tool, session ID, agent ID, start and end times, exit code, output, cancellation function, and current state. Synchronous requests create a process, execute it, stream optional output, and expose the final result through the request fid. Background requests use `proc/new.bg`; the process remains in the state table and its output and exit status are available under `proc/{pid}`. Process controls include: - `signal ` to deliver a signal to the child. - `dismiss` to remove a completed process from the namespace. - `KillAll` during server shutdown. - Periodic garbage collection for old completed processes. When a background process exits and contains both a session ID and agent ID, toolsrv writes a system completion message to the agent's `prompt` file through the Ollie 9P namespace. ## Tool execution `exec.ExecuteTool` performs the following steps: 1. Resolve the executable from metadata and the tools directory. 2. Parse and remove dispatch fields from the JSON arguments: `bypass`, `timeout`, and `sandbox`. 3. Resolve the working directory and environment. 4. Route the command to direct bypass execution or sandboxed execution. 5. Capture output, optionally stream it, and enforce output limits. 6. Return a structured `ToolResult`. Normal tools use `os/exec.CommandContext`, so request cancellation terminates the child process. The execution layer supports shell commands from metadata, scripts with interpreters, direct executables, and background process tracking. The model-facing agent limit is 32 KiB. Toolsrv also applies a larger process-output safety limit so runaway output cannot exhaust memory before the agent limit is applied. ## Sandbox The sandbox configuration is loaded from the installed `sandbox.yaml`. It contains general settings, filesystem paths, network policy, environment rules, and advanced Landlock options. ```yaml general: best_effort: true filesystem: ro: [] rox: [] rw: [] network: enabled: true env: [] advanced: ldd: false add_exec: false ``` Paths support `{CWD}`, `{HOME}`, `{TMPDIR}`, XDG variables, and Ollie configuration/data variables. The policy is encoded by the parent toolsrv process and passed to its own short-lived helper mode; no separately installed sandbox executable or wrapper is required. On Linux, the helper sets `no_new_privs`, creates a Landlock ruleset, adds the configured path rules, restricts itself, and executes the tool. `--yolo` skips enforcement for explicit development use. Sandbox validation rejects dangerous command patterns before execution. The execution service also rate-limits repeated validation failures. The bypass path is not a sandbox configuration override; it is a separately approved execution route. ## Cancellation and shutdown Cancellation has a direct transport path: ```mermaid sequenceDiagram participant A as Agent participant C as ToolsrvConn participant S as toolsrv participant P as Child process A->>C: CallTool(ctx) C->>S: write proc/new and block on read A-->>C: ctx cancelled C->>C: close request fid C->>S: Tclunk S->>S: cancel request context S->>P: CommandContext terminates child P-->>S: exit status S-->>C: cancelled/error result ``` A server shutdown cancels the run context, closes the listener, kills tracked processes, and removes the Unix socket. Closing a 9P request fid is idempotent with normal process completion. ## Remote deployment Remote toolsrv startup, SSH forwarding, configuration transfer, and recovery are documented in [`architecture-remote.md`](architecture-remote.md). ## Implementation map | Area | Implementation | |---|---| | CLI and lifecycle | `cmd/toolsrv/main.go` | | 9P namespace and auth | `cmd/toolsrv/internal/server/server.go` | | Process state and handlers | `cmd/toolsrv/internal/server/proc.go` | | Tool execution | `cmd/toolsrv/internal/exec/exec.go` | | Registry | `cmd/toolsrv/internal/registry/registry.go` | | Sandbox configuration | `cmd/toolsrv/internal/sandbox/config.go` | | Metadata parsing and variants | `toolsrv/metadata/meta.go` | | Metadata discovery | `toolsrv/metadata/discover.go` | | Wire types and payload parsing | `toolsrv/protocol/types.go`, `payload.go` | | Agent-side 9P client | `cmd/olliesrv/internal/toolclient/toolsrv.go` |