ollie/doc/usage.md

18 KiB

Usage

Server

Start and stop

olliesrv                # start the server (foreground)
# Stop by sending SIGTERM or via the supervisor (e.g. rc-service ollie stop)

TCP listener

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

olliesrv -tcp :9564

Debug logging

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

OLLIE_LOG=debug olliesrv

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


o — Terminal CLI

o is a multi-call shell script that wraps the 9P namespace into a human-friendly interface. Installed to ~/bin/o by just install-data.

Quick start

eval $(o env myproject default)   # set session + agent context
o prompt                           # interactive prompt REPL

In another terminal:

eval $(o env myproject default)
o tail    # stream chat output

Commands

Command Description
o prompt Interactive multi-line prompt REPL
o tail Stream chat output
o read <path> Read any file in the namespace
o write <path> [data] Write to any file
o readloop <path> Read in a loop (re-reads after each return)
o ls [path] List namespace entries
o ctl <cmd> Send raw control command to agent
o stop Interrupt the running agent
o kill Kill the active session
o tui Launch tmux TUI
o env [session] [agent] Print export statements for context

Context

Context is set via $session and $agent environment variables:

eval $(o env myproject default)   # export session=myproject agent=default

# Now every o command addresses the right agent automatically:
o read state       # → session/myproject/agent/default/state
o readloop statewait

Path resolution

Paths are slash-delimited 9P namespace paths. The o CLI resolves them based on context:

Path type Example Resolves to
Root-level o read models models (no context needed)
Agent file o read state session/$session/agent/$agent/state
Session file o read env session/$session/env
Full path o read session/foo/agent/bar/chat Used as-is

Root-level paths (session/, agents, models, help, ctl, tools) and absolute paths (starting with session/) bypass context resolution entirely. Agent-level files (chat, state, prompt, statewait, cfg, plan, log, cost, offset, context, systemprompt) require both $session and $agent. Session-level files (env, ctl, id, name, paused) require only $session.

Prompt REPL

o prompt is an interactive multi-line REPL with readline editing:

$ o prompt
[myproject/default]
Type . on a blank line to send, Ctrl+D to exit.
Slash commands: /stop /compact /kill /clear /model /backend /cwd /help

> write a fibonacci function in python
  and include a test case
  .

Type your prompt across multiple lines, then send with . on its own line. Slash commands control the agent without sending a prompt:

Command Description
/stop Interrupt running agent
/compact Compact context window
/kill Kill the session (exits REPL)
/clear Clear history
/model X Switch model
/backend X Switch backend
/cwd X Change working directory
/quit or /q Kill the tmux TUI
/help List commands

One-shot generation

No session or agent needed — o generate sends a prompt and returns a response:

o generate "explain monads in one sentence"
cat error.log | o generate   # pipe content as the prompt
echo "write a haiku about filesystems" | o generate | wc -w

Chain multiple steps:

echo "list 5 blog post ideas" | o generate | head -3 | o generate

o tui — tmux Terminal UI

o tui composes o tail and o prompt into a single tmux layout — two shell commands in two panes, with agent state in the tmux status bar:

┌──────────────────────────────────┐
│  o tail | grep                    │  ← chat stream
│                                   │
├──────────────────────────────────┤
│  o prompt                         │  ← input REPL
└──────────────────────────────────┘
Agent state shown in tmux status bar
  idle=green  calling=orange  thinking=blue  paused=gray

Usage

eval $(o env myproject default)
o tui
  1. Requires $session and $agent to be set.
  2. Creates a tmux session named o-<session>.
  3. Two panes:
    • Pane 0 (top, 80%): o tail | grep — streams chat output, filters block markers.
    • Pane 1 (bottom, 20%): o prompt — multi-line REPL with slash commands.
  4. A background loop reads statewait and updates the tmux status bar instantly.
  5. Focuses the prompt pane and attaches.

No polling. Each read blocks until data arrives. The status bar updates on every state transition via statewait — zero CPU when idle.

