## 9P Filesystem: Everything is a File The 9P namespace is the lowest-level interface to ollie. Everything — sessions, agents, prompts, tools, state — is a file. The `o` CLI wraps these operations, but the raw filesystem is the foundation. ### Mounting The olliesrv namespace can be mounted as a real filesystem using 9pfuse or Plan 9's mount(1): ```sh 9pfuse 'unix!/tmp/ns.$USER.$DISPLAY/ollie' ~/mnt/ollie cd ~/mnt/ollie ls session/ ``` Or use `ollie-9p` for direct file operations without mounting: ```sh ollie-9p ls session/ ollie-9p read session/myproj/agent/coding/chat ``` ### Raw session operations ```sh # Create a session echo "name=myproj" | ollie-9p write session/new # Create an agent echo "name=coding cwd=$PWD" | ollie-9p write session/myproj/agent/new # Submit a prompt echo "fix the bug in main.go" | ollie-9p write session/myproj/agent/coding/prompt # Stream the response ollie-9p read session/myproj/agent/coding/chat # Check state (blocks until change) ollie-9p read session/myproj/agent/coding/statewait # Spawn a sub-agent (blocks until done, returns reply) printf 'cwd=%s\nprompt=fix the failing tests\n' "$PWD" \ | ollie-9p rdwr session/myproj/agent/new ``` ### Network transparency 9P is a network protocol. Mount the agent namespace from any machine and every session, tool, and config value is accessible as if local: ```sh # From another machine: 9p -a 'tcp!server:5640' read session/myproj/agent/coding/statewait ``` No SSH tunneling, no port forwarding, no API gateway. ### Shell composability Because operations are file I/O, they compose with the full Unix toolkit: ```sh # Fan out a prompt to all agents for s in session/*/agent/*/prompt; do echo "run tests" > "$s"; done # Wait for all agents to finish for s in session/*/agent/*/statewait; do cat "$s" > /dev/null; done # Pipe git diffs into an observer's feed (dedup is automatic) while :; do git diff HEAD --; sleep 5; done | ollie-9p write session/myproj/agent/obs/feed # Wire an agent's state changes to an observer while :; do ollie-9p read session/myproj/agent/coder/statewait >/dev/null; git diff HEAD --; done \ | ollie-9p write session/myproj/agent/obs/feed # Grep all agent plans grep -r "TODO" session/*/agent/*/plan # Monitor costs cat session/*/agent/*/stats # Strip block markup from chat output cat session/myproj/agent/coding/chat | grep -vE '^\[\[\[.*' ``` Pipe agents into `awk`. Filter with `grep`. Schedule with `cron`. Orchestrate with a 10-line shell script instead of a framework. ### Plan 9 patterns - **`/net/dns` pattern** — `/generate` and `ctl` are stateless per-fid: write a request, read back the result - **`ctl` files** — control operations (stop, kill, rename, compact) are writes to a control file, not method calls - **Blocking reads** — `statewait` and `feed` block until their value changes, replacing event subscriptions with a simple `cat` in a loop - **Change detection** — `feed` deduplicates on the read side: same data written repeatedly never wakes the reader - **Multiplexing** — multiple clients mount the same server simultaneously, each with an independent view through per-fid state --- ## olliesrv namespace ### Root (`/`) | File | Mode | Description | |------|------|-------------| | `backends` | r | Available backend names, one per line | | `models` | r | Available models: `backendmodel` per line | | `agents` | r | Available agent profiles, one per line | | `tools` | r | Available tools: `namedescription` per line | | `help` | r | Filesystem reference | | `aliases` | r | ID-to-path table: `idpath` per line | | `ctl` | rdwr | Server control: `invalidate`, `kill` | | `eventwait` | r (blocking) | Blocks until a server event, returns event data | | `generate` | rdwr | One-shot LLM generation (write prompt, read response) | | `session/` | dir | Session management | ### `/generate` — one-shot LLM ```sh echo 'summarize this repo' | ollie-9p rdwr generate echo '{"prompt":"explain recursion"}' | ollie-9p rdwr generate ``` ### Session (`session/`) | File | Mode | Description | |------|------|-------------| | `session/new` | rdwr | Create session: write `name=X [remote=Y]`, read name | | `session/idx` | r | Session index | ### Session instance (`session/{sname}/`) | File | Mode | Description | |------|------|-------------| | `env` | r | Session environment (OLLIE_SESSION_ID, OLLIE_* vars) | | `paused` | r | `true` or `false` | | `ctl` | rdwr | Session control: `kill`, `save`, `invalidate`, `pause`, `resume` | | `name` | r/w | Mutable session name (write to rename) | | `id` | r | Immutable session UUID | | `agent/` | dir | Agents within this session | ### Agent directory (`session/{sname}/agent/`) | File | Mode | Description | |------|------|-------------| | `new` | rdwr | Create agent: write `name=X cwd=Y`, read agent ID | | `idx` | r | Agent index | ### Agent instance (`session/{sname}/agent/{aname}/`) | File | Mode | Description | |------|------|-------------| | `prompt` | w | Queue a new turn (write prompt text) | | `fifo` | r/w | Prompt queue (write=enqueue, read=dequeue) | | `feed` | r/w (blocking) | Change-detecting input: write data, read blocks until value changes | | `chat` | r (streaming) | Filtered text output (markers stripped) | | `chat.raw` | r (streaming) | Full output with block markers | | `statewait` | r (blocking) | Blocks until state changes, returns new state | | `log` | r | Last 64KB of conversation (non-blocking snapshot) | | `plan` | r/w | Scratch space for agent planning (survives compaction) | | `cfg` | r/w | Agent config (read: key=value dump; write: `key=value`) | | `ctl` | rdwr | Agent control (write command, read response) | | `stats` | r | Token usage, cost, context size (key=value) | | `name` | r/w | Mutable agent display name | | `id` | r | Immutable agent UUID | | `proc/` | dir | Background processes owned by this agent | | `proc/top` | r | Process table: PID, TOOL, CMD | ### Agent ctl commands All ctl commands are request-response: write the command, read the result. | Command | Description | |---------|-------------| | `stop` | Interrupt running agent | | `compact` | Compact context window | | `clear` | Clear conversation history | | `kill` | Remove the agent | | `inject ` | Inject text into context (alias: `i`) | | `agent [profile]` | Show or switch agent profile | | `model [name]` | Show or switch model | | `models` | List available models | | `backend` | Show current backend (read-only) | | `name [name]` | Show or set display name | | `cwd [path]` | Show or change working directory | | `tools` | List loaded tools | | `tool_load ` | Load a tool | | `tool_unload ` | Unload a tool | | `proc` | List background processes | | `proc top` | Formatted process table | | `proc term ` | Terminate a process | | `proc kill ` | Force-kill a process | | `proc out ` | Read process output | | `proc dismiss ` | Dismiss a completed process | | `systemprompt` | Dump the resolved system prompt | | `help` | List available commands | --- ## toolsrv namespace toolsrv is a separate 9P server spawned by olliesrv (one per session). It handles tool execution in a sandboxed environment. ### Root (`/`) | File | Mode | Description | |------|------|-------------| | `ctl` | w | Control: `load `, `unload ` | | `tools` | rdwr | Write agent ID, read tool list for that agent | | `info` | r | Host info: `platform=`, `arch=` | | `bypass/` | dir | Sandbox bypass approval broker | | `proc/` | dir | Process execution and management | ### Bypass broker (`bypass/`) | File | Mode | Description | |------|------|-------------| | `pending` | r (blocking) | Blocks until a bypass request arrives; returns JSON | | `resolve` | w | Resolve request: write `{"id":"...","approved":true}` or `{"id":"...","error":"..."}` | ### Processes (`proc/`) | File | Mode | Description | |------|------|-------------| | `list` | r | All processes: `pidstatetool` | | `new` | rdwr | Execute tool (blocking): write key=value payload, read result | | `new.bg` | rdwr | Execute tool in background: write same, read pid | Payload format for `new` / `new.bg` (key=value lines, `\n` escaped in values): ``` tool=shell agent= cmd=go test ./... args={"cmd":"go test ./..."} ``` ### Process instance (`proc/{pid}/`) | File | Mode | Description | |------|------|-------------| | `out` | r | Process output buffer | | `wait` | r (blocking) | Blocks until process exits, returns exit code | | `stat` | r | Process status: running/exited, runtime, tool | | `ctl` | w | Control: `term`, `kill`, `signal `, `dismiss` | When a background process exits, toolsrv pushes the complete output to the agent's prompt as a `` message: ```xml output here ``` --- ## Authentication and permissions Both servers use the identity provided at attach time for permission enforcement. **olliesrv** does not require Tauth (no secret exchange), but the `uname` from Tattach is used for standard 9P file permission checks (uid/gid/world bits). Files are owned by the server's user with group `agent`; any attaching client is added to the `agent` group, so group permission bits govern what agents can access. **toolsrv** requires Tauth. The protocol: 1. Client sends Tauth with `aname` identifying the agent. 2. Client writes the shared secret to the auth fid. 3. Server verifies the secret (first client sets it; subsequent clients must match). 4. Server returns a session token via read on the auth fid. 5. Client attaches using the authenticated afid. The `aname` field scopes tool registry lookups and process ownership to the connecting agent. Currently there is no mechanism to prevent one agent from claiming another's identity; the scheme relies on all clients being spawned by olliesrv with the correct secret.