# 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__LOG=debug` where `` 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//cfg # state, backend, model, agent, cwd, params 9p read session//agent/0/chat 9p rm session/ # 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/.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 ` 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: ` | | `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 } 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` and `OLLIE_TRANSCRIPT_PATH` are ordinary filesystem paths. Because `olliesrv` speaks 9P and any remote instance can be mounted locally via `9pfuse`, these vars can point at remote directories 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. ### Centralized transcript archive Collect transcripts from all hosts in one place: ```sh olliesrv mount archivehost:9564 ~/mnt/archive export OLLIE_TRANSCRIPT_PATH=~/mnt/archive/tr ``` Sessions started after this change write their transcripts to the remote host. Existing sessions are unaffected. Both vars can be combined. Set them in `~/.config/ollie/env` for persistence: ``` OLLIE_TOOLS_PATH=~/mnt/toolserver/t OLLIE_TRANSCRIPT_PATH=~/mnt/archive/tr ``` - 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/D-Bus 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.