ollie/doc/USAGE.md

17 KiB

Usage

Server

Start and stop

olliesrv start          # start in background (daemonizes)
olliesrv fgstart        # start in foreground
olliesrv stop           # stop the daemon
olliesrv status         # check if running

By default the server mounts at $HOME/mnt/ollie. Override with -mount:

olliesrv start -mount /tmp/ollie

TCP listener

To also accept connections over TCP (e.g. for remote access):

olliesrv start -tcp :9564

Debug logging

Log level is controlled by OLLIE_LOG (default: warn). Set to debug for verbose output:

OLLIE_LOG=debug olliesrv fgstart

Valid levels: debug, info, warn, error. Per-subsystem overrides follow the pattern OLLIE_<TAG>_LOG=debug where <TAG> matches the logger's tag (e.g. OLLIE_SESSION_LOG).


Examples below assume $OLLIE is set to the ollie mount point (default: $HOME/mnt/ollie). Scripts under s/ and u/ are available directly via the mounted filesystem after running mk && mk scripts.

Interactive shell

s/sh is an interactive shell for prompting a session. It resumes the most recent session for the current working directory by default, or creates a new one if none exists.

exec $OLLIE/s/sh                                       # resume or create session for pwd
exec $OLLIE/s/sh -new                                  # always start a fresh session
exec $OLLIE/s/sh -new -model qwen3:8b                  # fresh session with model override
exec $OLLIE/s/sh -session 1744276689123456789-2b986c   # attach to a specific session

At the > prompt:

Input Action
<text> Submit prompt; response streams to terminal
/q <text> Queue prompt for later execution (writes to fifo.in)
/<cmd> Send to session ctl (e.g. /compact, /clear, /model qwen3:8b)
/kill Kill the session and exit
Ctrl-C Interrupt a running turn (or exit if idle)

s/sh tracks the last session per working directory in ~/.config/ollie/last-session, so switching directories and re-running s/sh resumes the right context automatically.

One-shot queries

s/bfg and s/bbg submit a single prompt to a new session. The difference is only in how the caller is treated: s/bfg blocks until the agent finishes and writes the result to stdout; s/bbg returns immediately and prints the path of the session.

Both are thin wrappers around s/b, which is the core one-shot runner.

s/bfg

Submit a prompt, wait for completion, print the result:

$OLLIE/s/bfg "Summarize this repo"
cat main.go | $OLLIE/s/bfg "explain this file"
$OLLIE/s/bfg -parallel 4 -keep "Write a haiku"
$OLLIE/s/bfg -backend ollama -model qwen3:8b "Explain this" < main.go

With -parallel N, N jobs run concurrently and results are printed as they complete.

s/bbg

Submit a background job and return immediately; prints the s/{id} path:

$OLLIE/s/bbg "summarize the recent git log"
# /home/lkn/mnt/ollie/s/1744276689123456789-0

Poll for completion:

path=$($OLLIE/s/bbg "write a limerick about Go")
until [ "$(cat $path/statewait)" = "idle" ]; do :; done
tail -c +$(($(cat $path/offset) + 1)) $path/chat

Fan out with -parallel N and collect results:

paths=$($OLLIE/s/bbg -parallel 4 "draft a blog intro" < brief.txt)
for p in $paths; do
    until [ "$(cat $p/statewait 2>/dev/null)" = "idle" ]; do :; done
    tail -c +$(($(cat $p/offset) + 1)) $p/chat
    rm -r $p
done

s/cleanup

Remove all idle sessions:

$OLLIE/s/cleanup

Schedule regular cleanup with a cron job:

*/15 * * * * /path/to/mnt/ollie/s/cleanup

Raw s/ access

cat $OLLIE/s/new                           # show the spec template
printf 'cwd=%s\n---\nSummarize this.\n' "$PWD" > $OLLIE/s/new
cat $OLLIE/s/<session-id>/state            # running | idle | failed: <reason>
cat $OLLIE/s/<session-id>/chat
rm -r $OLLIE/s/<session-id>               # cancel and remove

Valid spec keys: name (auto-generated if omitted), cwd (required), agent, backend, model.

AI pipelines

s/bfg writes to stdout and composes naturally with Unix pipes. When both args and stdin are present, args are treated as the instruction and stdin as the content:

echo "write a haiku about filesystems" | $OLLIE/s/bfg | wc -w
cat error.log | $OLLIE/s/bfg "what is causing this?" | $OLLIE/s/bfg "suggest a fix"
$OLLIE/s/bfg "list 5 blog post ideas" | grep -v "^$" | head -3 | $OLLIE/s/bfg "expand the best one"

u/optimize is a worked example — it generates N candidate prompts in parallel via s/bfg -parallel N, then judges them with a second s/bfg call to return the best:

$OLLIE/u/optimize -n 3 -cm qwen/qwen3-8b -jm anthropic/claude-opus-4-6 "explain recursion"

Progress goes to stderr; pipe the optimized prompt with content into a subsequent call:

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:

{
  "steps": [{"tool": "subagent_spawn", "args": "-n 3 summarize this module"}]
}

