# Usage ## Server ### Start and stop ```sh 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): ```sh olliesrv -tcp :9564 ``` ### Debug logging Log level is controlled by `OLLIE_LOG` (default: `warn`). Set to `debug` for verbose output: ```sh OLLIE_LOG=debug olliesrv ``` Valid levels: `debug`, `info`, `warn`, `error`. Per-subsystem overrides follow the pattern `OLLIE__LOG=debug` where `` 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 ```sh eval $(o env myproject default) # set session + agent context o prompt # interactive prompt REPL ``` In another terminal: ```sh 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 ` | Read any file in the namespace | | `o write [data]` | Write to any file | | `o readloop ` | Read in a loop (re-reads after each return) | | `o ls [path]` | List namespace entries | | `o ctl ` | 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: ```sh 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: ```sh 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: ```sh 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 ```sh eval $(o env myproject default) o tui ``` 1. Requires `$session` and `$agent` to be set. 2. Creates a tmux session named `o-`. 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-`) --- ## 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: ```sh 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: ```json { "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/.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: ```json { "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: ```sh 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: ` | | `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`: ```sh #!/bin/sh cwd=${1:?usage: $0 } # 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 ```sh ollie-9p mount unix!/tmp/ns.$USER.$DISPLAY/ollie ~/mnt/ollie cd ~/mnt/ollie ls session/ ``` Or connect remotely: ```sh ollie-9p mount tcp!server:9564 ~/mnt/ollie ``` ### Raw session operations ```sh # 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: ```sh # 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: ```sh # 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 ```sh 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: ```sh echo '{"file":"main.go","prefix":"func ","suffix":""}' | ollie-9p rdwr complete ``` ### `/route` — task routing ```sh 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: ```sh # 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: ```json { "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: ```sh 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](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: ```json { "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](../el/)** — Emacs front-end (`ellie.el`) - **KDE** — plasmoid, standalone GUI, Kate plugin, KRunner, system tray - **Web** — built-in HTTP server (see `olliesrv -web`)