ollie/doc/resources/multi-agent.md

204 lines
7.0 KiB
Markdown

# Multi-Agent Coordination
ollie exposes agents as filesystem objects via the 9P protocol. This means
multi-agent coordination requires no framework, SDK, or message bus — it is
ordinary shell scripting against a mounted filesystem.
## The Namespace
Every session lives under `/session/{name}/` where `{name}` is the session's
**mutable human-readable name** (not the immutable UUID — see `id` and `name`
files). Session directories are keyed by Name; renaming a session via writing
to its `name` file moves the entire directory subtree.
| Path | Purpose |
|------|---------|
| `/session/new` | Create an empty session (write `name=...`, read back name) |
| `/session/{name}/agent/new` | Create an agent within a session (write `cwd=... backend=...`) |
| `/session/{name}/env` | Session environment variables |
| `/session/{name}/id` | Immutable session UUID |
| `/session/{name}/name` | Mutable session name (write to rename) |
| `/session/{name}/agent/{aname}/prompt` | Submit a prompt |
| `/session/{name}/agent/{aname}/chat` | Streaming chat (filtered text) |
| `/session/{name}/agent/{aname}/plan` | Agent-scoped markdown checklist |
| `/session/{name}/agent/{aname}/cfg` | Agent config KV: backend, model, agent, cwd, params |
| `/session/{name}/agent/{aname}/statewait` | **Blocking read** — returns when state changes |
| `/session/{name}/agent/{aname}/ctl` | Control commands: `stop`, `kill`, `compact`, `rn <name>` |
| `/session/{name}/agent/{aname}/id` | Immutable agent ID (uname) |
| `/session/{name}/agent/{aname}/name` | Mutable display name (write to rename) |
Global request-response files provide stateless operations without sessions:
| File | Purpose |
|------|---------|
| `/generate` | Write prompt, read response (one-shot generation) |
Global namespaces are shared across all sessions:
| Path | Purpose |
|------|---------|
| `/session/idx` | Session index (id, state, cwd, backend, model) |
| `/session/{name}/agent/{aname}/plan` | Agent checklist |
| `/session/{name}/agent/{aname}/chat` | Streaming filtered text |
## Spawning Agents
From within an agent session, use `subagent_spawn`:
```sh
subagent_spawn [-name NAME] [-agent AGENT] [-backend BACKEND] [-model MODEL] [-cwd DIR] <prompt>
```
This creates a child session + agent atomically, writes the prompt, and returns
the session path (e.g. `session/my-session`). Child sessions carry the parent
session ID in their name, so the spawning tree is recoverable from session IDs alone.
For transient `subagent_spawn` calls, the child gets its own context window and
runtime state. It receives a one-time snapshot of the parent's conversation
history, not a live shared context. The child cannot mutate the parent's
history, and the parent receives only the child's final reply. The parent
explicitly decides how to incorporate that reply. Workspace changes and other
shared resources are coordinated separately through toolsrv locks.
### Explicit Two-Step Creation
Session creation can be split into two phases for advanced orchestration
scenarios:
1. **Create empty session**: `echo "name=my-session" | ollie-9p rdwr session/new`
2. **Add agent**: `echo "cwd=$HOME agent=default backend=openai model=gpt-4o" | ollie-9p rdwr session/my-session/agent/new`
Empty sessions (`Core=nil`) are valid — they appear in `session/idx`, have
no running agent, and can be killed or renamed. This enables pre-provisioning
session directories before the agent environment is ready.
## Coordination Patterns
### Hub-and-Spoke (Conductor)
```mermaid
flowchart TB
ROOT["Root Agent\n(reasoning)"]
W1["Worker A\n(cheap model)"]
W2["Worker B\n(cheap model)"]
ROOT -- "subagent_spawn" --> W1
ROOT -- "subagent_spawn" --> W2
W1 -- "statewait → read chat" --> ROOT
W2 -- "statewait → read chat" --> ROOT
```
A root agent reasons about a task and delegates subtasks to workers. Workers
report back via their log; the conductor reads `/session/{name}/agent/{aname}/log` after
`statewait` unblocks.
```sh
# Conductor spawns two workers
worker_a=$(subagent_spawn -model haiku "analyse /src/a.go")
worker_b=$(subagent_spawn -model haiku "analyse /src/b.go")
# Wait for both
until [ "$(cat $worker_a/state)" = "idle" ]; do cat $worker_a/statewait; done
until [ "$(cat $worker_b/state)" = "idle" ]; do cat $worker_b/statewait; done
# Collect results
cat $worker_a/chat $worker_b/chat
```
The conductor and workers can run on different models. Mechanical analysis goes
to cheaper models; reasoning stays on the expensive one.
### Pipeline
```mermaid
flowchart LR
A["Step 1\nExtract TODOs"]
B["Step 2\nPrioritise"]
A -->|"/tmp/todos.txt"| B
```
Agent A produces output; agent B consumes it. Composition via `/tmp/` or direct
path passing.
```sh
step1=$(subagent_spawn "extract all TODOs from /src/ and write to /tmp/todos.txt")
until [ "$(cat $step1/state)" = "idle" ]; do cat $step1/statewait; done
subagent_spawn "prioritise the TODOs in /tmp/todos.txt and produce a plan"
```
### Scatter-Gather (Swarm)
```mermaid
flowchart TB
P["Parent"]
S1["Worker 1"]
S2["Worker 2"]
S3["Worker N"]
P --> S1
P --> S2
P --> S3
S1 --> P
S2 --> P
S3 --> P
```
Spawn N workers in parallel, collect when all reach `idle`. Because `statewait`
blocks until state changes, no polling loop is needed.
```sh
# Scatter
for f in /src/*.go; do
subagent_spawn "review $f for security issues" &
done
wait # wait for all subagent_spawn calls to return session paths
```
Or from within `shell`:
```json
{
"steps": [
{ "parallel": [
{ "code": "subagent_spawn 'task A'" },
{ "code": "subagent_spawn 'task B'" },
{ "code": "subagent_spawn 'task C'" }
]}
]
}
```
### Peer-to-Peer
Any agent can read any other agent's session directory. There is no privileged
orchestrator role — agents coordinate by reading each other's `chat` and `state`
files directly. A peer can wait on another peer's `statewait`, then act on what
it reads in `chat`.
### Reactive / Event-Driven
`statewait` is a proper synchronization primitive. A shell script (or another
agent) blocks on it and wakes exactly when the target agent transitions state.
This enables event-driven pipelines without polling:
```sh
# Block until agent finishes, then trigger downstream work
cat /session/worker-123/statewait
# agent is now idle — proceed
```
### Self-Directed Spawning
Agents can spawn sub-agents mid-task without human direction. If an agent
determines that parallelism or specialization would help, it calls
`subagent_spawn` on its own. No human prompt is required to initiate a
multi-agent workflow — the model decides.
## Design Principle
ollie imposes no coordination model. There is no built-in conductor, swarm
layer, or workflow engine. Orchestration is handled by the surrounding
environment — shell scripts, cron, systemd, containers, or another agent.
This is intentional. The filesystem namespace is the API. Any tool that can
read and write files can participate in a multi-agent workflow, which means the
full Unix toolchain is available as an orchestration layer.