506 lines
17 KiB
Markdown
506 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)
|
|
```
|
|
|
|
By default the server mounts at `$HOME/mnt/ollie`. Override with `-mount`:
|
|
|
|
```sh
|
|
olliesrv -mount /tmp/ollie
|
|
```
|
|
|
|
### 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`).
|
|
|
|
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:
|
|
|
|
```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
|
|
```
|
|
|
|
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:
|
|
|
|
```sh
|
|
echo '{"file":"main.go","prefix":"func ","suffix":""}' | ollie-9p rdwr complete
|
|
```
|
|
|
|
## Routing
|
|
|
|
The `route` file selects the best backend+model for a task description:
|
|
|
|
```sh
|
|
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
|
|
|
|
```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
|
|
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"prompt": "You are a helpful assistant.",
|
|
"temperature": 0.7,
|
|
"topP": 0.95,
|
|
"reasoning": 10000,
|
|
"stop": ["\n\n"]
|
|
}
|
|
```
|
|
|
|
Example runtime override:
|
|
|
|
```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.
|
|
|
|
## 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`:
|
|
|
|
```json
|
|
{
|
|
"steps": [{"tool": "subagent_spawn", "args": "-n 3 summarize this module"}]
|
|
}
|
|
```
|
|
|
|
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 |
|
|
|---|---|
|
|
| `-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:**
|
|
|
|
```sh
|
|
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:**
|
|
|
|
```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:**
|
|
|
|
| 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:
|
|
|
|
```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 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:
|
|
|
|
```sh
|
|
# 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:
|
|
|
|
```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
|
|
|
|
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.\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:
|
|
|
|
```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
|
|
|
|
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.
|
|
|
|
```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"}}}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
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.
|
|
|
|
### 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.
|
|
|
|
## 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/`)
|
|
|
|
See each repo's README for installation and usage.
|