287 lines
9.8 KiB
Markdown
287 lines
9.8 KiB
Markdown
## 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):
|
|
|
|
```sh
|
|
9pfuse 'unix!/tmp/ns.$USER.$DISPLAY/ollie' ~/mnt/ollie
|
|
cd ~/mnt/ollie
|
|
ls session/
|
|
```
|
|
|
|
Or use `ollie-9p` for direct file operations without mounting:
|
|
|
|
```sh
|
|
ollie-9p ls session/
|
|
ollie-9p read session/myproj/agent/coding/chat
|
|
```
|
|
|
|
### Raw session operations
|
|
|
|
```sh
|
|
# 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:
|
|
|
|
```sh
|
|
# 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:
|
|
|
|
```sh
|
|
# 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
|
|
|
|
```sh
|
|
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:
|
|
|
|
```xml
|
|
<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.
|