ollie/doc/architecture-9p.md

11 KiB
Raw Blame History

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.

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, and served by olliesrv. The separate toolsrv namespace is described in 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:

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:

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:

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}/log r Rendered text conversation snapshot (last 64KB): user/assistant text, tool calls, tool output, bypass notices; reasoning/context hidden.
agent/{aname}/log.raw r Full JSONL conversation snapshot of finalized blocks (one-shot read).
agent/{aname}/chat.raw r Live JSONL stream: finalized history then live deltas (blocking).
agent/{aname}/chat r Rendered text conversation stream (blocking). Same rendering as log.
agent/{aname}/block rdwr Lookup block by ID (write ID, read JSON).
agent/{aname}/state r Current agent state (idle, calling, thinking, paused).
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:

echo 'fix the bug in main.go' \
  | ollie-9p write session/myproj/agent/coding/prompt
ollie-9p read session/myproj/agent/coding/log.raw
# 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.

# 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:

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.

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