tmux tips

  • Ctrl+b ; — toggle between last two panes (fast prompt ↔ tail switch)
  • Ctrl+b z — zoom any pane to full screen
  • Ctrl+b [ — scroll mode (arrow keys, q to exit)
  • Ctrl+b d — detach (session keeps running, reattach with tmux attach -t o-<session>)

Generation parameters

Parameters are set in the agent config JSON and can be overridden at runtime by writing key=value to the agent config. Runtime overrides take precedence:

echo "temperature=0.3" | ollie-9p write session/mysession/agent/{aname}/cfg
Key Type Description
maxTokens int Max output tokens (0 = no limit)
maxCompletionTokens int OpenAI o-series alternative to maxTokens
temperature float Sampling temperature
topP float Nucleus sampling threshold
topK int Top-K sampling (Anthropic, Ollama)
minP float Min-P sampling (Ollama)
topA float Top-A sampling (Ollama)
frequencyPenalty float Frequency penalty
presencePenalty float Presence penalty
repetitionPenalty float Repetition penalty (Ollama)
reasoning int Thinking budget in tokens (Anthropic); 0 = disabled
reasoningEffort string low, medium, high (OpenAI o-series)
includeReasoning bool Include reasoning in response (OpenRouter)
responseFormat string json_object, text, etc. (OpenAI)
stop string Comma-separated stop sequences
verbosity string Output detail level (internal, not sent to API)

Example agent config:

{
  "prompt": "You are a helpful assistant.",
  "temperature": 0.7,
  "topP": 0.95,
  "reasoning": 10000,
  "stop": ["\n\n"]
}

Sandbox

Every shell invocation (and promoted tool call) runs inside a Landlock sandbox configured by ~/.config/ollie/sandbox/<name>.yaml. The sandbox wraps each invocation as a new command, so config changes (e.g. granting access to an additional directory) take effect on the next call without restarting the server or session.

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 forks N ephemeral agents with a shared prompt, waits for all to finish, and returns their results concatenated. Use it when work splits into independent subtasks:

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

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

Concurrent sessions

For long-lived parallel agents, create named sessions and pass prompts between them via the filesystem:

o write session/new "name=writer cwd=$PWD"
o write session/new "name=reviewer cwd=$PWD"
agent=$(o ls session/writer/agent | grep -v '^new$')
o write "session/writer/agent/$agent/prompt" "Draft a design doc"
o read "session/writer/agent/$agent/chat"       # read writer's response

Key files per agent:

File Mode Description
prompt write Queue a new turn; 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 history
context read Full message history as JSONL
offset read Byte position in chat 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

Predefined workflows

When coordination logic is known in advance, a shell script can drive the entire workflow using named sessions and statewait:

#!/bin/sh
cwd=${1:?usage: $0 <cwd>}

# Create named sessions with agent profiles
o write session/new "name=developer agent=developer cwd=$cwd"
o write session/new "name=reviewer  agent=reviewer  cwd=$cwd"
o write session/new "name=tester    agent=tester    cwd=$cwd"

# Discover agent names (first agent in each session)
agent_developer=$(o ls session/developer/agent | grep -v '^new$')
agent_reviewer=$(o ls session/reviewer/agent | grep -v '^new$')
agent_tester=$(o ls session/tester/agent | grep -v '^new$')

send() {
    sid=$1; shift
    aname=$1; shift
    # Open statewait, send prompt, wait for completion, read response
    exec 3< <(o readloop "session/$sid/agent/$aname/statewait" &)
    sleep 0.1
    o write "session/$sid/agent/$aname/prompt" "$*"
    until o read "session/$sid/agent/$aname/state" | grep -q idle; do sleep 0.5; done
    exec 3<&-
    offset=$(o read "session/$sid/agent/$aname/offset" | tr -d '[:space:]')
    o read "session/$sid/agent/$aname/chat" | tail -c +$((offset + 1))
}

reply=$(send developer "$agent_developer" "Implement a function that parses a JSON config file.")

while true; do
    review=$(send reviewer "$agent_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 "$agent_developer" "Revise based on this feedback:

$review")
        continue
    fi

    result=$(send tester "$agent_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 "$agent_developer" "Fix the following test failures:

$result")
done

o kill developer
o kill reviewer
o kill tester

The agents have no knowledge of each other — the script holds all the routing logic. Each agent can use a different agent=, backend=, or model=.


9P filesystem reference

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 knowing the raw filesystem is useful for scripting, debugging, and understanding how things work.

Mounting

ollie-9p mount unix!/tmp/ns.$USER.$DISPLAY/ollie ~/mnt/ollie
cd ~/mnt/ollie
ls session/

Or connect remotely:

ollie-9p mount tcp!server:9564 ~/mnt/ollie

Raw session operations

# Create a session
echo "name={sname}" > session/new

# Submit a prompt
echo "fix the bug in main.go" > session/{sname}/agent/{aname}/prompt

# Stream the response
cat session/{sname}/agent/{aname}/chat

# Check state
cat session/{sname}/agent/{aname}/state

# Block until state changes
cat session/{sname}/agent/{aname}/statewait

# Read offset (byte position after user prompt)
cat session/{sname}/agent/{aname}/offset

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:

# From another machine:
9p -a 'tcp!server:5640' read session/{sname}/agent/{aname}/state

No SSH tunneling, no port forwarding, no API gateway.

Shell composability

Because operations are file I/O, they compose with the full Unix toolkit:

# 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

# Grep all agent plans
grep -r "TODO" session/*/plan

# Monitor costs
cat session/*/agent/*/cost

# Strip block markup from chat output
cat session/{sname}/agent/{aname}/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 — /complete, /generate, /route 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 blocks until state changes, replacing event subscriptions with a simple cat
  • Multiplexing — multiple clients mount the same server simultaneously, each with an independent view through per-fid state

/generate — one-shot LLM

echo 'summarize this repo' | ollie-9p rdwr generate
cat main.go | ollie-9p rdwr generate
echo '{"prompt":"explain recursion"}' | ollie-9p rdwr generate

/complete — code completion

Fill-in-the-middle:

echo '{"file":"main.go","prefix":"func ","suffix":""}' | ollie-9p rdwr complete

/route — task routing

echo 'implement login page' | ollie-9p rdwr route
# backend=anthropic model=claude-sonnet-4-20250514

Key files per agent

File Mode Description
prompt write Queue a new turn
statewait read (blocking) Blocks until state changes
state read Current state
chat read Full conversation history
context read Full message history as JSONL
offset read Byte position after 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

Store federation

OLLIE_TOOLS_PATH is an ordinary filesystem path. Because the server speaks 9P and any remote instance can be mounted locally, this var can point at a remote directory 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:
ollie-9p mount toolserver:9564 ~/mnt/toolserver
export OLLIE_TOOLS_PATH=~/mnt/toolserver/t

Set it in ~/.config/ollie/env for persistence:

OLLIE_TOOLS_PATH=~/mnt/toolserver/t

Tool discovery

Tools are executables in $OLLIE_TOOLS_PATH (default: ~/.config/ollie/tools/). At session startup, all .meta sidecar files are scanned and a compact listing (name + one-line description) is injected into the system prompt.

Metadata format

Each tool has a .meta JSON sidecar file alongside the executable:

{
  "description": "Short description (used in system prompt listing).",
  "prompt": "## my_tool\n\nFull documentation shown when tool is loaded.",
  "args": {"type":"object","required":["path"],"properties":{"path":{"type":"string","description":"File path"}}},
  "tier": "cold",
  "readOnly": true
}
Field Purpose
description One-liner shown in tool listing
prompt Full documentation injected when tool is loaded
args JSON Schema for tool input parameters
tier Result cacheability: cold, warm, or hot (default: hot)
readOnly Safe for parallel execution with other read tools
cmd Executable path or name override
sudo Requires root privileges; implies elevation
variants Conditional definitions for heterogeneous hosts

Runtime access

The full prompt for any tool is available on-demand:

echo 'file_edit' | 9p rdwr ollie/tools   # returns file_edit's full documentation

Adding a tool

Drop an executable and its .meta file in $OLLIE_TOOLS_PATH. The next session created will pick it up automatically. No restart required.

See doc/writing-tools.md for the full specification including host-conditional variants and privileged tools.


System prompt override

The default system prompt is compiled into olliesrv. To replace it, set the systemPrompt field in an agent config JSON to a file path:

{
  "systemPrompt": "~/.config/ollie/prompts/SYSTEM_PROMPT_QWEN.md",
  "prompt": ["$OLLIE_CFG_PATH/prompts/agent-coding.md"],
  "temperature": 0.7
}

Viewing model context

The rendered context window for any session is readable at session/{sname}/agent/{aname}/context — the exact message array sent to the backend on the last turn. Combined with session/{sname}/agent/{aname}/systemprompt, this gives full visibility into what the model sees.

Alternative front-ends

  • ellie — Emacs front-end (ellie.el)
  • KDE — plasmoid, standalone GUI, Kate plugin, KRunner, system tray
  • Web — built-in HTTP server (see olliesrv -web)