Update 9P architecture documentation

This commit is contained in:
Ollie Agent 2026-08-16 19:50:35 +02:00
parent 55a9de9710
commit 02f130f79e
4 changed files with 176 additions and 288 deletions

286
doc/9p.md
View File

@ -1,286 +0,0 @@
## 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: `backend<tab>model` per line |
| `agents` | r | Available agent profiles, one per line |
| `tools` | r | Available tools: `name<spaces>description` per line |
| `help` | r | Filesystem reference |
| `aliases` | r | ID-to-path table: `id<tab>path` 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 <text>` | 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 <name>` | Load a tool |
| `tool_unload <name>` | Unload a tool |
| `proc` | List background processes |
| `proc top` | Formatted process table |
| `proc term <pid>` | Terminate a process |
| `proc kill <pid>` | Force-kill a process |
| `proc out <pid>` | Read process output |
| `proc dismiss <pid>` | 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 <agentID> <tool>`, `unload <agentID> <tool>` |
| `tools` | rdwr | Write agent ID, read tool list for that agent |
| `info` | r | Host info: `platform=<os>`, `arch=<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: `pid<tab>state<tab>tool` |
| `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=<agentID>
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 <N>`, `dismiss` |
When a background process exits, toolsrv pushes the complete output to the
agent's prompt as a `<system-proc-complete>` message:
```xml
<system-proc-complete id="1" cmd="echo test" exit="0">
output here
</system-proc-complete>
```
---
## 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.

174
doc/architecture-9p.md Normal file
View File

