ollie/doc/usage.md

585 lines
17 KiB
Markdown

# 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_<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
```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 <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:
```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-<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:
```sh
echo "temperature=0.3" | ollie-9p write session/mysession/agent/0/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/<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:
```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
printf 'name=writer\ncwd=%s\n' "$PWD" | o write session/new
printf 'name=reviewer\ncwd=%s\n' "$PWD" | o write session/new
o write prompt "Draft a design doc" # with session=writer agent=0
o tail # 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`:
```sh
#!/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() {
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.
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=worker" > session/new
# Submit a prompt
echo "fix the bug in main.go" > session/worker/agent/0/prompt
# Stream the response
cat session/worker/agent/0/chat
# Check state
cat session/worker/agent/0/state
# Block until state changes
cat session/worker/agent/0/statewait
# Read offset (byte position after user prompt)
cat session/worker/agent/0/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/worker/agent/0/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/{id}/agent/{aid}/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/{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.
## 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`)