9.7 KiB
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}/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}/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:
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.
# 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
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,feed, and streaming chat replace event subscriptions with ordinary reads. Theeventfile supports filtered subscriptions via the streaming rdwr pattern. - Stateless request/response:
generate,ctl, andrdwrfiles accept a request and return a result. - Change detection:
feeddoes 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:
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 |