# Usage ## Server ### Start and stop ```sh olliesrv start # start in background (daemonizes) olliesrv fgstart # start in foreground olliesrv stop # stop the daemon olliesrv status # check if running ``` By default the server mounts at `$HOME/mnt/ollie`. Override with `-mount`: ```sh olliesrv start -mount /tmp/ollie ``` ### TCP listener To also accept connections over TCP (e.g. for remote access): ```sh olliesrv start -tcp :9564 ``` ### Debug logging Log level is controlled by `OLLIE_LOG` (default: `warn`). Set to `debug` for verbose output: ```sh OLLIE_LOG=debug olliesrv fgstart ``` 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 `s/{id}/context` — the exact message array sent to the backend on the last turn. Combined with `s/{id}/systemprompt`, this gives full visibility into what the model sees. --- Examples below assume `$OLLIE` is set to the ollie mount point (default: `$HOME/mnt/ollie`). Scripts under `s/` and `u/` are available directly via the mounted filesystem after running `mk && mk scripts`. ## Interactive shell `s/sh` is an interactive shell for prompting a session. It resumes the most recent session for the current working directory by default, or creates a new one if none exists. ```sh exec $OLLIE/s/sh # resume or create session for pwd exec $OLLIE/s/sh -new # always start a fresh session exec $OLLIE/s/sh -new -model qwen3:8b # fresh session with model override exec $OLLIE/s/sh -session 1744276689123456789-2b986c # attach to a specific session ``` At the `> ` prompt: | Input | Action | |---|---| | `` | Submit prompt; response streams to terminal | | `/stop` | Interrupt the current agent turn | | `/kill` | Kill the session and exit | | `/compact` | Compact conversation history | | `/clear` | Clear conversation history | | `/rn ` | Rename the session | | `/cwd [path]` | Get or set working directory | | `/agent [name]` | Get or set agent | | `/model [name]` | Get or set model | | `/backend [name]` | Get or set backend | | `/models` | List available models | | `/backends` | List available backends | | `/usage` | Show token usage | | `/ctxsz` | Show context size | | `/q ` | Enqueue prompt for later | | `! ` | Run shell command in session cwd | | C-j | Insert newline (multi-line input) | | Ctrl-C | Interrupt running turn (or exit if idle) | `s/sh` tracks the last session per working directory in `~/.config/ollie/last-session`, so switching directories and re-running `s/sh` resumes the right context automatically. ## One-shot queries `s/bfg` and `s/bbg` submit a single prompt to a new session. The difference is only in how the caller is treated: `s/bfg` blocks until the agent finishes and writes the result to stdout; `s/bbg` returns immediately and prints the path of the session. Both are thin wrappers around `s/b`, which is the core one-shot runner. ### s/bfg Submit a prompt, wait for completion, print the result: ```sh $OLLIE/s/bfg "Summarize this repo" cat main.go | $OLLIE/s/bfg "explain this file" $OLLIE/s/bfg -parallel 4 -keep "Write a haiku" $OLLIE/s/bfg -backend ollama -model qwen3:8b "Explain this" < main.go ``` With `-parallel N`, N jobs run concurrently and results are printed as they complete. ### s/bbg Submit a background job and return immediately; prints the `s/{id}` path: ```sh $OLLIE/s/bbg "summarize the recent git log" # /home/lkn/mnt/ollie/s/1744276689123456789-0 ``` Poll for completion: ```sh path=$($OLLIE/s/bbg "write a limerick about Go") until [ "$(cat $path/statewait)" = "idle" ]; do :; done tail -c +$(($(cat $path/offset) + 1)) $path/chat ``` Fan out with `-parallel N` and collect results: ```sh paths=$($OLLIE/s/bbg -parallel 4 "draft a blog intro" < brief.txt) for p in $paths; do until [ "$(cat $p/statewait 2>/dev/null)" = "idle" ]; do :; done tail -c +$(($(cat $p/offset) + 1)) $p/chat rm -r $p done ``` ### s/cleanup Remove all idle sessions: ```sh $OLLIE/s/cleanup ``` Schedule regular cleanup with a cron job: ``` */15 * * * * /path/to/mnt/ollie/s/cleanup ``` ### Raw s/ access ```sh cat $OLLIE/s/new # show the spec template printf 'cwd=%s\n---\nSummarize this.\n' "$PWD" > $OLLIE/s/new cat $OLLIE/s//cfg # state, backend, model, agent, cwd, params cat $OLLIE/s//chat rm -r $OLLIE/s/ # 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 `s/{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" > $OLLIE/s/mysession/cfg echo "topP=0.9" > $OLLIE/s/mysession/cfg ``` ### Sandbox Every `execute_code` invocation 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 `execute_code` call without restarting the server or session. ## AI pipelines `s/bfg` writes to stdout and composes naturally with Unix pipes. When both args and stdin are present, args are treated as the instruction and stdin as the content: ```sh echo "write a haiku about filesystems" | $OLLIE/s/bfg | wc -w cat error.log | $OLLIE/s/bfg "what is causing this?" | $OLLIE/s/bfg "suggest a fix" $OLLIE/s/bfg "list 5 blog post ideas" | grep -v "^$" | head -3 | $OLLIE/s/bfg "expand the best one" ``` `u/optimize` is a worked example — it generates N candidate prompts in parallel via `s/bfg -parallel N`, then judges them with a second `s/bfg` call to return the best: ```sh $OLLIE/u/optimize -n 3 -cm qwen/qwen3-8b -jm anthropic/claude-opus-4-6 "explain recursion" ``` Progress goes to stderr; pipe the optimized prompt with content into a subsequent call: ```sh prompt=$($OLLIE/u/optimize "translate this to Spanish" 2>/dev/null) cat document.txt | $OLLIE/s/bfg "$prompt" ``` ## Code completion (acme) `u/complete` provides AI ghost-text code completion for [acme](http://acme.cat-v.org) windows. It reads the text before and after the cursor (dot), sends it to a lightweight model, and opens the suggested insertion in a new window. ### Setup Two environment variables are required (set them in `~/.config/ollie/env` or export them in your shell): ``` OLLIE_COMPLETE_BACKEND=ollama OLLIE_COMPLETE_MODEL=qwen3:4b ``` ### Usage From an acme window, middle-click `u/complete` in the tag (or add it to the global tag). The script: 1. Reads up to 4000 characters before the cursor and 1000 after. 2. Finds or creates a persistent copilot session for the current working directory. 3. Submits a fill-in-the-middle prompt to the configured model. 4. Opens the completion in a new `ollie/complete` window. Override model or backend per invocation with flags: ```sh u/complete -model phi-4-mini -backend ollama ``` ### Session reuse 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 `execute_code`: ```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" > $OLLIE/s/new printf 'name=reviewer\ncwd=%s\n' "$PWD" > $OLLIE/s/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." > $OLLIE/s/writer/prompt # Wait for the turn to finish (statewait blocks until state changes) until [ "$(cat $OLLIE/s/writer/statewait)" = "idle" ]; do :; done # Read only the new output (offset marks the byte position after the user prompt) tail -c +$(($(cat $OLLIE/s/writer/offset) + 1)) $OLLIE/s/writer/chat ``` **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 transcript | | `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 } cd $OLLIE printf 'name=developer\nagent=developer\ncwd=%s\n' "$cwd" > s/new printf 'name=reviewer\nagent=reviewer\ncwd=%s\n' "$cwd" > s/new printf 'name=tester\nagent=tester\ncwd=%s\n' "$cwd" > s/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< s/$sid/statewait printf '%s' "$*" > s/$sid/prompt while true; do read -r _ <&3 && { exec 3<&-; break; } exec 3<&- [ "$(awk -F= '/^state=/{print $2}' s/$sid/cfg)" != "idle" ] && break exec 3< s/$sid/statewait done until [ "$(awk -F= '/^state=/{print $2}' s/$sid/cfg)" = "idle" ]; do sleep 0.5; done tail -c +$(($(cat s/$sid/offset) + 1)) s/$sid/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 s/developer s/reviewer s/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 `s/new` spec lines. ### Self-generating workflows Because agents have access to `execute_code`, 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 `s/{id}/t/idx`) 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 ``` ## HTTP gateway `ollie-httpgw` is an HTTP gateway that translates REST-style HTTP calls into 9P filesystem operations. It lets any HTTP client — including the web UI, `curl`, or custom scripts — interact with `olliesrv` without a 9P client library. ### Build ```sh mk httpgw # from repo root # or cd httpgw && mk ``` Produces `~/bin/ollie-httpgw`. ### Start and stop Like `olliesrv`, the gateway supports daemon management: ```sh ollie-httpgw start # start in background (daemonizes) ollie-httpgw fgstart # start in foreground ollie-httpgw stop # stop the daemon ollie-httpgw status # check if running ``` ### Run (legacy) For backwards compatibility, running without a subcommand starts in foreground: ```sh ollie-httpgw # connect to local olliesrv via namespace; listen on :8080 ollie-httpgw -listen :9090 # different port ollie-httpgw -net tcp -addr host:9564 # connect to remote olliesrv over TCP ``` **Flags** | Flag | Default | Description | |---|---|---| | `-listen` | `:8080` | HTTP listen address | | `-service` | `ollie` | 9P service name in the local namespace | | `-net` | `` | Network type (`tcp`, `unix`); empty = use namespace | | `-addr` | `` | 9P server address; required when `-net` is set | The gateway must be able to reach `olliesrv`. For a local server started with `olliesrv start`, the defaults work as-is. For a remote server started with `olliesrv start -tcp :9564`, use `-net tcp -addr remotehost:9564`. ### HTTP → 9P mapping Each HTTP method maps to a 9P operation: | Method | 9P operation | Example | |---|---|---| | `GET` | read file / list dir | `GET /s/idx` | | `POST` | write to file | `POST /s/new`, `POST /s/{id}/prompt` | | `DELETE` | remove | `DELETE /s/{id}` | | `PATCH` | rename (JSON `{"name":"..."}`) | `PATCH /s/{id}` | | `HEAD` | stat (metadata in `X-9p-*` headers) | `HEAD /s/{id}` | The full API is described in `httpgw/openapi.json` and served live at `GET /openapi.json`. ### Key endpoints ```sh GET /s/idx # list sessions (JSON array) GET /s/{id}/cfg # session config (key-value JSON) GET /s/{id}/chat # conversation transcript (plain text) POST /s/new # create session (JSON: {cwd, backend, model, agent}) POST /s/{id}/prompt # submit prompt (JSON: {prompt: "..."}) POST /s/{id}/ctl # send control command (JSON: {ctl: "stop"}) DEL /s/{id} # kill and remove session GET /backends # list available backends GET /a/ # agent configs GET /s/{id}/t/ # tool scripts GET /m/ # memory files GET /sk/ # skills ``` ```sh # Examples with curl: curl http://localhost:8080/s/idx curl -X POST http://localhost:8080/s/new \ -d '{"cwd":"/home/user/project","backend":"anthropic"}' curl -X POST http://localhost:8080/s/mysession/prompt \ -d '{"prompt":"summarize the recent commits"}' ``` ## Web UI The web UI is a Preact single-page application that talks to `ollie-httpgw`. It provides session management, a chat interface, and a basic 9P filesystem browser in the browser. ### Build ```sh mk webui # from repo root # or cd webui && npm run build ``` Requires Node.js and npm. Output lands in `webui/dist/`. ### Serve The `dist/` folder is a static site. Serve it with any HTTP server and proxy the API paths to `ollie-httpgw`: **nginx example:** ```nginx server { listen 3000; root /path/to/ollie/webui/dist; index index.html; location / { try_files $uri /index.html; } location ~ ^/(s|a|m|t|sk|backends|openapi\.json)(/|$) { proxy_pass http://localhost:8080; } } ``` **Python one-liner (local testing only):** ```sh cd webui/dist && python3 -m http.server 3000 # API calls will fail — use the Vite dev server instead for local testing ``` ### Development Vite's dev server proxies API paths to httpgw automatically: ```sh # Terminal 1: gateway ollie-httpgw -listen :8080 # Terminal 2: dev server (hot-reload, proxied API) cd webui && npm run dev # Open http://localhost:5173 ``` The proxy configuration is in `webui/vite.config.ts` and targets `localhost:8080` by default. ### Features - Session list in a sidebar with state, model, backend, and cwd - Create, rename, and kill sessions - Send prompts; streaming via 2-second poll - Stop a running agent - Markdown rendering of assistant responses - 9P filesystem browser (navigate `s/`, `a/`, `m/`, `sk/`) - Two built-in themes: `midnight` (dark) and `acme` (Plan 9-inspired light); choice persisted to `localStorage` ## Alternative front-ends The 9P filesystem is the lowest level interface to ollie. All interfaces are simply wrappers around filesystem operations, and `s/sh` is the standard chat interface. Other front-ends: - **[ellie](https://github.com/lneely/ollie-el)** — `ellie.el` is the Emacs front-end, just wire it into your `init.el` and `M-x ellie` - **Web UI** — browser-based UI; requires `ollie-httpgw` (see above) See each repo's README for installation and usage.