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=/home/user/project" | 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=/home/user/project\nprompt=fix the failing tests\n' \
| 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/dnspattern —/generateandctlare stateless per-fid: write a request, read back the resultctlfiles — control operations (stop, kill, rename, compact) are writes to a control file, not method calls- Blocking reads —
statewaitandfeedblock until their value changes, replacing event subscriptions with a simplecatin a loop - Change detection —
feeddeduplicates 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:
- Client sends Tauth with
anameidentifying the agent. - Client writes the shared secret to the auth fid.
- Server verifies the secret (first client sets it; subsequent clients must match).
- Server returns a session token via read on the auth fid.
- 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.