ollie/doc/usage.md

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

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

olliesrv -mount /tmp/ollie

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

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


Examples below use 9p for interactive use.

One-shot generation

The generate file provides stateless prompt→response without creating a session:

echo 'summarize this repo' | ollie-9p rdwr generate
cat main.go | ollie-9p rdwr generate    # pipe content as the prompt
echo '{"prompt":"explain recursion"}' | ollie-9p rdwr generate  # JSON form

For programmatic use from any language, write a prompt to the generate file and read back the response.

Code completion

The complete file provides fill-in-the-middle code completion:

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

Routing

The route file selects the best backend+model for a task description:

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

Use this to dispatch work to the appropriate tier without hardcoding model names.

Raw session access

9p read session/new                           # show the spec template
printf 'cwd=%s\n---\nSummarize this.\n' "$PWD" | 9p write session/new
9p read session/<session-id>/cfg              # state, backend, model, agent, cwd, params
9p read session/<session-id>/agent/0/chat
9p rm session/<session-id>                    # cancel and remove

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

Generation parameters

Parameters are set in the agent config JSON and can be overridden at runtime by writing key=value lines to session/{id}/cfg. Runtime writes take precedence over the agent config. All are optional; omitted values use the backend's default.

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"]
}

Example runtime override:

echo "temperature=0.3" | 9p write session/mysession/agent/0/cfg
echo "topP=0.9" | 9p write session/mysession/agent/0/cfg

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.

AI pipelines

generate composes naturally with Unix pipes:

echo "write a haiku about filesystems" | ollie-9p rdwr generate | wc -w
cat error.log | ollie-9p rdwr generate    # content IS the prompt

Chain multiple generation steps:

echo "list 5 blog post ideas" | ollie-9p rdwr generate | head -3 | ollie-9p rdwr generate

Code completion

The complete file provides fill-in-the-middle code completion:

One copilot session is maintained per working directory. The session ID is derived deterministically from the cwd, so concurrent completions in the same directory share a single session and never spawn duplicates. The conversation history is cleared after each completion to keep context fresh.

If a session enters a failed state, it is automatically killed and recreated on the next invocation.

Model recommendations

Completion models should be small and fast — the goal is insertion text, not reasoning. Good choices for OLLIE_COMPLETE_MODEL:

Model Backend Notes
qwen3:4b ollama Use with /no_think or a PARAMETER stop <think> Modelfile to disable reasoning
phi-4-mini ollama ~3.8B, strong at code, no thinking mode
llama3.1:8b ollama Straightforward, no chain-of-thought overhead
openai/gpt-4o-mini openai Fast and cheap via OpenRouter

Avoid "thinking" models (Qwen3 8B+ with default settings, DeepSeek-R1) — they burn tokens on internal reasoning chains that add latency without improving completion quality.

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

{
  "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"   | 9p write session/new
printf 'name=reviewer\ncwd=%s\n' "$PWD" | 9p write session/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." | 9p write session/writer/agent/0/prompt

# Wait for the turn to finish (statewait blocks until state changes)
until [ "$(9p read session/writer/agent/0/statewait)" = "idle" ]; do :; done

# Read only the new output (offset marks the byte position after the user prompt)
offset=$(9p read session/writer/agent/0/offset)
9p read session/writer/agent/0/chat | tail -c +$((offset + 1))

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 history
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>}

printf 'name=developer\nagent=developer\ncwd=%s\n' "$cwd" | 9p write session/new
printf 'name=reviewer\nagent=reviewer\ncwd=%s\n'  "$cwd" | 9p write session/new
printf 'name=tester\nagent=tester\ncwd=%s\n'      "$cwd" | 9p write session/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< session/$sid/agent/0/statewait
    printf '%s' "$*" > session/$sid/agent/0/prompt
    while true; do
        read -r _ <&3 && { exec 3<&-; break; }
        exec 3<&-
        [ "$(cat session/$sid/agent/0/state)" != "idle" ] && break
        exec 3< session/$sid/agent/0/statewait
    done
    until [ "$(cat session/$sid/agent/0/state)" = "idle" ]; do sleep 0.5; done
    tail -c +$(($(cat session/$sid/agent/0/offset) + 1)) session/$sid/agent/0/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 session/developer session/reviewer session/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 session/new spec lines.

Self-generating workflows

Because agents have access to shell, 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 $OLLIE_TOOLS_PATH) are the agent's reference. No additional scaffolding is needed.

Store federation

OLLIE_TOOLS_PATH is an ordinary filesystem path. Because olliesrv speaks 9P and any remote instance can be mounted locally via 9pfuse, 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:
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.

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

OLLIE_TOOLS_PATH=~/mnt/toolserver/t
  • Markdown rendering of assistant responses
  • 9P filesystem browser (navigate sessions, agents, tools, skills)
  • Two built-in themes: midnight (dark) and acme (Plan 9-inspired light); choice persisted to localStorage

System prompt override

The default system prompt is compiled into olliesrv. To replace it — for example, when using a model that needs its own identity preamble — 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
}

The file is read at session creation time. If it doesn't exist, the embedded default is used. The override replaces only the base system prompt layer; the operational model (9P documentation), environment block, and agent-specific prompt commands are still appended on top.

Model-specific prompts live at ~/.config/ollie/prompts/SYSTEM_PROMPT_*.md. Create an agent config per model family and point systemPrompt at the appropriate file.

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.\n\n**Args**: ...",
  "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; overrides co-located binary (see WRITING_TOOLS.md)
