Update 9P architecture documentation
This commit is contained in:
parent
55a9de9710
commit
02f130f79e
286
doc/9p.md
286
doc/9p.md
|
|
@ -1,286 +0,0 @@
|
|||
## 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=$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:
|
||||
|
||||
```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.
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
# 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.
|
||||
|
||||
```text
|
||||
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`](architecture-virtfs.md), and served by `olliesrv`. The separate toolsrv namespace is described in [`architecture-toolsrv.md`](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.
|
||||
|
||||
Use `ollie-9p` for direct operations:
|
||||
|
||||
```sh
|
||||
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`. |
|
||||
| `eventwait` | 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:
|
||||
|
||||
```sh
|
||||
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]`; read the resulting name. |
|
||||
| `session/idx` | r | Session index. |
|
||||
| `session/{sname}/env` | r | Session environment and runtime variables. |
|
||||
| `session/{sname}/paused` | r | Pause state. |
|
||||
| `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:
|
||||
|
||||
```sh
|
||||
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`. |
|
||||
| `agent/idx` | r | Agent index. |
|
||||
| `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}/statewait` | r | Blocking state-change read. |
|
||||
| `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}/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:
|
||||
|
||||
```sh
|
||||
echo 'fix the bug in main.go' \
|
||||
| ollie-9p write session/myproj/agent/coding/prompt
|
||||
ollie-9p read session/myproj/agent/coding/chat
|
||||
ollie-9p read session/myproj/agent/coding/statewait
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### 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. |
|
||||
| `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:** `statewait`, `eventwait`, `feed`, and streaming chat replace event subscriptions with ordinary reads.
|
||||
- **Stateless request/response:** `generate`, `ctl`, and `rdwr` files accept a request and return a result.
|
||||
- **Change detection:** `feed` does 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:
|
||||
|
||||
```sh
|
||||
for p in session/*/agent/*/prompt; do echo 'run tests' > "$p"; done
|
||||
for s in session/*/agent/*/statewait; do cat "$s" > /dev/null; 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.
|
||||
|
||||
`toolsrv` has its own authenticated 9P connection. `ToolsrvConn` performs `Tauth`, sends the shared secret, receives a session token, and attaches with the authenticated fid. The agent ID scopes loaded tools and process ownership. See [`architecture-toolsrv.md`](architecture-toolsrv.md) for the toolsrv authentication and policy details.
|
||||
|
||||
## 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` |
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Ollie Technical Whitepaper
|
||||
|
||||
The full 9P filesystem reference has been moved to [`9p.md`](9p.md).
|
||||
The full 9P filesystem reference has been moved to [`architecture-9p.md`](architecture-9p.md).
|
||||
|
||||
## Store Federation
|
||||
|
||||
|
|
|
|||
|
|
@ -291,5 +291,5 @@ while :; do git diff HEAD --; sleep 5; done | tee >(o myproj/security write feed
|
|||
- **[KDE](kde-gui.md)** — plasmoid, standalone GUI, Kate plugin, KRunner
|
||||
- **acme** — Plan 9 editor (scripts in `data/scripts/acme/`)
|
||||
|
||||
For technical details on the raw 9P filesystem, see [`doc/9p.md`](9p.md).
|
||||
For technical details on the raw 9P filesystem, see [`doc/architecture-9p.md`](architecture-9p.md).
|
||||
For tool architecture and authoring, see [`architecture-tools.md`](architecture-tools.md).
|
||||
|
|
|
|||
Loading…
Reference in New Issue