# ollie
Ollie is an AI agent runtime. A single binary (`olliesrv`) exposes agent sessions
over two surfaces: a **9P2000 filesystem** (the canonical interface) and a
**D-Bus service** (`org.ollie.SessionManager`). Both are thin adapters over the
same core — the agent loop, tool dispatch, sandboxing, and multi-backend routing.
Choice of surface is per-deployment: 9P for its Unix composability, D-Bus for
desktop integration, or both.
The core is deliberately minimal. Capabilities come from composing small pieces —
shell scripts, tool scripts, skill files — rather than building a monolithic
framework.
## Getting started
```sh
git clone --recurse-submodules https://git.lneely.de/lkn/ollie.git
cd ollie
just
```
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
OLLIE_MEMORY_PATH=~/.config/ollie/memory # directory for memory files
OLLIE_ELEVATE_SOCKET=${XDG_RUNTIME_DIR}/ollie/elevate.sock
OLLIE_COMPLETE_BACKEND=ollama # backend for u/complete
OLLIE_COMPLETE_MODEL=qwen3:latest # model for u/complete
OLLIE_ENABLED_BACKENDS=openrouter,kiro # backends available for routing (comma-separated)
OLLIE_ROUTE_BACKEND=ollama # backend for the /route classifier
OLLIE_ROUTE_MODEL=qwen3:8b # model for task routing
```
Shell environment variables take precedence over the env file.
## Repository layout
Single Go module with two Git submodules for frontends.
| Directory | Language | Description |
|-----------|----------|-------------|
| `agent/` | Go | Agent loop, history, hooks, prompt resolution, commands |
| `backend/` | Go | LLM providers (Anthropic, OpenAI, Ollama, Gemini, Copilot, CodeWhisperer) |
| `toolsrv/` | Go | Tool server, registry, sandboxed execution, remote execution |
| `session/` | Go | Session lifecycle, config, persistence |
| `fs/session/` | Go | 9P filesystem tree for session namespace |
| `dbus/` | Go | D-Bus adapter (`org.ollie.SessionManager`) |
| `detach/` | Go | Background process management |
| `elevate/` | Go | Elevation broker (privilege escalation) |
| `sandbox/` | Go | Landlock sandbox config |
| `cmd/olliesrv/` | Go | The main binary |
| `cmd/ollie-9p/` | Go | 9P client |
| `cmd/ollie-remote/` | Go | Remote execution binary |
| `kde/` | C++/Qt6 | KDE plasmoid, GUI, Kate plugin, KRunner, tray *(submodule)* |
| `agents/` | JSON | Agent configs |
| `prompts/` | Markdown | System prompt templates |
| `tools/` | Python/Bash | Tool scripts (file_read, lsp_*, memory_*, etc.) |
| `scripts/` | Shell | Session management, utilities, plumbing |
| `doc/` | Markdown | Architecture docs, usage guide |
## Architecture
```mermaid
graph TB
subgraph Frontends
TER[Terminal
b / bfg / sh]
ACME[Plan 9 acme]
EMACS[Emacs
ellie.el]
KDE[KDE
plasmoid / GUI / Kate
KRunner / tray]
end
subgraph "olliesrv"
P9[9P Filesystem
session/ namespace]
DBUS[D-Bus Adapter
org.ollie.SessionManager]
SM[Session Manager]
AG[agent.Agent
loop · compaction
prompt templates]
ROUTE[Router
complete · generate · route]
TOOLS[toolsrv.Server
dynamic registry · dispatch]
SB[Landlock Sandbox]
end
subgraph "LLM Backends"
OLL[Ollama]
OAI[OpenAI / OpenRouter]
ANT[Anthropic]
COP[Copilot]
KIRO[Kiro]
end
subgraph Execution
LOCAL[Tool Scripts
file_* · lsp_* · memory_*
gui_* · subagent_*]
REMOTE[ollie-remote
via SSH]
RTOOL[Remote Tools]
end
TER & ACME & EMACS -->|9P| P9
KDE -->|D-Bus| DBUS
P9 --> SM
DBUS --> SM
SM --> AG
AG --> ROUTE
AG --> TOOLS
TOOLS --> SB
AG -->|streaming| OLL & OAI & ANT & COP & KIRO
SB --> LOCAL
SB -->|SSH| REMOTE --> RTOOL
```
`olliesrv` is a single binary. Which surface it exposes is a flag:
| Mode | Flags | Interface | Frontends |
|------|-------|-----------|-----------|
| +9p +dbus | (default) | 9P filesystem + D-Bus signals | All |
| +9p -dbus | `-nodbus` | 9P filesystem only | Terminal, acme, Emacs, web |
| -9p +dbus | `-no9p` | D-Bus only | KDE (plasmoid, GUI, Kate, KRunner, tray) |
## 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. 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/session/new
# Submit a prompt
echo "fix the bug in main.go" > /mnt/ollie/session/worker/agent/0/prompt
# Stream the response
tail -f /mnt/ollie/session/worker/agent/0/chat
# Check state
cat /mnt/ollie/session/worker/agent/0/state
# One-shot generation (no session needed)
exec 3<>/mnt/ollie/generate; echo 'explain monads' >&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 from any machine:
```sh
9pfuse 'tcp!server:5640' /mnt/ollie
```
No SSH tunneling, no port forwarding, no API gateway. One mount and every
session, every tool, every config value is accessible as if local.
### 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 /mnt/ollie/session/*/agent/*/prompt; do echo "run tests" > "$s"; done
# Wait for all agents to finish
for s in /mnt/ollie/session/*/agent/*/statewait; do cat "$s" > /dev/null; done
# Grep all agent plans
grep -r "TODO" /mnt/ollie/session/*/plan
# Monitor costs
paste /mnt/ollie/session/*/agent/*/cost
```
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
## D-Bus: desktop integration
The D-Bus interface (`org.ollie.SessionManager`) is embedded in `olliesrv`
(enabled by default, or exclusively via `-no9p`). It provides typed method
calls and real-time signals.
```sh
# Create a session
dbus-send --session --dest=org.ollie.SessionManager --type=method_call --print-reply \
/org/ollie/SessionManager org.ollie.SessionManager.CreateSession \
string:"$PWD" string:"" string:"" string:"default" string:"" string:""
# Submit a prompt
dbus-send --session --dest=org.ollie.SessionManager --type=method_call \
/org/ollie/SessionManager org.ollie.SessionManager.Submit \
string:"SESSION_ID" string:"fix the bug"
# Read state
dbus-send --session --dest=org.ollie.SessionManager --type=method_call --print-reply \
/org/ollie/SessionManager org.ollie.SessionManager.GetState string:"SESSION_ID"
```
### Signals
D-Bus signals push events without polling:
- **`ChatUpdated`** — new chat output (session_id, offset, text)
- **`StateChanged`** — transition notifications
- **`SessionCreated` / `SessionKilled` / `SessionRenamed`** — lifecycle events
- **`ProcessDetached` / `ProcessExited`** — background process events
GUI frontends subscribe once and react — no timers, no file watches, no busy loops.
### KDE integration
- **Plasmoid** — embed an agent chat widget 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
## Tool registry: dynamic native tools
Tools are executable scripts in `~/.config/ollie/tools/`. Each one declares its
metadata — JSON schema, documentation, concurrency class — in header comments.
No server restart needed. No MCP server. No protocol. Just a file with a header.
```bash
# Discover all available tools
cat session/{id}/tools
# Load one into your session — becomes a native callable function
echo file_glob | 9p write session/{id}/tools
# The model sees it as a first-class tool on the next turn.
```
The header:
```bash
#!/usr/bin/env bash
# args_json: {"type":"object","properties":{"pattern":{"type":"string"}},"required":["pattern"]}
# ollie:prompt
# ## file_glob
# Find files by glob pattern.
# ollie:end
```
Everything is sandboxed by default via Landlock. Tools that need to escape
request elevation explicitly (`"elevated": true`).
## What you can do
| Capability | How |
|---|---|
| **Run an agent** | `olliesrv` → `s/sh` or any frontend |
| **Remote execution** | Set `remote=user@host` in session config |
| **Multi-agent** | `subagent_spawn` from within a session, or shell scripts |
| **Code completion** | `u/complete` from acme, or read/write `/complete` |
| **One-shot LLM** | Write to `/generate`, read back the response |
| **Task routing** | Write to `/route`, read back `backend=X model=Y` |
| **Background jobs** | Detach via `shell({"elevated":true, "detach":true})` |
| **Custom tools** | Drop a script in `$OLLIE_TOOLS_PATH` |
| **Custom agents** | Write a JSON config in `agents/` |
| **Domain skills** | Write a markdown file in `skills/` |
## Credits
- [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
- [@aws](https://github.com/aws/amazon-q-developer-cli) — for a solid open-source agent implementation
## License
GPLv3