diff --git a/doc/USAGE.md b/doc/USAGE.md index d1b4ed5..0e29a70 100644 --- a/doc/USAGE.md +++ b/doc/USAGE.md @@ -168,6 +168,209 @@ prompt=$($OLLIE/u/optimize "translate this to Spanish" 2>/dev/null) cat document.txt | $OLLIE/s/bfg "$prompt" ``` +## Multi-agent workflows + +Two patterns cover most multi-agent use cases: ephemeral subagent delegation for +independent parallel subtasks, and persistent concurrent sessions for workflows that +need coordination or long-lived state. + +### Subagent delegation + +`subagent_spawn` is a tool script that forks N ephemeral agents with a shared prompt, +waits for all to finish, and returns their results concatenated in submission order. It +is the right tool when work can be split into independent subtasks that don't need to +talk to each other. + +The agent invokes it via `execute_code`: + +```json +{ + "steps": [{"tool": "subagent_spawn", "args": "-n 3 summarize this module"}] +} +``` + +Or from a shell step: + +```sh +subagent_spawn -n 3 "review this diff for security issues" +subagent_spawn -n 1 -agent reviewer -cwd /path/to/repo "check for style violations" +``` + +**Flags** + +| Flag | Description | +|---|---| +| `-n N` | Number of subagents to spawn (default: 1) | +| `-agent A` | Agent config for each subagent | +| `-backend B` | Backend override | +| `-model M` | Model override | +| `-cwd DIR` | Working directory for subagents (default: `pwd`) | +| `-keep` | Do not remove sessions after collecting results | + +With `-n 1` (the default) the result is just the single agent's output. With `-n > 1` +results are separated by `=== {id} ===` headers. + +### Concurrent sessions + +For workflows where multiple long-lived agents work in parallel and hand off to each +other, the mechanism is direct prompt passing via the filesystem. Each agent has a +`prompt` file that accepts writes; writing to it queues a new turn, so a busy agent +will not drop a message. + +**Create named sessions:** + +```sh +printf 'name=writer\ncwd=%s\n' "$PWD" > $OLLIE/s/new +printf 'name=reviewer\ncwd=%s\n' "$PWD" > $OLLIE/s/new +``` + +**Send a prompt and read the response:** + +```sh +# Queue a prompt on a named session +printf '%s\n' "Draft a design doc for the new cache layer." > $OLLIE/s/writer/prompt + +# Wait for the turn to finish (statewait blocks until state changes) +until [ "$(cat $OLLIE/s/writer/statewait)" = "idle" ]; do :; done + +# Read only the new output (offset marks the byte position after the user prompt) +tail -c +$(($(cat $OLLIE/s/writer/offset) + 1)) $OLLIE/s/writer/chat +``` + +**Key files per session:** + +| File | Mode | Description | +|---|---|---| +| `prompt` | write | Queue a new turn; writes are enqueued if agent is busy | +| `statewait` | read (blocking) | Blocks until state changes; returns new state | +| `state` | read | Current state: `idle`, `running`, `failed: ` | +| `chat` | read | Full conversation transcript | +| `offset` | read | Byte position in `chat` immediately after the last user prompt | + +Reading `offset` before writing `prompt`, then extracting `chat[offset:]` after the +turn completes, isolates exactly the model's response for that turn. + +### Predefined workflows + +When coordination logic is known in advance, a shell script can drive the entire +workflow. Named sessions make routing unambiguous; `statewait` eliminates polling +races. No external coordinator process is needed — the script is the coordinator. + +The following example implements a write → review → test loop. The developer agent +revises until the reviewer approves, then the tester validates: + +```sh +#!/bin/sh +cwd=${1:?usage: $0 } +cd $OLLIE + +printf 'name=developer\nagent=developer\ncwd=%s\n' "$cwd" > s/new +printf 'name=reviewer\nagent=reviewer\ncwd=%s\n' "$cwd" > s/new +printf 'name=tester\nagent=tester\ncwd=%s\n' "$cwd" > s/new + +# Send a prompt to a named session and return the model's response. +# Opens statewait before writing the prompt so the idle baseline is captured +# before the agent starts processing. offset marks the byte position after the +# user prompt in chat; everything from that position to EOF is the model response. +send() { + sid=$1; shift + exec 3< s/$sid/statewait + printf '%s' "$*" > s/$sid/prompt + while true; do + read -r _ <&3 && { exec 3<&-; break; } + exec 3<&- + [ "$(cat s/$sid/state)" != "idle" ] && break + exec 3< s/$sid/statewait + done + until [ "$(cat s/$sid/state)" = "idle" ]; do sleep 0.5; done + tail -c +$(($(cat s/$sid/offset) + 1)) s/$sid/chat +} + +reply=$(send developer "Implement a function that parses a JSON config file.") + +while true; do + review=$(send reviewer "Review the following code. Reply LGTM if ready, or PTAL with feedback. + +$reply") + + if ! printf '%s' "$review" | grep -qi "LGTM"; then + reply=$(send developer "Revise based on this feedback: + +$review") + continue + fi + + result=$(send tester "Test the following code. Reply Approved if all tests pass, or Rejected with details. + +$reply") + + if printf '%s' "$result" | grep -qi "Approved"; then + printf '%s\n' "$reply" + break + fi + + reply=$(send developer "Fix the following test failures: + +$result") +done + +rm -r s/developer s/reviewer s/tester +``` + +The agents have no knowledge of each other — the script holds all the routing logic. +A human can intervene at any time by writing directly to any session's `prompt`. +Each agent can use a different `agent=`, `backend=`, or `model=` — set them in the +`s/new` spec lines. + +### Self-generating workflows + +Because agents have access to `execute_code`, a session can write and run a workflow +script without any human involvement. Given a task description and knowledge of the +filesystem layout, an agent can decompose the work, create the named sessions, write +the coordination script to a temp file, and execute it — all in a single turn. + +The architecture documents (`ARCHITECTURE.md`, `USAGE.md`) and tool descriptions +(available via `t/idx`) are the agent's reference. No additional scaffolding is needed. + +## Store federation + +`OLLIE_TOOLS_PATH` and `OLLIE_TRANSCRIPT_PATH` are ordinary filesystem paths. Because +`olliesrv` speaks 9P and any remote instance can be mounted locally via `9pfuse`, these +vars can point at remote directories with no code changes — the OS handles the proxying +transparently. + +### Centralized tool distribution + +Host one authoritative tool set and point all machines at it: + +```sh +# On each client machine: +olliesrv mount toolserver:9564 ~/mnt/toolserver +export OLLIE_TOOLS_PATH=~/mnt/toolserver/t +``` + +Every agent on every host now uses the same tool scripts. Update tools in one place; +all clients pick up the change immediately. + +### Centralized transcript archive + +Collect transcripts from all hosts in one place: + +```sh +olliesrv mount archivehost:9564 ~/mnt/archive +export OLLIE_TRANSCRIPT_PATH=~/mnt/archive/tr +``` + +Sessions started after this change write their transcripts to the remote host. +Existing sessions are unaffected. + +Both vars can be combined. Set them in `~/.config/ollie/env` for persistence: + +``` +OLLIE_TOOLS_PATH=~/mnt/toolserver/t +OLLIE_TRANSCRIPT_PATH=~/mnt/archive/tr +``` + ## Alternative front-ends The 9P filesystem is the lowest level interface to ollie. All interfaces are simply wrappers around filesystem operations, and `s/sh` is the standard chat interface. A couple of other front-ends are also provided: