ollie/doc/architecture-9p.md

235 lines
11 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.

# 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.
Agents use the same namespace to bootstrap capabilities. A minimal profile can
start with only `client_9p`; it loads another tool by writing
`tool_load <name>` to its own `agent/{name}/ctl` file. The tool becomes callable
after the runtime refreshes its toolsrv registry revision.
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`. |
| `event` | 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] [yolo=true]`; read the resulting name. |
| `session/idx` | r | Session index: `session-id\tsession-name\tpaused\tconnected\tremote\tcwd`. |
| `session/{sname}/env` | r | Session environment and runtime variables. |
| `session/{sname}/paused` | r | Pause state. |
| `session/{sname}/goal` | r/w | Session goal text. Writing triggers the workflow if not running. |
| `session/{sname}/goalstatus` | r/w | Goal status: running, complete, blocked, or error. |
| `session/{sname}/goalwait` | r | Blocks until goal status changes. |
| `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`. With `prompt=`, runs as sub-agent. |
| `agent/idx` | r | Agent index: `session-id\tagent-id\tagent-name\tparent-id\tdepth\tstate`. |
| `agent/{aname}/prompt` | w | Queue a user turn. |
| `agent/{aname}/fifo` | r/w | Prompt queue. |
| `agent/{aname}/chat` | r | Filtered streaming chat output. |
| `agent/{aname}/chat.raw` | r | Full streaming output with markers. |
| `agent/{aname}/state` | r | Current agent state (idle, calling, thinking, paused). |
| `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}/peer/` | dir | Peer agent links (write-only entries). |
| `agent/{aname}/peer/{name}` | w | Send a message to the named peer agent. |
| `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
# Wait for any agent state to change
ollie-9p read event | grep "\.state"
# Wait for specific agent state changes
echo "session.myproj.agent.coding.state" | ollie-9p rdwrs event
```
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.
### Peers
Agents in the same session can be linked as peers via `peeradd`. Peer links are bidirectional — adding A as a peer of B also adds B as a peer of A. Once linked, agents communicate by writing to `peer/{name}`, which delivers the message to the target agent's prompt handler. Agents can only message their declared peers, providing topology-level access control.
```sh
# Link two agents
echo "peeradd panelist_a" | ollie-9p rdwr session/myproj/agent/foreman/ctl
# Agent sends a message to its peer
echo "here are my findings" | ollie-9p write session/myproj/agent/panelist_a/peer/foreman
```
### Per-agent file ownership
Each agent directory is owned by its agent ID and belongs to the `agent` group.
File permissions enforce isolation between agents:
| File | Mode | Owner | Effect |
|------|------|-------|--------|
| `prompt` | 0200 | agent | Only the owning agent can write prompts |
| `fifo` | 0600 | agent | Only the owning agent can read/write its queue |
| `chat` | 0440 | agent | Owner and group can read; other agents cannot |
| `plan` | 0600 | agent | Only the owning agent can access its plan |
| `ctl` | 0600 | agent | Only the owning agent can control itself |
| `cfg` | 0640 | agent | Owner can read/write; group can read |
| `state` | 0444 | agent | World-readable (non-sensitive) |
| `id` | 0444 | agent | World-readable (non-sensitive) |
**Admin access**: Clients connecting with empty uname or "admin" bypass permission
checks and have full access to all files. This allows CLI tools and GUIs to
manage all agents.
**Agent isolation**: Agent A cannot read Agent B's `plan` or `chat` (mode 0600/0440
with different owner). This is enforced at the 9P protocol layer — the permission
check happens on open, not in application logic.
**Peer communication**: Inter-agent messaging uses the `peer/` directory, which is
dynamically generated from the peer list. An agent can only write to peers it has
been explicitly linked to via `peeradd`.
### 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. |
| `peeradd <name>` | Add a bidirectional peer link to the named agent. |
| `peerdel <name>` | Remove a bidirectional peer link. |
| `peers` | List current peers. |
| `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:** `event` and streaming chat replace event subscriptions with ordinary reads. The `event` file supports filtered subscriptions via the streaming rdwr pattern.
- **Stateless request/response:** `generate`, `ctl`, and `rdwr` files accept a request and return a result.
- **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
# Subscribe to all state changes and process them
ollie-9p read event | while read -r topic payload; do
case "$topic" in
*.state) echo "State: $payload" ;;
esac
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.
The Ollie client authenticates to toolsrv over a separate 9P connection. The toolsrv authentication and policy details belong in [`architecture-toolsrv.md`](architecture-toolsrv.md#authentication).
## 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` |