ollie/doc/9p.md

9.8 KiB

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

9pfuse 'unix!/tmp/ns.$USER.$DISPLAY/ollie' ~/mnt/ollie
cd ~/mnt/ollie
ls session/

Or use ollie-9p for direct file operations without mounting:

ollie-9p ls session/
ollie-9p read session/myproj/agent/coding/chat

Raw session operations

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

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

# 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

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:

<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.