|
|
||
|---|---|---|
| agent | ||
| backend | ||
| cmd | ||
| contrib/elisp | ||
| data | ||
| detach | ||
| doc | ||
| elevate | ||
| env | ||
| experiments/9p-stream | ||
| fs | ||
| kde@34d4a97b3d | ||
| log | ||
| mount | ||
| paths | ||
| prompts | ||
| sandbox | ||
| session | ||
| skills | ||
| tools/lsp | ||
| toolsrv | ||
| .gitignore | ||
| .gitmodules | ||
| .plan.md | ||
| AGENTS.md | ||
| Containerfile | ||
| Ollie | ||
| README.md | ||
| go.mod | ||
| go.sum | ||
| justfile | ||
| lsp_definition | ||
| ollie-remote | ||
| pull.sh | ||
README.md
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 — tool executables, skill files, metadata sidecars — rather than building a monolithic framework.
Getting started
git clone --recurse-submodules https://git.lneely.de/lkn/ollie.git
cd ollie
just
Requires just:
cargo install just
See 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 executables + .meta files
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 code completion
OLLIE_COMPLETE_MODEL=qwen3:latest # model for code completion
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 one Git submodule (kde).
| 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, dynamic tool dispatch, sandboxed execution, remote execution |
tools/lsp/ |
Go | LSP bridge daemon + client library (gopls, clangd, intelephense) |
session/ |
Go | Session lifecycle, config, persistence |
fs/ |
Go | 9P filesystem tree for session namespace |
detach/ |
Go | Background process management |
elevate/ |
Go | Elevation broker (privilege escalation) |
mount/ |
Go | FUSE-based 9P mount for network transparency |
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) |
data/agents/ |
JSON | Agent configs |
data/prompts/ |
Markdown | System prompt templates |
data/tools/ |
Mixed | Tool executables (scripts + compiled) + .meta sidecar files |
data/skills/ |
Markdown | Domain knowledge modules |
doc/ |
Markdown | Architecture docs, usage guide |
Architecture
graph TB
subgraph Frontends
ACME[Plan 9 acme]
EMACS[Emacs<br>ellie.el]
KDE[KDE<br>GUI / Kate / KRunner]
end
subgraph "olliesrv"
P9[9P Filesystem<br>session/ namespace]
SM[Session Manager]
AG[agent.Agent<br>loop · compaction<br>prompt templates]
ROUTE[Router<br>complete · generate · route]
TOOLS[toolsrv.Server<br>dynamic dispatch · sandbox]
end
subgraph "LLM Backends"
OLL[Ollama]
OAI[OpenAI / OpenRouter]
ANT[Anthropic]
COP[Copilot]
KIRO[Kiro]
end
subgraph Execution
LOCAL[Tool Scripts<br>loaded via 9P write<br>· shell, reasoning_think<br>· file_*, lsp_*, memory_*<br>· gui_*, subagent_*]
REMOTE[ollie-remote<br>via SSH · no embedded tools]
end
ACME & EMACS & KDE --> P9
P9 --> SM
SM --> AG
AG --> ROUTE
AG --> TOOLS
AG -->|streaming| OLL & OAI & ANT & COP & KIRO
TOOLS --> LOCAL
TOOLS -->|SSH| REMOTE
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.
# 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:
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:
# 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
# Strip block markup from chat output (text-only frontends)
ollie-9p read session/{id}/agent/{aid}/chat | grep -vE '^\[\[\[.*'
# → filters [[[type]]]/[[[end]]] block markers, leaving plain text
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/dnspattern —/complete,/generate,/routeare stateless per-fid: write a request, read back the resultctlfiles — control operations (stop, kill, rename, compact) are writes to a control file, not method calls- Blocking reads —
statewaitblocks until state changes, replacing event subscriptions with a simplecat - 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.
# 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 notificationsSessionCreated/SessionKilled/SessionRenamed— lifecycle eventsProcessDetached/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: zero built-in tools
No tools are compiled into the Go binary. Every tool — including shell and reasoning_think — is an external executable loaded dynamically through the 9P filesystem. Tool definitions, schemas, and execution logic live entirely in the filesystem.
Loading tools
Tools are loaded by writing their name to session/{id}/agent/{aid}/tools:
# List currently loaded tools
ollie-9p read session/{id}/agent/{aid}/tools
# Load a tool — becomes a native callable function
echo file_glob | ollie-9p write session/{id}/agent/{aid}/tools
# Global catalog: all discoverable tools on disk
ollie-9p read tools
The tool_load tool is itself a bash script that does exactly this — it wraps the 9P write in a callable function so the LLM can load tools during a turn.
Agent autoLoad configuration
Agent configs declare which tools to load at startup via autoLoad:
{
"autoLoad": ["file_read", "file_edit", "file_glob", "file_grep", "file_write", "tool_load"]
}
Each agent profile (default, copilot, explorer, librarian, navigator, taskmanager, theo) has its own autoLoad list. The allowTools field provides a secondary security gate.
Declarative registration
Tools are executables in ~/.config/ollie/tools/. Each one has a .meta sidecar
file declaring its JSON schema, documentation, concurrency class, tier, and
privilege requirements. The registry reads .meta files — it never inspects the
executable itself.
This is declarative tool registration: write a .meta file, drop an
executable in $OLLIE_TOOLS_PATH, and the tool is live. No server restart,
no config file, no code change.
The .meta file (file_glob.meta):
{
"description": "Find files by glob pattern, sorted by mtime (newest first).",
"args": {"type":"object","properties":{"pattern":{"type":"string"}},"required":["pattern"]},
"tier": "cold",
"readOnly": true
}
Everything is sandboxed by default via Landlock. Tools that need to escape
request elevation explicitly ("elevated": true).
Privileged tools: sudo
Tools that need root privileges declare "sudo": true in their .meta. The
dispatch chain becomes a two-gate sequence: elevation approval (escape sandbox)
→ credential prompt → sudo -S execution. The tool itself has no knowledge of
sudo — the privilege wrapping is entirely in the dispatch layer.
{
"description": "Read system logs (dmesg).",
"sudo": true,
"cmd": "system_logs_dmesg",
"args": {"type":"object","properties":{"lines":{"type":"string"}}},
"readOnly": true
}
Credentials are prompted via kdialog/zenity on desktop, or forwarded over
SSH for remote execution. See doc/WRITING_TOOLS.md for
details.
Host-conditional variants
A single .meta file can describe a tool that works differently on different
hosts. Variants are gated by match conditions (binary, file, os, arch,
env, nenv). The first matching variant determines the tool's schema,
documentation, and executable. If no variant matches, the tool is hidden from
the registry — it doesn't exist on this host.
{
"description": "Read system logs.",
"variants": [
{
"match": {"binary": "journalctl"},
"cmd": "system_logs_journald",
"args": {"type":"object","properties":{"unit":{"type":"string"},"since":{"type":"string"}}}
},
{
"match": {"file": "/var/log/syslog"},
"cmd": "system_logs_syslog",
"args": {"type":"object","properties":{"lines":{"type":"string"}}}
}
]
}
On a systemd host the model sees unit filtering and time ranges. On Alpine it
sees tail + grep. On a host with neither, the tool doesn't appear. One .meta
deploys everywhere. See doc/WRITING_TOOLS.md for the
full variant specification.
What you can do
| Capability | How |
|---|---|
| Run an agent | olliesrv → any frontend (ellie, KDE, acme) |
| Remote execution | Set remote=user@host in session config |
| Multi-agent | subagent_spawn from within a session, or shell scripts |
| Code completion | Read/write /complete for fill-in-the-middle |
| 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}) |
| Load tools | Write tool name to agent/{id}/tools via 9P |
| Custom tools | Drop an executable + .meta in $OLLIE_TOOLS_PATH |
| Privileged tools | Declare "sudo": true in .meta — broker handles elevation + credentials |
| Custom agents | Write a JSON config in agents/ |
| Domain skills | Write a markdown file in skills/ |
Credits
- Plan 9 from Bell Labs — for an interesting system
- @9fans — for the Plan 9 port
- Suckless — for articulating good software development principles
- @simonfxr — for a solid agent baseline to "borrow" from
- @aws — for a solid open-source agent implementation
License
GPLv3