@ -0,0 +1,174 @@
# 9P Architecture
9P is Ollie’s public API. The server exposes sessions, agents, prompts, tools, state, and control operations as a filesystem. Clients use reads, writes, and blocking reads instead of an application-specific RPC API.
```text
client ── 9P2000 over Unix socket or TCP ── olliesrv
│
└─ virtfs.Tree
└─ Ollie namespace
agent ── authenticated 9P over Unix socket ── toolsrv
├─ registry
├─ sandbox
└─ processes
```
The Ollie namespace is declared in `cmd/olliesrv/internal/fs/spec.go`, materialized by [`virtfs`](architecture-virtfs.md), and served by `olliesrv`. The separate toolsrv namespace is described in [`architecture-toolsrv.md`](architecture-toolsrv.md).
## Transports
`olliesrv` listens on the Unix socket selected by `$NAMESPACE`. It can also expose the same server on an optional TCP address. The TCP listener speaks the same 9P protocol and namespace; it does not add an HTTP or JSON API.
Use `ollie-9p` for direct operations:
```sh
ollie-9p ls session/
ollie-9p read session/myproj/agent/coding/chat
```
A 9P filesystem client can mount the namespace when the platform provides a 9P mount utility. The `o` shell client and KDE integrations are higher-level clients over the same namespace.
## Root namespace
| Path | Mode | Purpose |
|---|---:|---|
| `backends` | r | Available backend names. |
| `models` | r | Available models as `backend<TAB>model`. |
| `agents` | r | Available agent profiles. |
| `tools` | r | Available tool names and descriptions. |
| `help` | r | Generated filesystem reference. |
| `aliases` | r | Stable ID-to-path mappings. |
| `ctl` | rdwr | Server controls such as `invalidate` and `kill`. |
| `eventwait` | r | Blocking server-event stream. |
| `generate` | rdwr | One-shot LLM request. |
| `session/` | dir | Session collection. |
One-shot generation uses a write followed by a read:
```sh
echo 'summarize this repo' | ollie-9p rdwr generate
echo '{"prompt":"explain recursion"}' | ollie-9p rdwr generate
```
## Session namespace
| Path | Mode | Purpose |
|---|---:|---|
| `session/new` | rdwr | Create a session. Write `name=X [remote=Y]`; read the resulting name. |
| `session/idx` | r | Session index. |
| `session/{sname}/env` | r | Session environment and runtime variables. |
| `session/{sname}/paused` | r | Pause state. |
| `session/{sname}/ctl` | rdwr | `kill`, `save`, `invalidate`, `pause`, and `resume`. |
| `session/{sname}/name` | r/w | Mutable session display name. |
| `session/{sname}/id` | r | Immutable session UUID. |
| `session/{sname}/agent/` | dir | Agents in the session. |
Create a session and agent:
```sh
echo 'name=myproj' | ollie-9p write session/new
echo "name=coding cwd=$PWD" | ollie-9p write session/myproj/agent/new
```
## Agent namespace
| Path | Mode | Purpose |
|---|---:|---|
| `agent/new` | rdwr | Create an agent from `name=X cwd=Y`. |
| `agent/idx` | r | Agent index. |
| `agent/{aname}/prompt` | w | Queue a user turn. |
| `agent/{aname}/fifo` | r/w | Prompt queue. |
| `agent/{aname}/feed` | r/w | Change-detecting input stream. |
| `agent/{aname}/chat` | r | Filtered streaming chat output. |
| `agent/{aname}/chat.raw` | r | Full streaming output with markers. |
| `agent/{aname}/statewait` | r | Blocking state-change read. |
| `agent/{aname}/log` | r | Conversation snapshot. |
| `agent/{aname}/plan` | r/w | Persistent planning scratch space. |
| `agent/{aname}/cfg` | r/w | Agent configuration. |
| `agent/{aname}/ctl` | rdwr | Agent controls and queries. |
| `agent/{aname}/stats` | r | Token, cost, and context statistics. |
| `agent/{aname}/name` | r/w | Mutable agent display name. |
| `agent/{aname}/id` | r | Immutable agent UUID. |
| `agent/{aname}/proc/` | dir | Agent-owned background processes. |
Submit and observe work:
```sh
echo 'fix the bug in main.go' \
| ollie-9p write session/myproj/agent/coding/prompt
ollie-9p read session/myproj/agent/coding/chat
ollie-9p read session/myproj/agent/coding/statewait
```
Sub-agents use the same namespace. The `agent/new` rdwr operation can create a child session and return its final response after the child exits.
### Agent controls
| Command | Purpose |
|---|---|
| `stop` | Interrupt the running turn. |
| `compact` | Compact conversation history. |
| `clear` | Clear conversation history. |
| `kill` | Remove the agent. |
| `inject <text>` | Add text to the context. |
| `agent [profile]` | Show or switch the profile. |
| `model [name]` | Show or switch the model. |
| `models` | List models. |
| `backend` | Show the current backend. |
| `name [name]` | Show or set the display name. |
| `cwd [path]` | Show or change the working directory. |
| `tools` | List loaded tools. |
| `tool_load <name>` | Load a tool. |
| `tool_unload <name>` | Unload a tool. |
| `proc` / `proc top` | List background processes. |
| `proc term <pid>` / `proc kill <pid>` | Stop a background process. |
| `proc out <pid>` | Read process output. |
| `proc dismiss <pid>` | Dismiss a completed process. |
| `systemprompt` | Read the rendered system prompt. |
| `help` | List controls. |
Control files use request-response semantics: write one command, then read the response.
## Plan 9 interaction patterns
- **Control files:** writes express operations such as stop, kill, rename, compact, and reload.
- **Blocking reads:** `statewait`, `eventwait`, `feed`, and streaming chat replace event subscriptions with ordinary reads.
- **Stateless request/response:** `generate`, `ctl`, and `rdwr` files accept a request and return a result.
- **Change detection:** `feed` does not wake readers for identical consecutive data.
- **Shared namespace:** multiple clients can inspect and modify the same sessions concurrently.
- **Stable aliases:** mutable names are listed normally; immutable IDs can resolve the same nodes through invisible aliases.
Shell composition is intentional:
```sh
for p in session/*/agent/*/prompt; do echo 'run tests' > "$p"; done
for s in session/*/agent/*/statewait; do cat "$s" > /dev/null; done
grep -r TODO session/*/agent/*/plan
cat session/*/agent/*/stats
```
## Authentication and permissions
`olliesrv` serves the Ollie namespace and applies 9P identity and file-mode checks. The namespace is intended for the local user and trusted clients; the Unix socket and optional TCP exposure provide transport boundaries, not a separate authorization API.
`toolsrv` has its own authenticated 9P connection. `ToolsrvConn` performs `Tauth`, sends the shared secret, receives a session token, and attaches with the authenticated fid. The agent ID scopes loaded tools and process ownership. See [`architecture-toolsrv.md`](architecture-toolsrv.md) for the toolsrv authentication and policy details.
## Lifecycle
At startup, `olliesrv` resolves the namespace, constructs the virtfs tree, starts the Unix listener, restores sessions, and optionally starts TCP. Shutdown cancels the root context, closes listeners, terminates tracked processes, removes the Unix socket, and flushes logs.
The filesystem tree contains live handlers. Reads and writes therefore reach current session and agent state rather than a static export. Dynamic session and agent directories are rebuilt through virtfs bindings when the namespace resolves them.
## Source map
| Responsibility | Source |
|---|---|
| Ollie namespace declaration | `cmd/olliesrv/internal/fs/spec.go` |
| Namespace tree construction | `cmd/olliesrv/internal/fs/newroot.go` |
| Ollie 9P protocol server | `cmd/olliesrv/server.go` |
| Server startup and listeners | `cmd/olliesrv/main.go` |
| Direct command-line client | `cmd/ollie-9p/main.go` |
| Shared virtfs implementation | `virtfs/` |
| Tool-service 9P server | `cmd/toolsrv/p9.go` |
| Tool-service client | `cmd/olliesrv/internal/toolclient/toolsrv.go` |

View File

@ -1,6 +1,6 @@
# Ollie Technical Whitepaper
The full 9P filesystem reference has been moved to [`9p.md`](9p.md).
The full 9P filesystem reference has been moved to [`architecture-9p.md`](architecture-9p.md).
## Store Federation

View File

@ -291,5 +291,5 @@ while :; do git diff HEAD --; sleep 5; done | tee >(o myproj/security write feed
- **[KDE](kde-gui.md)** — plasmoid, standalone GUI, Kate plugin, KRunner
- **acme** — Plan 9 editor (scripts in `data/scripts/acme/`)
For technical details on the raw 9P filesystem, see [`doc/9p.md`](9p.md).
For technical details on the raw 9P filesystem, see [`doc/architecture-9p.md`](architecture-9p.md).
For tool architecture and authoring, see [`architecture-tools.md`](architecture-tools.md).