Or from a shell step:

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:

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:

# 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: <reason>
chat read Full conversation transcript
context read Full message history as JSONL
offset read Byte position in chat immediately after the last user prompt
plan r/w Scratch space for agent planning
prompt.prev read The last submitted prompt
env read Session environment variables
cost read Cumulative cost in USD (if reported by backend)

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:

#!/bin/sh
cwd=${1:?usage: $0 <cwd>}
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:

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

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

HTTP gateway

ollie-httpgw is an HTTP gateway that translates REST-style HTTP calls into 9P filesystem operations. It lets any HTTP client — including the web UI, curl, or custom scripts — interact with olliesrv without a 9P client library.

Build

mk httpgw          # from repo root
# or
cd httpgw && mk

Produces ~/bin/ollie-httpgw.

Start and stop

Like olliesrv, the gateway supports daemon management:

ollie-httpgw start          # start in background (daemonizes)
ollie-httpgw fgstart        # start in foreground
ollie-httpgw stop           # stop the daemon
ollie-httpgw status         # check if running

Run (legacy)

For backwards compatibility, running without a subcommand starts in foreground:

ollie-httpgw                           # connect to local olliesrv via namespace; listen on :8080
ollie-httpgw -listen :9090             # different port
ollie-httpgw -net tcp -addr host:9564  # connect to remote olliesrv over TCP

Flags

Flag Default Description
-listen :8080 HTTP listen address
-service ollie 9P service name in the local namespace
-net `` Network type (tcp, unix); empty = use namespace
-addr `` 9P server address; required when -net is set

The gateway must be able to reach olliesrv. For a local server started with olliesrv start, the defaults work as-is. For a remote server started with olliesrv start -tcp :9564, use -net tcp -addr remotehost:9564.

HTTP → 9P mapping

Each HTTP method maps to a 9P operation:

Method 9P operation Example
GET read file / list dir GET /s/idx
POST write to file POST /s/new, POST /s/{id}/prompt
DELETE remove DELETE /s/{id}
PATCH rename (JSON {"name":"..."}) PATCH /s/{id}
HEAD stat (metadata in X-9p-* headers) HEAD /s/{id}

The full API is described in httpgw/openapi.json and served live at GET /openapi.json.

Key endpoints

GET  /s/idx                  # list sessions (JSON array)
GET  /s/{id}/cfg             # session config (key-value JSON)
GET  /s/{id}/chat            # conversation transcript (plain text)
POST /s/new                  # create session (JSON: {cwd, backend, model, agent})
POST /s/{id}/prompt          # submit prompt (JSON: {prompt: "..."})
POST /s/{id}/ctl             # send control command (JSON: {ctl: "stop"})
DEL  /s/{id}                 # kill and remove session
GET  /backends               # list available backends
GET  /a/                     # agent configs
GET  /t/                     # tool scripts
GET  /m/                     # memory files
GET  /sk/                    # skills
# Examples with curl:
curl http://localhost:8080/s/idx
curl -X POST http://localhost:8080/s/new \
     -d '{"cwd":"/home/user/project","backend":"anthropic"}'
curl -X POST http://localhost:8080/s/mysession/prompt \
     -d '{"prompt":"summarize the recent commits"}'

Web UI

The web UI is a Preact single-page application that talks to ollie-httpgw. It provides session management, a chat interface, and a basic 9P filesystem browser in the browser.

Build

mk webui           # from repo root
# or
cd webui && npm run build

Requires Node.js and npm. Output lands in webui/dist/.

Serve

The dist/ folder is a static site. Serve it with any HTTP server and proxy the API paths to ollie-httpgw:

nginx example:

server {
    listen 3000;
    root /path/to/ollie/webui/dist;
    index index.html;

    location / { try_files $uri /index.html; }

    location ~ ^/(s|a|m|t|sk|backends|openapi\.json)(/|$) {
        proxy_pass http://localhost:8080;
    }
}

Python one-liner (local testing only):

cd webui/dist && python3 -m http.server 3000
# API calls will fail — use the Vite dev server instead for local testing

Development

Vite's dev server proxies API paths to httpgw automatically:

# Terminal 1: gateway
ollie-httpgw -listen :8080

# Terminal 2: dev server (hot-reload, proxied API)
cd webui && npm run dev
# Open http://localhost:5173

The proxy configuration is in webui/vite.config.ts and targets localhost:8080 by default.

Features

  • Session list in a sidebar with state, model, backend, and cwd
  • Create, rename, and kill sessions
  • Send prompts; streaming via 2-second poll
  • Stop a running agent
  • Markdown rendering of assistant responses
  • 9P filesystem browser (navigate s/, a/, t/, m/, sk/)
  • Two built-in themes: midnight (dark) and acme (Plan 9-inspired light); choice persisted to localStorage

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. Other front-ends:

  • ellie — ellie.el is the Emacs front-end, just wire it into your init.el and M-x ellie
  • Web UI — browser-based UI; requires ollie-httpgw (see above)

See each repo's README for installation and usage.