350 lines
15 KiB
Markdown
350 lines
15 KiB
Markdown
# 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: <tool>`) 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
|