usage: rewrite for o CLI, move raw 9P to reference section

This commit is contained in:
Ollie Agent 2026-08-04 19:06:29 +02:00
parent d314c7d4ee
commit 63a9f0b1d6
1 changed files with 309 additions and 381 deletions

View File

@ -9,14 +9,6 @@ olliesrv # start the server (foreground)
# Stop by sending SIGTERM or via the supervisor (e.g. rc-service ollie stop)
```
Mount the 9P namespace locally:
```sh
ollie-9p mount unix!/tmp/ns.$USER.$DISPLAY/ollie ~/mnt/ollie
cd ~/mnt/ollie
ls session/
```
### TCP listener
To also accept connections over TCP (e.g. for remote access):
@ -36,62 +28,154 @@ 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.
---
## `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
```
### 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
```
---
Examples below use `9p` for interactive use.
## `o tui` — tmux Terminal UI
## One-shot generation
`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:
The `generate` file provides stateless prompt→response without creating a session:
```sh
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
```
┌──────────────────────────────────┐
│ o tail | grep │ ← chat stream
│ │
├──────────────────────────────────┤
│ o prompt │ ← input REPL
└──────────────────────────────────┘
Agent state shown in tmux status bar
idle=green calling=orange thinking=blue paused=gray
```
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:
### Usage
```sh
echo '{"file":"main.go","prefix":"func ","suffix":""}' | ollie-9p rdwr complete
eval $(o env myproject default)
o tui
```
## Routing
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.
The `route` file selects the best backend+model for a task description:
No polling. Each read blocks until data arrives. The status bar updates on every
state transition via `statewait` — zero CPU when idle.
```sh
echo 'implement login page' | ollie-9p rdwr route
# backend=anthropic model=claude-sonnet-4-20250514
```
### tmux tips
Use this to dispatch work to the appropriate tier without hardcoding model names.
- `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>`)
### Raw session access
---
```sh
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
## 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.
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 |
|---|---|---|
@ -124,74 +208,25 @@ Example agent config:
}
```
Example runtime override:
## Sandbox
```sh
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:
```sh
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:
```sh
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.
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.
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`:
`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
{
@ -199,13 +234,6 @@ The agent invokes it via `shell`:
}
```
Or from a shell step:
```sh
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 |
@ -217,63 +245,37 @@ subagent_spawn -n 1 -agent reviewer -cwd /path/to/repo "check for style violatio
| `-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:**
For long-lived parallel agents, create named sessions and pass prompts between
them via the filesystem:
```sh
printf 'name=writer\ncwd=%s\n' "$PWD" | 9p write session/new
printf 'name=reviewer\ncwd=%s\n' "$PWD" | 9p write session/new
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
```
**Send a prompt and read the response:**
```sh
# 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:**
Key files per agent:
| File | Mode | Description |
|---|---|---|
| `prompt` | write | Queue a new turn; writes are enqueued if agent is busy |
| `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` immediately after the last user prompt |
| `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 (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.
| `cost` | read | Cumulative cost in USD |
### 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:
workflow using named sessions and `statewait`:
```sh
#!/bin/sh
@ -283,10 +285,6 @@ 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
@ -333,26 +331,145 @@ 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.
Each agent can use a different `agent=`, `backend=`, or `model=`.
### 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.
## 9P filesystem reference
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.
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
`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.
`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
@ -364,42 +481,13 @@ ollie-9p 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:
```json
{
"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
@ -415,7 +503,7 @@ 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.\n\n**Args**: ...",
"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
@ -429,210 +517,50 @@ Each tool has a `.meta` JSON sidecar file alongside the executable:
| `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) |
| `cmd` | Executable path or name override |
| `sudo` | Requires root privileges; implies elevation |
| `variants` | Array of conditional definitions for heterogeneous hosts (see WRITING_TOOLS.md) |
| `variants` | Conditional definitions for heterogeneous hosts |
### Runtime access
The full prompt for any tool is available on-demand via the `/tools` endpoint:
The full prompt for any tool is available on-demand:
```sh
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
See [doc/writing-tools.md](writing-tools.md) for the full specification including
host-conditional variants and privileged tools.
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.
---
## 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
{
"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"}}}
}
]
"systemPrompt": "~/.config/ollie/prompts/SYSTEM_PROMPT_QWEN.md",
"prompt": ["$OLLIE_CFG_PATH/prompts/agent-coding.md"],
"temperature": 0.7
}
```
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](WRITING_TOOLS.md) for the full specification.
## Viewing model context
### 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.
```json
{
"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](WRITING_TOOLS.md) for details.
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
The 9P filesystem is the lowest level interface to ollie. All interfaces are wrappers around filesystem operations. Front-ends:
- **[ellie](../el/)** — `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
```sh
# 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
```sh
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
```sh
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>`)
- **[ellie](../el/)** — Emacs front-end (`ellie.el`)
- **KDE** — plasmoid, standalone GUI, Kate plugin, KRunner, system tray
- **Web** — built-in HTTP server (see `olliesrv -web`)