sudo Requires root privileges; implies elevation
variants Array of conditional definitions for heterogeneous hosts (see WRITING_TOOLS.md)

Runtime access

The full prompt for any tool is available on-demand via the /tools endpoint:

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

The system prompt only includes names and one-line descriptions to save context. Agents query /tools when they need detailed usage for a specific tool.

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.

Host-conditional variants

Tools can declare multiple variants gated by match conditions. The first variant where all conditions pass determines the tool's schema, documentation, and executable. If no variant matches, the tool is hidden from the registry.

{
  "description": "Read system logs.",
  "variants": [
    {
      "match": {"binary": "journalctl"},
      "cmd": "system_logs_journald",
      "args": {"type":"object","properties":{"unit":{"type":"string"},"since":{"type":"string"}}}
    },
    {
      "match": {"file": "/var/log/syslog"},
      "cmd": "system_logs_syslog",
      "args": {"type":"object","properties":{"lines":{"type":"string"}}}
    }
  ]
}

Supported match keys: binary, file, os, arch, env, nenv. All conditions in a match must be true (AND). Values can be a string or array (OR). See WRITING_TOOLS.md for the full specification.

Privileged tools: sudo

Tools that need root privileges declare "sudo": true in their .meta. The dispatch chain becomes a two-gate sequence: elevation approval (escape sandbox) → credential prompt → sudo -S execution. The tool itself has no knowledge of sudo — the privilege wrapping is entirely in the dispatch layer.

{
  "description": "Read system logs (dmesg).",
  "sudo": true,
  "cmd": "system_logs_dmesg",
  "args": {"type":"object","properties":{"lines":{"type":"string"}}},
  "readOnly": true
}

Credentials are prompted via kdialog/zenity on desktop, or forwarded over SSH for remote execution. See WRITING_TOOLS.md for details.

Alternative front-ends

The 9P filesystem is the lowest level interface to ollie. All interfaces are wrappers around filesystem operations. Front-ends:

  • ellie — ellie.el is the Emacs front-end, just wire it into your init.el and M-x ellie
  • KDE — plasmoid, standalone GUI, Kate plugin, KRunner, system tray (see kde/)
  • o — terminal CLI and tmux TUI (see below)

See each repo's README for installation and usage.


o — Terminal CLI

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

Quick start

# Set context first:
export session=myproject agent=default
# or use the env helper:
eval $(o env myproject default)

# Then interact:
o tail          # stream chat output
o prompt        # interactive prompt REPL
o stop          # interrupt the agent
o kill          # kill the session

Commands

Command Description
o read <path> Read a file from the namespace
o write <path> [data] Write to a file (args or stdin)
o readloop <path> Read in a loop (re-reads after 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 prompt Interactive multi-line prompt REPL
o tail Stream chat output
o tui Launch tmux TUI (see below)
o env [session] [agent] Print export statements for context

Path resolution

Paths are resolved based on context ($session, $agent):

  • Root paths (session/*, agents, models, help, ctl) → used as-is
  • Agent files (chat, state, prompt, etc.) → require $session + $agent
  • Session files (env, ctl, name, etc.) → require $session only
o read models                  # root-level, no context needed
o ls session                   # list all sessions
export session=foo agent=bar
o read chat                    # → session/foo/agent/bar/chat
o readloop statewait           # blocks until agent state changes

Prompt REPL slash commands

Inside o prompt, slash commands control the agent without sending a prompt:

/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
/help       — list commands

Multi-line input: type lines, press Enter on a blank line to send. Ctrl+D exits.

Environment

Variable Purpose
session Active session name
agent Active agent name

Context must be set explicitly via export or eval $(o env ...). Commands that require context will fail fast with a clear error message if not set.


o tui — tmux Terminal UI

o tui composes a complete agent interaction environment from four tmux panes, each running a blocking 9P read or a REPL. The layout exploits ollie's streaming file semantics: chat blocks until new tokens arrive, statewait blocks until the agent state changes.

Layout

┌─────────────────────────────┬───────────────┐
│                             │  statewait    │
│   chat stream               │  (reactive —  │
│   (tokens appear live)      │   updates on  │
│                             │   state       │
│                             │   change)     │
│                             ├───────────────┤
│                             │  $ shell      │
│                             │  (session +   │
│                             │   agent       │
│                             │   exported)   │
├─────────────────────────────┴───────────────┤
│  > prompt REPL (multi-line, /commands)      │
└─────────────────────────────────────────────┘

How it works

export session=myproject agent=default
o tui
  1. Requires $session and $agent to be set (fails with error if missing).
  2. Creates a tmux session named o-<session>.
  3. Splits into 4 panes, each with session and agent exported:
    • Pane 0 (left, large): o tail — blocking read on chat, streams tokens as they arrive.
    • Pane 1 (top-right): o readloop statewait — blocks until agent state changes (idle → thinking → calling: tool → idle), then prints and blocks again.
    • Pane 2 (bottom-right): bare shell with context set — run o stop, o read cost, o ctl compact, etc.
    • Pane 3 (bottom, full-width): o prompt — multi-line REPL with slash commands.
  4. Focuses the prompt pane and attaches.

Key insight

No polling. Each pane performs a blocking read on a 9P file that only returns data when something changes. The server wakes the reader (via sync.Cond) when new tokens/state arrive. This gives reactive, instant updates with zero CPU overhead when idle.

tmux tips within o tui

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