ollie/doc/MULTI_AGENT.md

5.6 KiB

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/{id}/:

Path Purpose
/session/new Create a session (write name, backend, model, agent, cwd)
/session/{id}/plan Session-scoped markdown checklist
/session/{id}/env Session environment variables
/session/{id}/agent/{aid}/prompt Submit a prompt (dispatched async on close)
/session/{id}/agent/{aid}/chat Cumulative conversation history (append-only read)
/session/{id}/agent/{aid}/cfg Agent config (KV): state, backend, model, agent, cwd, params
/session/{id}/agent/{aid}/statewait Blocking read — returns when state changes
/session/{id}/agent/{aid}/ctl Control commands: stop, kill, compact, rn <name>

Global request-response files provide stateless operations without sessions:

File Purpose
/generate Write prompt, read response (one-shot generation)
/complete Write JSON {file, prefix, suffix}, read code completion
/route Write task description, read backend=X model=Y recommendation

Global namespaces are shared across all sessions:

Path Purpose
/session/idx Session index (id, state, cwd, backend, model)
/session/{id}/plan Shared session-scoped checklist
/session/{id}/agent/{aid}/chat Full chat history (searchable)

Spawning Agents

From within an agent session, use subagent_spawn:

subagent_spawn [-name NAME] [-agent AGENT] [-backend BACKEND] [-model MODEL] [-cwd DIR] <prompt>

This creates a child session, 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.

Coordination Patterns

Hub-and-Spoke (Conductor)

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 chat history; the conductor reads /session/{id}/agent/{aid}/chat after statewait unblocks.

# 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

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.

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)

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.

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

{
  "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:

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