# ollie Ollie is an AI agent runtime. A small Go core handles the agent loop, tool dispatch, sandboxing, and multi-backend routing; frontends (terminal, acme, Emacs, KDE, browser) and orchestration (shell scripts, multi-agent workflows, cron) are external. The core is deliberately minimal — capabilities come from composing small pieces rather than building a monolithic framework. ## Getting started Clone with submodules: ```sh git clone --recurse-submodules https://git.lneely.de/lkn/ollie.git ``` Or after cloning: ```sh git submodule update --init --recursive ``` Build and install (pick one): ```sh just # 9P path (recommended): core, 9p, acme, kde, httpgw, webui, emacs just dbus # D-Bus path (KDE-only): core, ollied, kde, httpgw, webui ``` These are mutually exclusive — pick the one that matches your setup. Requires [just](https://github.com/casey/just): ```sh cargo install just ``` See [doc/USAGE.md](doc/USAGE.md) for usage instructions. ## Configuration ### Environment: `~/.config/ollie/env` ``` OLLIE_BACKEND=openai # ollama | openai | anthropic | copilot | kiro (default: ollama) OLLIE_OLLAMA_URL= # base URL for Ollama (default: http://localhost:11434) OLLIE_OPENAI_URL=https://openrouter.ai/api OLLIE_OPENAI_KEY=sk-or-... OLLIE_ANTHROPIC_KEY=sk-ant-... OLLIE_COPILOT_TOKEN=... OLLIE_KIRO_TOKEN=... # bearer token or sqlite:// path (auto-detected from Kiro CLI if unset) OLLIE_MODEL=qwen/qwen3-235b-a22b OLLIE_TOOLS_PATH=~/.config/ollie/tools # directory for tool scripts ({tool} steps) OLLIE_MEMORY_PATH=~/.config/ollie/memory # directory for memory files (ollie/m) OLLIE_ELEVATE_SOCKET=${XDG_RUNTIME_DIR}/ollie/elevate.sock # socket path for x/elevate adapter OLLIE_COMPLETE_BACKEND=ollama # backend for u/complete (required) OLLIE_COMPLETE_MODEL=qwen3:latest # model for u/complete (required) OLLIE_ENABLED_BACKENDS=openrouter,kiro # backends available for routing (comma-separated) # /route file configuration OLLIE_ROUTE_BACKEND=ollama # backend for the classifier model OLLIE_ROUTE_MODEL=qwen3:8b # model that classifies task complexity ``` Shell environment variables take precedence over the env file. ## Development Pull latest across all submodules: ```sh ./pull.sh ``` Run tests: ```sh just test ``` ## Repository layout Monorepo with Git submodules. Each submodule has its own Go module (except `el` which is Elisp and `kde` which is C++/Qt). | Directory | Language | Description | |-----------|----------|-------------| | [core/](core/) | Go | Core library: agent loop, backends, tools, config, sandbox | | [9p/](9p/) | Go | 9P filesystem server (`olliesrv`) | | [remote/](remote/) | Go | Remote execution server (`ollie-remote`) for split-brain SSH mode | | [dbus/](dbus/) | Go | D-Bus session manager daemon (`ollied`) | | [acme/](acme/) | Go | Plan 9 acme editor frontend | | [httpgw/](httpgw/) | Go | HTTP-to-9P gateway (`ollie-httpgw`) | | [webui/](webui/) | TypeScript | Preact SPA browser frontend | | [kde/](kde/) | C++/Qt6 | KDE integration: plasmoid, standalone GUI, Kate plugin, KRunner, tray | | [el/](el/) | Elisp | Emacs frontend (`ellie.el`) | | [agents/](agents/) | JSON | Agent config files (loaded at runtime) | | [prompts/](prompts/) | Markdown | System prompt templates | | [tools/](tools/) | Python/Bash | Tool scripts (file_read, lsp_*, memory_*, etc.) | | [scripts/](scripts/) | Shell | User-facing CLI scripts: session management, utilities, plumbing | | [sandbox/](sandbox/) | YAML | Landlock sandbox config | | [doc/](doc/) | Markdown | Architecture docs, usage guide | | [contrib/](contrib/) | Mixed | Community scripts | | [experiments/](experiments/) | Mixed | Trial notes | ## Features ### Sessions - **Create / kill / rename / list** — full lifecycle management of concurrent agent sessions - **Prompt submission with FIFO queue** — prompts are queued and processed in order; multiple clients can submit without conflicts - **State observation** — each session exposes its current state (`idle`, `thinking`, `calling: `) with a blocking-wait primitive for efficient polling - **Chat streaming** — stream model output in real time via file tail or D-Bus signal - **Session persistence** — active sessions survive server restarts; state is checkpointed to `~/.local/share/ollie/sessions/` - **Plan / scratchpad** — per-session markdown checklist that survives context compaction and is visible to peer agents - **Environment inspection** — read the effective environment variables for any session ### Agent loop - **Streaming tool calls** — the core runs a turn loop: prompt the model, parse tool calls, dispatch them (in parallel where safe), feed results back, repeat until done - **Automatic context compaction** — long conversations are summarized transparently when approaching the context limit - **System prompt templating** — prompts are rendered from markdown templates with variable substitution; inspect the fully rendered prompt at any time ### Backends - **Multi-backend routing** — Ollama (local), OpenAI-compatible, Anthropic, GitHub Copilot, Kiro; switch backends per-session - **Model listing and switching** — enumerate available models from any backend; switch models mid-session - **Connection pooling** — all sessions sharing the same API host multiplex over a single HTTP/2 connection; no redundant TLS handshakes - **24-hour model cache** — model lists are cached at the backend level to avoid repeated API calls ### Tools and sandboxing - **Sandboxed execution** — every tool call runs inside a Landlock sandbox with per-profile filesystem access rules - **Extensible tools** — tools are executable scripts with header metadata; add one by dropping a file in a directory - **Elevation** — commands that need to escape the sandbox can request elevation through a streaming protocol (frame-based, over unix socket) ### Agents - **Agent definitions** — JSON configs that set personality, tools, backend preferences, and constraints - **List and switch** — enumerate available agents and assign one to any session ### Multi-agent - **Peer communication** — bidirectional links between sessions; peers can submit prompts to each other - **Orchestrator/worker patterns** — subagent delegation, parallel fan-out, coordinated through session primitives - **Domain skills** — teach the agent your project’s conventions with a markdown file; loaded on demand ### Background processes - **Detach** — start a long-running command and immediately background it; the agent continues working - **Monitor** — list running processes, read their output (64KB ring buffer), check status - **Signal and dismiss** — send TERM/KILL to a detached process; remove finished processes from the list ### Remote execution (split-brain) - **Local brain, remote hands** — the agent loop and LLM streaming stay local; tool execution happens on a remote host over SSH - **Zero-setup deployment** — `ollie-remote` is bootstrapped automatically on first connect (binary cached by hash) - **Sandboxed remotely** — Landlock enforcement runs on the remote host; `landrun` is shipped alongside the binary - **Convention over configuration** — sensible sandbox defaults; per-project overrides via `.ollie-sandbox.yaml` in the project root - **GUI works unchanged** — D-Bus signals fire locally, so KDE GUI / web UI see real-time progress See [doc/REMOTE_EXECUTION.md](doc/REMOTE_EXECUTION.md) for the design. ### Stateless endpoints - **Code completion** — write cursor context (prefix, suffix, file path), read back code to insert; works from any editor or script - **One-shot generation** — write a prompt, read back a full LLM response; no session, no agent loop, no tools; scriptable from any language - **Task routing** — write a task description, the system classifies complexity against all available models and returns the best backend+model for the job ### Frontends - **Terminal** — interactive CLI (`b`, `bfg`, `sh`) - **Plan 9 acme** — editor integration for acme - **Emacs** — `ellie.el` package - **KDE** — plasmoid, standalone GUI, Kate plugin, KRunner, system tray - **Web UI** — Preact SPA served by the HTTP gateway ### Configuration - **Per-session config** — backend, model, agent, generation parameters; all readable and writable at runtime --- ## Architecture Ollie has two session manager backends. Both embed the same core library and expose the same features; they differ in interface philosophy and deployment context. | | 9P (`olliesrv`) | D-Bus (`ollied`) | |---|---|---| | Interface | Synthetic filesystem + D-Bus protocol adapter | Desktop bus methods + signals | | Frontends | All (terminal, acme, Emacs, KDE, web) | KDE only (plasmoid, GUI, Kate, KRunner, tray) | | Network | Native (mount over TCP) | Local session bus | | Dependencies | None beyond Go runtime | D-Bus daemon, desktop session | `olliesrv` is the primary server. It exposes sessions as a 9P synthetic filesystem and embeds a D-Bus protocol adapter that emits the same signals and methods that the KDE frontends expect. All frontends work with `olliesrv`. `ollied` is a standalone D-Bus daemon that only supports the KDE frontends. It predates the 9P server's D-Bus adapter and remains available for deployments that don't need 9P. Use whichever fits your environment — or both at once. Note that sessions are not shared between them; each backend manages its own session pool. --- ## 9P: everything is a file The 9P server exposes the agent runtime as a synthetic filesystem. Every session is a directory; every operation is a read or write. It also embeds a D-Bus protocol adapter on `org.ollie.SessionManager` that streams `StateChanged`, `ChatUpdated`, and lifecycle signals to desktop clients — so the KDE plasmoid, standalone GUI, Kate plugin, and system tray all work without `ollied`. There is no client library, no SDK, no protocol buffer — `echo`, `cat`, `tail`, and `rm` are the API. ```sh # Create a session echo "name=worker" > /mnt/ollie/s/new # Submit a prompt echo "fix the bug in main.go" > /mnt/ollie/s/worker/prompt # Stream the response tail -f /mnt/ollie/s/worker/chat # Check state cat /mnt/ollie/s/worker/state # Kill a background process echo "signal 12345 TERM" > /mnt/ollie/s/worker/ctl # Get completions echo '{"language":"go","code":"func he"}' > /mnt/ollie/s/worker/complete cat /mnt/ollie/s/worker/complete # One-shot generation (no session needed) exec 3<>/mnt/ollie/generate; echo 'explain monads in one sentence' >&3; cat <&3; exec 3>&- # Route a task to the best model exec 3<>/mnt/ollie/route; echo 'redesign the auth system' >&3; cat <&3; exec 3>&- # → backend=kiro model=claude-sonnet-4-20250514 ``` ### Network transparency 9P is a network protocol. Mount the agent filesystem from any machine on the network: ```sh 9pfuse 'tcp!server:5640' /mnt/ollie ``` No SSH tunneling, no port forwarding for individual endpoints, no API gateway. One mount and every session, every tool, every config value is accessible as if local. ### Container and headless deployment The 9P server embeds the core library directly — no display server, no desktop dependencies. It runs anywhere a Go binary runs: containers, cloud VMs, CI runners, embedded systems. The embedded D-Bus adapter is automatically disabled when using `-tcp` (remote listen mode). You can also disable it explicitly with `-nodbus`: ```sh olliesrv start -tcp :5640 # TCP listener, no D-Bus (implied) olliesrv start -nodbus # Unix socket only, no D-Bus ``` ### Shell composability Because operations are file I/O, they compose with the entire Unix toolkit: ```sh # Fan out a prompt to all sessions for s in /mnt/ollie/s/*/prompt; do echo "run tests" > "$s"; done # Wait for all agents to finish for s in /mnt/ollie/s/*/statewait; do cat "$s" > /dev/null; done # Grep all agent plans grep -r "TODO" /mnt/ollie/s/*/plan # Monitor costs paste /mnt/ollie/s/*/cost ``` Pipe agents into `awk`. Filter with `grep`. Schedule with `cron`. Orchestrate with a 10-line shell script instead of a framework. ### Multiplexing Multiple clients — terminals, editors, scripts, other agents — can mount the same server simultaneously. Each gets an independent view through 9P’s per-fid state model. No connection pools, no session affinity, no WebSocket juggling. ### Plan 9 patterns The design follows Plan 9 conventions directly: - **`/net/dns` pattern** — `/complete`, `/generate`, and `/route` are stateless per-fid: write a request, read back the result, like a DNS lookup - **`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` --- ## D-Bus: desktop integration The D-Bus interface (`org.ollie.SessionManager`) is available two ways: embedded in `olliesrv` (recommended) or as the standalone `ollied` daemon. Both expose the same methods and signals. The KDE frontends connect to whichever is running. ### Typed method calls D-Bus gives you introspectable, typed interfaces with proper error returns. No string parsing, no exit codes to check: ```sh # Create a session dbus-send --session --dest=de.lneely.ollie --print-reply \ /de/lneely/ollie de.lneely.ollie.Manager.CreateSession \ string:"worker" string:"" string:"" # Submit a prompt dbus-send --session --dest=de.lneely.ollie --print-reply \ /de/lneely/ollie/sessions/worker de.lneely.ollie.Session.SubmitPrompt \ string:"fix the bug in main.go" # Read state dbus-send --session --dest=de.lneely.ollie --print-reply \ /de/lneely/ollie/sessions/worker de.lneely.ollie.Session.GetState ``` ### Real-time signals D-Bus signals push events to subscribers without polling: - **`ChatDelta`** — stream tokens as they arrive - **`StateChanged`** — transition notifications (`idle` → `thinking` → `calling: grep` → `idle`) - **`SessionCreated` / `SessionRemoved`** — lifecycle events for UIs that display session lists GUI frontends subscribe once and react to changes — no timers, no file watches, no busy loops. ### KDE integration The D-Bus interface is what makes native desktop integration possible: - **Plasmoid** — embed an agent chat widget directly in the Plasma panel - **Kate plugin** — inline AI assistance in the text editor - **KRunner** — launch prompts from the desktop search bar - **System tray** — status indicator and quick actions - **Standalone GUI** — full-featured Qt6 chat application All of these talk to `org.ollie.SessionManager` over D-Bus using Qt's native bindings — no IPC glue code, no subprocess management. They work with either `olliesrv` or `ollied`. ### Activation and lifecycle D-Bus supports socket activation: `ollied` can start on first use and exit when idle. The desktop session manages its lifecycle automatically through a `.service` file — no init scripts, no manual daemon management. ## Credits Many sources of inspiration: - [Plan 9 from Bell Labs](https://9fans.net) — for an interesting system - [@9fans](https://github.com/9fans) — for the Plan 9 port - [Suckless](https://suckless.org) — for articulating good software development principles - [@simonfxr](https://github.com/simonfxr) — for a solid agent baseline to "borrow" from, and other nifty ideas - [@aws](https://github.com/aws/amazon-q-developer-cli) — for a solid open-source agent implementation ## License GPLv3