585 lines
17 KiB
Markdown
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`) |