readme: bring up to date after Great Flattening

- Frame olliesrv as single binary with two surfaces (9P + D-Bus)
- Fix repository layout table: no core/, 9p/, remote/ submodules
- Fix all namespace paths: session/{id}/agent/{aid}/
- Fix architecture diagram labels (agent.Agent, toolsrv.Server)
- Fix httpgw description (HTTP→9P, not D-Bus)
- Replace verbose features list with concise capability table
- Remove outdated development/pull.sh sections
This commit is contained in:
Ollie Agent 2026-07-30 20:02:14 +02:00
parent f63cb3bb37
commit dd5701b1bb
1 changed files with 119 additions and 244 deletions

363
README.md
View File

@ -1,25 +1,22 @@
# 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.
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
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:
```sh
just # builds core, 9p, acme, kde, httpgw, webui, emacs
cd ollie
just
```
Requires [just](https://github.com/casey/just):
@ -43,144 +40,44 @@ 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_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)
# /route file configuration
OLLIE_ROUTE_BACKEND=ollama # backend for the classifier model
OLLIE_ROUTE_MODEL=qwen3:8b # model that classifies task complexity
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.
## 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).
Single Go module with two Git submodules for frontends.
| 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 |
| [acme/](acme/) | Go | Plan 9 acme editor frontend |
| [httpgw/](httpgw/) | Go | HTTP-to-D-Bus 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 and every `shell` command runs inside a Landlock sandbox with per-profile filesystem access rules. Promoted tools are not exempt — they execute through the same sandbox path as inline shell commands.
- **Extensible tools** — tools are executable scripts with header metadata; add one by dropping a file in a directory
- **Dynamic tool registry** — tools are discoverable and hot-reloadable; add, modify, or remove tool scripts at any time without restarting the server. Only loaded tools are callable. Write a tool name to `s/{sid}/tools` to promote it to a native callable function — no MCP server, no SDK, no protocol boilerplate
- **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 + tools cached by hash)
- **Streaming output** — real-time command output over the SSH channel; builds and tests display as they run
- **Session persistence** — remote sessions survive server restarts; reconnect automatically on restore
- **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
The split-brain model complements 9P's network transparency. 9P mounts a remote agent as a local filesystem (remote brain, local view); split-brain keeps the brain local and sends execution remote (local brain, remote hands). Use 9P for monitoring fleets of headless agents; use split-brain for interactive development on remote hosts.
```
9P remote mount: you ──9P──▶ remote olliesrv ──▶ remote tools
Split-brain: local olliesrv ──SSH──▶ remote ollie-remote ──▶ remote tools
```
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
---
| `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-httpgw/` | Go | HTTP-to-9P gateway |
| `cmd/ollie-remote/` | Go | Remote execution binary |
| `kde/` | C++/Qt6 | KDE plasmoid, GUI, Kate plugin, KRunner, tray *(submodule)* |
| `webui/` | TypeScript | Preact SPA browser frontend *(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
@ -195,12 +92,12 @@ graph TB
end
subgraph "olliesrv"
9P[9P Filesystem]
DBUS[D-Bus Adapter]
P9[9P Filesystem<br>session/ namespace]
DBUS[D-Bus Adapter<br>org.ollie.SessionManager]
SM[Session Manager]
CORE[Core Library<br>agent loop · compaction<br>prompt templates]
AG[agent.Agent<br>loop · compaction<br>prompt templates]
ROUTE[Router<br>complete · generate · route]
TOOLS[Tool Registry<br>dynamic · lazy-load · dispatch]
TOOLS[toolsrv.Server<br>dynamic registry · dispatch]
SB[Landlock Sandbox]
end
@ -213,74 +110,59 @@ graph TB
end
subgraph Execution
LOCAL[Local Tools<br>file_read · lsp_* · shell<br>memory_* · gui_*]
LOCAL[Tool Scripts<br>file_* · lsp_* · memory_*<br>gui_* · subagent_*]
REMOTE[ollie-remote<br>via SSH]
RTOOL[Remote Tools]
end
HTTPGW[ollie-httpgw<br>HTTP→D-Bus gateway]
HTTPGW[ollie-httpgw<br>HTTP→9P gateway]
%% Frontend connections
TER & ACME & EMACS -->|9P| 9P
TER & ACME & EMACS -->|9P| P9
KDE -->|D-Bus| DBUS
WEB --> HTTPGW --> DBUS
WEB --> HTTPGW --> P9
%% Internal layers
9P --> SM
P9 --> SM
DBUS --> SM
SM --> CORE
CORE --> ROUTE
CORE --> TOOLS
SM --> AG
AG --> ROUTE
AG --> TOOLS
TOOLS --> SB
%% Backend connections
CORE -->|streaming| OLL & OAI & ANT & COP & KIRO
AG -->|streaming| OLL & OAI & ANT & COP & KIRO
%% Tool execution
SB --> LOCAL
SB -->|SSH| REMOTE --> RTOOL
```
`olliesrv` is a single binary that operates in three modes:
`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 (terminal, acme, Emacs, KDE, web) |
|------|-------|-----------|-----------|
| +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) |
All modes embed the same core library and session manager. The 9P and D-Bus interfaces are thin adapters over the shared 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. In default mode, it also embeds a D-Bus adapter on `org.ollie.SessionManager` that streams `StateChanged`, `ChatUpdated`, and lifecycle signals to desktop clients.
There is no client library, no SDK, no protocol buffer — `echo`, `cat`, `tail`, and `rm` are the API.
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/prompt
echo "fix the bug in main.go" > /mnt/ollie/session/worker/agent/0/prompt
# Stream the response
tail -f /mnt/ollie/session/worker/chat
tail -f /mnt/ollie/session/worker/agent/0/chat
# Check state
cat /mnt/ollie/session/worker/state
# Kill a background process
echo "signal 12345 TERM" > /mnt/ollie/session/worker/ctl
# Get completions
echo '{"language":"go","code":"func he"}' > /mnt/ollie/session/worker/complete
cat /mnt/ollie/session/worker/complete
cat /mnt/ollie/session/worker/agent/0/state
# One-shot generation (no session needed)
exec 3<>/mnt/ollie/generate; echo 'explain monads in one sentence' >&3; cat <&3; exec 3>&-
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>&-
@ -289,66 +171,52 @@ exec 3<>/mnt/ollie/route; echo 'redesign the auth system' >&3; cat <&3; exec 3>&
### Network transparency
9P is a network protocol. Mount the agent filesystem from any machine on the network:
9P is a network protocol. Mount the agent from any machine:
```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 -tcp :5640 # TCP listener, no D-Bus (implied)
olliesrv -nodbus # Unix socket only, no D-Bus
```
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 entire Unix toolkit:
Because operations are file I/O, they compose with the full Unix toolkit:
```sh
# Fan out a prompt to all sessions
for s in /mnt/ollie/session/*/prompt; do echo "run tests" > "$s"; done
# 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/*/statewait; do cat "$s" > /dev/null; done
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/*/cost
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.
### 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.
Pipe agents into `awk`. Filter with `grep`. Schedule with `cron`. Orchestrate
with a 10-line shell script instead of a framework.
### 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`
---
- **`/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`). The KDE frontends connect to it over the session bus.
### Typed method calls
D-Bus gives you introspectable, typed interfaces with proper error returns. No string parsing, no exit codes to check:
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
@ -359,53 +227,48 @@ dbus-send --session --dest=org.ollie.SessionManager --type=method_call --print-r
# 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 in main.go"
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"
/org/ollie/SessionManager org.ollie.SessionManager.GetState string:"SESSION_ID"
```
### Real-time signals
D-Bus signals push events to subscribers without polling:
### Signals
D-Bus signals push events without polling:
- **`ChatUpdated`** — new chat output (session_id, offset, text)
- **`StateChanged`** — transition notifications (`idle` → `thinking` → `calling: grep` → `idle`)
- **`SessionCreated` / `SessionKilled` / `SessionRenamed`** — lifecycle events for UIs
- **`StateChanged`** — transition notifications
- **`SessionCreated` / `SessionKilled` / `SessionRenamed`** — lifecycle events
- **`ProcessDetached` / `ProcessExited`** — background process events
GUI frontends subscribe once and react to changes — no timers, no file watches, no busy loops.
GUI frontends subscribe once and react — 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
- **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
All of these talk to `org.ollie.SessionManager` over D-Bus using Qt's native bindings — no IPC glue code, no subprocess management.
## Tool registry: dynamic native tools
### Tool registry: dynamic native tools
Tools are scripts in `~/.config/ollie/tools/`. Write one, add a JSON schema header, and it's **discoverable** immediately — no server restart needed. Add, modify, or remove tools at any time; the registry re-reads from disk on every `tool_list` and `tool_load`. A tool is only **callable** when you promote it into your session.
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 (always reflects current directory contents)
cat s/{sid}/tools
# Discover all available tools
cat session/{id}/tools
# Load one into your session — it becomes a native callable function
echo file_glob | 9p write s/{sid}/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.
# No MCP server. No protocol. No SDK. Just a script with a header.
```
The tool header is minimal:
The header:
```bash
#!/usr/bin/env bash
@ -416,20 +279,32 @@ The tool header is minimal:
# ollie:end
```
The `args_json` field is the JSON schema the model uses to call it. The `ollie:prompt` block is the documentation. Everything else is just a script.
Everything is sandboxed by default via Landlock. Tools that need to escape
request elevation explicitly (`"elevated": true`).
This is the anti-MCP. No subprocess servers, no protocol negotiation, no capability advertisement, no transport layer. A file. A header. A script. The model calls it natively.
## 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
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
- [@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
GPLv3