remove httpgw and webui; update AGENTS.md to reflect current structure
- Remove cmd/ollie-httpgw/ directory and webui/ submodule - Update justfile: remove httpgw/webui build targets - Update README.md: remove httpgw/webui references - Update doc/ARCHITECTURE.md: remove httpgw/webui references - Update doc/EVOLUTION.md: remove httpgw/webui references - Update doc/USAGE.md: remove httpgw/webui sections - Update AGENTS.md: fix repo layout, build targets, testing, architecture paths, key files, adding tools/prompts, submodule workflow
This commit is contained in:
parent
dd5701b1bb
commit
ea3db9eb08
|
|
@ -1,6 +1,3 @@
|
||||||
[submodule "webui"]
|
|
||||||
path = webui
|
|
||||||
url = ../ollie-webui.git
|
|
||||||
[submodule "kde"]
|
[submodule "kde"]
|
||||||
path = kde
|
path = kde
|
||||||
url = ../ollie-kde.git
|
url = ../ollie-kde.git
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Plan: Remove httpgw + webui
|
||||||
|
|
||||||
|
1. Remove cmd/ollie-httpgw/ directory ✓ (git rm)
|
||||||
|
2. Remove webui/ submodule ✓ (git rm)
|
||||||
|
3. Update justfile ✓
|
||||||
|
4. Update README.md ✓
|
||||||
|
5. Update doc/ARCHITECTURE.md ✓
|
||||||
|
6. Update doc/EVOLUTION.md ✓
|
||||||
|
7. Update doc/USAGE.md ✓
|
||||||
|
8. Update AGENTS.md — repo layout, build system, testing, architecture, key files, submodule workflow ← IN PROGRESS
|
||||||
|
9. Commit + push
|
||||||
167
AGENTS.md
167
AGENTS.md
|
|
@ -1,27 +1,21 @@
|
||||||
# AGENTS.md
|
# AGENTS.md
|
||||||
|
|
||||||
Project-level context for AI agents working in this repository.
|
Project-level context for AI agents working in this repository.
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
Ollie is an AI agent runtime inspired by Plan 9: agent state and behaviors are exposed as files in a 9P namespace. Orchestration, scheduling, and UIs are external — shell scripts, editors, web apps. The core is minimal; capabilities come from composing scripts.
|
Ollie is an AI agent runtime inspired by Plan 9: agent state and behaviors are exposed as files in a 9P namespace. Orchestration, scheduling, and UIs are external — shell scripts, editors, web apps. The core is minimal; capabilities come from composing scripts.
|
||||||
|
|
||||||
## Repository Layout
|
## Repository Layout
|
||||||
|
|
||||||
Monorepo with Git submodules. Each submodule has its own Go module (except `el` which is Elisp and `kde` which is C++/Qt).
|
Monorepo with Git submodules. Each submodule has its own Go module (except `el` which is Elisp and `kde` which is C++/Qt).
|
||||||
|
|
||||||
```
|
```
|
||||||
ollie/ ← you are here
|
ollie/ ← you are here
|
||||||
├── core/ (Go) Core library: agent loop, backends, tools, config
|
├── agent/ (Go) Agent loop, history, hooks, commands
|
||||||
├── 9p/ (Go) 9P filesystem server (olliesrv)
|
├── toolsrv/ (Go) Tool server, registry, sandboxed execution
|
||||||
├── dbus/ (Go) D-Bus session manager daemon (ollied)
|
├── session/ (Go) Session lifecycle, config, persistence
|
||||||
├── acme/ (Go) Plan 9 acme editor frontend
|
├── fs/session/ (Go) 9P filesystem tree
|
||||||
├── httpgw/ (Go) HTTP-to-9P gateway
|
├── dbus/ (Go) D-Bus adapter
|
||||||
├── webui/ (TypeScript) Preact SPA browser frontend
|
├── cmd/ (Go) Binaries (olliesrv, ollie-9p, ollie-remote)
|
||||||
├── kde/ (C++/Qt6) KDE integration: plasmoid, standalone GUI, Kate plugin, KRunner, tray
|
├── kde/ (C++/Qt6) KDE integration: plasmoid, GUI, Kate plugin, KRunner, tray
|
||||||
├── el/ (Elisp) Emacs frontend (ellie.el)
|
├── el/ (Elisp) Emacs frontend (ellie.el)
|
||||||
├── agents/ Agent config JSONs (loaded at runtime)
|
├── agents/ Agent config JSONs (loaded at runtime)
|
||||||
├── prompts/ System prompt templates (markdown) ← NOT used; see note below
|
├── prompts/ System prompt templates (markdown)
|
||||||
├── tools/ Tool scripts (file_read, lsp_*, memory_*, etc.)
|
├── tools/ Tool scripts (file_read, lsp_*, memory_*, etc.)
|
||||||
├── sandbox/ Landlock sandbox config YAML
|
├── sandbox/ Landlock sandbox config YAML
|
||||||
├── scripts/ User-facing CLI scripts:
|
├── scripts/ User-facing CLI scripts:
|
||||||
|
|
@ -32,162 +26,119 @@ ollie/ ← you are here
|
||||||
├── contrib/ Community scripts
|
├── contrib/ Community scripts
|
||||||
└── experiments/ Trial notes
|
└── experiments/ Trial notes
|
||||||
```
|
```
|
||||||
|
|
||||||
### Canonical source for prompts/tools/skills
|
### Canonical source for prompts/tools/skills
|
||||||
|
Prompt and tool files live in `contrib/`. The `just install-data` target copies them to `~/.config/ollie/`. **Do not edit `~/.config/ollie/` directly** — changes are overwritten on next install.
|
||||||
Three locations have prompt and tool files. The **source of truth** depends on which component you're editing:
|
|
||||||
|
|
||||||
| Location | Purpose | Deployed by |
|
| Location | Purpose | Deployed by |
|
||||||
|----------|---------|-------------|
|
|----------|---------|-------------|
|
||||||
| `9p/prompts/`, `9p/tools/` | Canonical source for the 9P server | `just install-data` |
|
| `contrib/prompts/`, `contrib/tools/` | Canonical source for all prompts, tools, and skills | `just install-data` |
|
||||||
| `9p/skills/`, `kde/skills/` | Shared `agent-skills` submodule (same repo in both) | `just install-data` |
|
| `contrib/skills/` | Domain knowledge modules (markdown) | `just install-data` |
|
||||||
| `kde/prompts/`, `kde/tools/` | KDE-specific copies (may diverge for KDE-only tools like `gui_*`) | `just install-kde` |
|
| `kde/` | KDE-specific tool scripts (`gui_*`) | `just install-kde` |
|
||||||
| Root `prompts/`, `tools/` | Top-level copies installed by `just install-data` | `just install-data` |
|
|
||||||
|
|
||||||
The `just install-data` target copies `agents/`, `prompts/`, `tools/`, `sandbox/` (and `skills/` from submodules) into `~/.config/ollie/`. **Do not edit `~/.config/ollie/` directly** — changes are overwritten on next install.
|
|
||||||
|
|
||||||
When adding a new prompt/tool that should be available to all frontends, add it to both `9p/prompts/` (or `9p/tools/`) and the root `prompts/` (or `tools/`) directory.
|
|
||||||
|
|
||||||
## Build System
|
## Build System
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From monorepo root (pick one):
|
# Build everything:
|
||||||
just # 9P path (recommended): core, 9p, acme, kde, httpgw, webui, emacs
|
just
|
||||||
just dbus # D-Bus path (KDE-only): core, ollied, kde, httpgw, webui
|
# Individual targets:
|
||||||
|
just ninep # olliesrv + ollie-9p
|
||||||
# Individual build targets:
|
|
||||||
just core # core library
|
|
||||||
just ninep # 9P server
|
|
||||||
just acme # acme frontend
|
just acme # acme frontend
|
||||||
just kde # KDE integration (cmake with ~/.local prefix)
|
just kde # KDE integration (cmake with ~/.local prefix)
|
||||||
just httpgw # HTTP gateway
|
just ollie-remote # remote execution binary
|
||||||
just webui # web UI (needs npm)
|
|
||||||
just dbus-bin # D-Bus daemon (ollied)
|
|
||||||
|
|
||||||
# Install targets (run automatically by the top-level paths):
|
# Install targets (run automatically by the top-level paths):
|
||||||
just install-data # agents, prompts, tools, skills → ~/.config/ollie/
|
just install-data # agents, prompts, tools, skills → ~/.config/ollie/
|
||||||
just install-scripts # CLI scripts → ~/.config/ollie/scripts/
|
just install-scripts # CLI scripts → ~/.config/ollie/scripts/
|
||||||
just install-contrib # contrib scripts → ~/bin/
|
just install-contrib # contrib scripts → ~/bin/
|
||||||
just install-kde # KDE plugins, desktop file, env → ~/.local/
|
just install-kde # KDE plugins, desktop file, env → ~/.local/
|
||||||
just install-el # ellie.el → ~/.config/emacs/ellie/
|
just install-el # ellie.el → ~/.config/emacs/ellie/
|
||||||
just install-dbus-bin # ollied + autostart → ~/.local/bin/, ~/.config/autostart/
|
|
||||||
|
|
||||||
# Test:
|
# Test:
|
||||||
just test # run all tests (core + 9p)
|
just test # run all tests
|
||||||
just test-core # core tests only
|
just test-core # core tests only
|
||||||
just test-9p # 9p tests only
|
just test-9p # 9p tests only
|
||||||
|
|
||||||
# Lifecycle:
|
# Lifecycle:
|
||||||
just uninstall # remove all installed files
|
just uninstall # remove all installed files
|
||||||
just clean # remove build artifacts
|
just clean # remove build artifacts
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires [just](https://github.com/casey/just): `cargo install just`
|
Requires [just](https://github.com/casey/just): `cargo install just`
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Go tests (from any Go submodule):
|
# All Go tests:
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|
||||||
# Core has the most tests:
|
|
||||||
cd core && go test ./...
|
|
||||||
|
|
||||||
# 9P server tests:
|
|
||||||
cd 9p && go test ./...
|
|
||||||
|
|
||||||
# KDE has integration test scripts:
|
# KDE has integration test scripts:
|
||||||
cd kde && ./test-e2e.sh
|
cd kde && ./test-e2e.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Language & Conventions
|
## Language & Conventions
|
||||||
|
|
||||||
- **Go** (core, 9p, dbus, acme, httpgw): Go 1.25+, standard library preferred, minimal dependencies. Modules are `ollie` (core), `olliesrv` (9p), `ollie-dbus`, `ollie-acme`, `ollie-httpgw`.
|
- **Go** (root module): Go 1.25+, standard library preferred, minimal dependencies.
|
||||||
- **C++20/Qt6/KF6** (kde): CMake build, dual Qt5/Qt6 support where noted.
|
- **C++20/Qt6/KF6** (kde): CMake build, dual Qt5/Qt6 support where noted.
|
||||||
- **TypeScript** (webui): Vite + Preact.
|
|
||||||
- **Elisp** (el): single file `ellie.el`.
|
- **Elisp** (el): single file `ellie.el`.
|
||||||
- **Tool scripts**: Python 3 or Bash. Must be executable. Header comments declare metadata (`# ollie:parallel read`, `# ollie:tier cold`, `# description:`, `# args:`).
|
- **Tool scripts**: Python 3 or Bash. Must be executable. Header comments declare metadata (`# ollie:parallel read`, `# ollie:tier cold`, `# description:`, `# args:`).
|
||||||
|
|
||||||
### Code style
|
### Code style
|
||||||
- Go: `gofmt`, short variable names, error returns (no panics), table-driven tests.
|
- Go: `gofmt`, short variable names, error returns (no panics), table-driven tests.
|
||||||
- Tool scripts: emit structured output (`STATUS=ok`, `STATUS=error`). Image/LSP tools return JSON content blocks.
|
- Tool scripts: emit structured output (`STATUS=ok`, `STATUS=error`). Image/LSP tools return JSON content blocks.
|
||||||
- Prompts: markdown, concise, example-driven. Follow the pattern in existing `tools-*.md` files.
|
- Prompts: markdown, concise, example-driven. Follow the pattern in existing `tools-*.md` files.
|
||||||
|
|
||||||
## Architecture (key concepts)
|
## Architecture (key concepts)
|
||||||
|
1. **Two integration surfaces**: 9P filesystem (sessions at `session/{id}/agent/{aid}/`) and D-Bus (`org.ollie.SessionManager`). Tools, skills, memory on physical filesystem via env vars.
|
||||||
1. **Two integration surfaces**: 9P filesystem (sessions at `s/{name}/`) and D-Bus (`org.ollie.SessionManager`). Tools, skills, memory on physical filesystem via env vars.
|
2. **Agent loop** (`agent/loop.go`): Streaming LLM call → parse tool calls → dispatch → loop until no more tool calls or max steps.
|
||||||
2. **Agent loop** (`core/pkg/agent/`): Streaming LLM call → parse tool calls → dispatch → loop until no more tool calls or max steps.
|
3. **Tool dispatch** (`toolsrv/`): `shell` is built-in. All others are external scripts resolved from `OLLIE_TOOLS_PATH`.
|
||||||
3. **Tool dispatch** (`core/pkg/tools/`): `shell` is built-in. All others are external scripts resolved from `OLLIE_TOOLS_PATH`.
|
4. **Sandbox** (`sandbox/`): Landlock-based. Config in `sandbox/*.yaml` defines filesystem access per profile.
|
||||||
4. **Sandbox** (`core/internal/sandbox/`): Landlock-based. Config in `sandbox/*.yaml` defines filesystem access per profile.
|
5. **Backends** (`backend/`): Ollama, OpenAI-compatible, Anthropic, Copilot, Kiro, Gemini, CodeWhisperer. Selectable per-session.
|
||||||
5. **Backends** (`core/pkg/backend/`): Ollama, OpenAI-compatible, Anthropic, Copilot, Kiro. Selectable per-session.
|
|
||||||
6. **Prompts assembled at runtime**: Agent JSON `prompt` array runs shell commands (via `x/prime`) that cat markdown files together. No static prompt file is used directly.
|
6. **Prompts assembled at runtime**: Agent JSON `prompt` array runs shell commands (via `x/prime`) that cat markdown files together. No static prompt file is used directly.
|
||||||
7. **D-Bus** (kde/dbus only): `ollied` exposes `org.ollie.SessionManager` for KDE frontends. It's a separate daemon from `olliesrv`.
|
7. **D-Bus** embedded in `olliesrv`: `org.ollie.SessionManager` exposed by the same binary as the 9P server.
|
||||||
|
|
||||||
## Key Files
|
## Key Files
|
||||||
|
|
||||||
| What | Where |
|
| What | Where |
|
||||||
|------|-------|
|
|------|-------|
|
||||||
| Agent loop | `core/pkg/agent/loop.go` |
|
| Agent loop | `agent/loop.go` |
|
||||||
| Tool dispatcher | `core/pkg/tools/execute/` |
|
| Tool server | `toolsrv/server.go` |
|
||||||
| Remote execution client | `core/pkg/remote/remote.go` |
|
| Remote execution | `toolsrv/remote.go`, `cmd/ollie-remote/` |
|
||||||
| Remote execution server | `remote/main.go` |
|
| Sandbox enforcement | `sandbox/` |
|
||||||
| Sandbox enforcement | `core/internal/sandbox/` |
|
| 9P filesystem | `fs/session/` |
|
||||||
| 9P filesystem | `9p/fs/` |
|
| Session management | `session/session.go` |
|
||||||
| Session management | `9p/session/` |
|
| System prompt template | Embedded in binary |
|
||||||
| System prompt template | Embedded in binary (`system_prompt.md`) |
|
|
||||||
| Agent configs | `agents/*.json` |
|
| Agent configs | `agents/*.json` |
|
||||||
| D-Bus daemon | `dbus/main.go` |
|
| D-Bus adapter | `dbus/` |
|
||||||
| KDE D-Bus client | `kde/plasmoid/plugin/olliedbusclient.cpp` |
|
| KDE D-Bus client | `kde/plasmoid/plugin/olliedbusclient.cpp` |
|
||||||
| Standalone GUI | `kde/gui/` |
|
| Standalone GUI | `kde/gui/` |
|
||||||
| Kate plugin | `kde/kate/` |
|
| Kate plugin | `kde/kate/` |
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
Config lives in `~/.config/ollie/env`. Key variables:
|
Config lives in `~/.config/ollie/env`. Key variables:
|
||||||
- `OLLIE_BACKEND` — default backend (ollama, openai, anthropic, copilot, kiro)
|
- `OLLIE_BACKEND` — default backend (ollama, openai, anthropic, copilot, kiro)
|
||||||
- `OLLIE_MODEL` — default model
|
- `OLLIE_MODEL` — default model
|
||||||
- `OLLIE_TOOLS_PATH` — where tool scripts live (default: `~/.config/ollie/tools`)
|
- `OLLIE_TOOLS_PATH` — where tool scripts live (default: `~/.config/ollie/tools`)
|
||||||
- `OLLIE_MEMORY_PATH` — persistent memory directory
|
- `OLLIE_MEMORY_PATH` — persistent memory directory
|
||||||
- `OLLIE_ENABLED_BACKENDS` — backends available for routing
|
- `OLLIE_ENABLED_BACKENDS` — backends available for routing
|
||||||
|
|
||||||
|
|
||||||
## Adding a new tool
|
## Adding a new tool
|
||||||
|
1. Create an executable script in `contrib/tools/`
|
||||||
1. Create an executable script in `tools/` (and `9p/tools/`, `kde/tools/` if needed)
|
|
||||||
2. Add header comments: `# description:`, `# args:`, optionally `# ollie:parallel read` or `# ollie:tier cold`
|
2. Add header comments: `# description:`, `# args:`, optionally `# ollie:parallel read` or `# ollie:tier cold`
|
||||||
3. Create a matching `prompts/tools-*.md` documentation file (and in `9p/prompts/`, `kde/prompts/`)
|
3. Run `just install-data` to install
|
||||||
4. Run `just install-data` to install
|
|
||||||
|
|
||||||
## Adding a new prompt
|
## Adding a new prompt
|
||||||
|
1. Write the markdown file in `contrib/prompts/`
|
||||||
1. Write the markdown file in `9p/prompts/` and root `prompts/`
|
|
||||||
2. If it should be loaded by default, add a `$OLLIE_CFG_PATH/scripts/x/prime <name>` entry to `agents/default.json`
|
2. If it should be loaded by default, add a `$OLLIE_CFG_PATH/scripts/x/prime <name>` entry to `agents/default.json`
|
||||||
3. Run `just install-data` to install
|
3. Run `just install-data` to install
|
||||||
|
|
||||||
## Submodule workflow
|
## Submodule workflow
|
||||||
|
The only remaining submodule is `kde/`. For KDE:
|
||||||
```bash
|
```bash
|
||||||
# Update all submodules:
|
# Update KDE submodule:
|
||||||
git submodule update --remote --merge
|
git submodule update --remote --merge kde
|
||||||
|
# Work in the KDE submodule:
|
||||||
# Work in a submodule (e.g., core):
|
cd kde
|
||||||
cd core
|
|
||||||
# ... make changes, commit ...
|
# ... make changes, commit ...
|
||||||
git push
|
git push
|
||||||
cd ..
|
cd ..
|
||||||
git add core
|
git add kde
|
||||||
git commit -m "update core submodule"
|
git commit -m "update kde submodule"
|
||||||
```
|
```
|
||||||
|
|
||||||
Each submodule has its own remote at `ssh://lkn@lneely.de:44220/lkn/ollie-{name}.git`.
|
Each submodule has its own remote at `ssh://lkn@lneely.de:44220/lkn/ollie-{name}.git`.
|
||||||
|
When cloning, use `--recurse-submodules` or run `git submodule update --init --recursive`.
|
||||||
### Nested submodules
|
## Environment
|
||||||
|
The only submodules are `kde/` and `webui/` (now removed). For KDE:
|
||||||
Both `9p/` and `kde/` contain a nested `skills/` submodule pointing to the same repo (`agent-skills.git`). There is no root-level `skills/` directory.
|
```bash
|
||||||
|
# Update KDE submodule:
|
||||||
|
git submodule update --remote --merge kde
|
||||||
|
# Work in the KDE submodule:
|
||||||
|
cd kde
|
||||||
|
# ... make changes, commit ...
|
||||||
|
git push
|
||||||
|
cd ..
|
||||||
|
git add kde
|
||||||
|
git commit -m "update kde submodule"
|
||||||
```
|
```
|
||||||
9p/skills/ → ssh://lkn@lneely.de:44220/lkn/agent-skills.git
|
Each submodule has its own remote at `ssh://lkn@lneely.de:44220/lkn/ollie-{name}.git`.
|
||||||
kde/skills/ → ssh://lkn@lneely.de:44220/lkn/agent-skills.git (same repo)
|
When cloning, use `--recurse-submodules` or run `git submodule update --init --recursive`.
|
||||||
```
|
|
||||||
|
|
||||||
When cloning, use `--recurse-submodules` or run `git submodule update --init --recursive` to get these.
|
|
||||||
|
|
|
||||||
|
|
@ -69,10 +69,8 @@ Single Go module with two Git submodules for frontends.
|
||||||
| `sandbox/` | Go | Landlock sandbox config |
|
| `sandbox/` | Go | Landlock sandbox config |
|
||||||
| `cmd/olliesrv/` | Go | The main binary |
|
| `cmd/olliesrv/` | Go | The main binary |
|
||||||
| `cmd/ollie-9p/` | Go | 9P client |
|
| `cmd/ollie-9p/` | Go | 9P client |
|
||||||
| `cmd/ollie-httpgw/` | Go | HTTP-to-9P gateway |
|
|
||||||
| `cmd/ollie-remote/` | Go | Remote execution binary |
|
| `cmd/ollie-remote/` | Go | Remote execution binary |
|
||||||
| `kde/` | C++/Qt6 | KDE plasmoid, GUI, Kate plugin, KRunner, tray *(submodule)* |
|
| `kde/` | C++/Qt6 | KDE plasmoid, GUI, Kate plugin, KRunner, tray *(submodule)* |
|
||||||
| `webui/` | TypeScript | Preact SPA browser frontend *(submodule)* |
|
|
||||||
| `agents/` | JSON | Agent configs |
|
| `agents/` | JSON | Agent configs |
|
||||||
| `prompts/` | Markdown | System prompt templates |
|
| `prompts/` | Markdown | System prompt templates |
|
||||||
| `tools/` | Python/Bash | Tool scripts (file_read, lsp_*, memory_*, etc.) |
|
| `tools/` | Python/Bash | Tool scripts (file_read, lsp_*, memory_*, etc.) |
|
||||||
|
|
@ -88,7 +86,6 @@ graph TB
|
||||||
ACME[Plan 9 acme]
|
ACME[Plan 9 acme]
|
||||||
EMACS[Emacs<br>ellie.el]
|
EMACS[Emacs<br>ellie.el]
|
||||||
KDE[KDE<br>plasmoid / GUI / Kate<br>KRunner / tray]
|
KDE[KDE<br>plasmoid / GUI / Kate<br>KRunner / tray]
|
||||||
WEB[Web UI<br>Preact SPA]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "olliesrv"
|
subgraph "olliesrv"
|
||||||
|
|
@ -115,11 +112,8 @@ graph TB
|
||||||
RTOOL[Remote Tools]
|
RTOOL[Remote Tools]
|
||||||
end
|
end
|
||||||
|
|
||||||
HTTPGW[ollie-httpgw<br>HTTP→9P gateway]
|
|
||||||
|
|
||||||
TER & ACME & EMACS -->|9P| P9
|
TER & ACME & EMACS -->|9P| P9
|
||||||
KDE -->|D-Bus| DBUS
|
KDE -->|D-Bus| DBUS
|
||||||
WEB --> HTTPGW --> P9
|
|
||||||
|
|
||||||
P9 --> SM
|
P9 --> SM
|
||||||
DBUS --> SM
|
DBUS --> SM
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
ollie-httpgw
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
# ollie-httpgw
|
|
||||||
|
|
||||||
HTTP gateway for ollie. Translates REST requests to D-Bus calls against `org.ollie.SessionManager`, allowing any HTTP client to manage sessions without a D-Bus library. Also serves as the backend for the web UI.
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
```sh
|
|
||||||
mk
|
|
||||||
```
|
|
||||||
|
|
||||||
Installs `ollie-httpgw` to `$HOME/bin`.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ollie-httpgw start # start daemon (backgrounds itself)
|
|
||||||
ollie-httpgw fgstart # start in foreground
|
|
||||||
ollie-httpgw stop # stop daemon
|
|
||||||
ollie-httpgw status # check if running
|
|
||||||
```
|
|
||||||
|
|
||||||
The gateway listens on `:8011` by default. Override with `-listen`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ollie-httpgw start -listen :9090
|
|
||||||
```
|
|
||||||
|
|
||||||
Requires either `olliesrv` or `ollie-dbus` running (provides the D-Bus service).
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
All responses are JSON. An OpenAPI spec is served at `GET /openapi.json`.
|
|
||||||
|
|
||||||
### Sessions
|
|
||||||
|
|
||||||
| Method | Path | Action |
|
|
||||||
|--------|------|--------|
|
|
||||||
| `GET` | `/sessions` | List all sessions |
|
|
||||||
| `POST` | `/sessions` | Create a session (body: `{cwd, backend, model, agent}`) |
|
|
||||||
| `GET` | `/sessions/{id}` | Get session info |
|
|
||||||
| `DELETE` | `/sessions/{id}` | Kill a session |
|
|
||||||
| `PATCH` | `/sessions/{id}` | Rename a session (body: `{name}`) |
|
|
||||||
|
|
||||||
### Interaction
|
|
||||||
|
|
||||||
| Method | Path | Action |
|
|
||||||
|--------|------|--------|
|
|
||||||
| `POST` | `/sessions/{id}/submit` | Submit a prompt (body: `{prompt}`) |
|
|
||||||
| `POST` | `/sessions/{id}/interrupt` | Interrupt the current turn |
|
|
||||||
|
|
||||||
### State queries
|
|
||||||
|
|
||||||
| Method | Path | Action |
|
|
||||||
|--------|------|--------|
|
|
||||||
| `GET` | `/sessions/{id}/state` | Get session state |
|
|
||||||
| `GET` | `/sessions/{id}/chat?offset=N` | Get chat text from offset |
|
|
||||||
| `GET` | `/sessions/{id}/usage` | Get token usage |
|
|
||||||
| `GET` | `/sessions/{id}/cost` | Get cost |
|
|
||||||
| `GET` | `/sessions/{id}/config` | Get config |
|
|
||||||
| `POST` | `/sessions/{id}/config` | Set config (body: `{key, value}`) |
|
|
||||||
| `GET` | `/sessions/{id}/context` | Get message history (JSONL) |
|
|
||||||
| `GET` | `/sessions/{id}/models` | List models for session's backend |
|
|
||||||
|
|
||||||
### Peers
|
|
||||||
|
|
||||||
| Method | Path | Action |
|
|
||||||
|--------|------|--------|
|
|
||||||
| `GET` | `/sessions/{id}/peers` | List peers |
|
|
||||||
| `POST` | `/sessions/{id}/peers` | Add peer (body: `{peer_id}`) |
|
|
||||||
| `DELETE` | `/sessions/{id}/peers/{peer_id}` | Remove peer |
|
|
||||||
| `POST` | `/sessions/{id}/peers/{peer_id}/submit` | Submit to peer (body: `{prompt}`) |
|
|
||||||
|
|
||||||
### Enumeration
|
|
||||||
|
|
||||||
| Method | Path | Action |
|
|
||||||
|--------|------|--------|
|
|
||||||
| `GET` | `/backends` | List available backends |
|
|
||||||
| `GET` | `/agents` | List available agents |
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Create a session
|
|
||||||
curl -X POST http://localhost:8011/sessions \
|
|
||||||
-d '{"cwd": "/home/user/project", "agent": "default"}'
|
|
||||||
|
|
||||||
# Submit a prompt
|
|
||||||
curl -X POST http://localhost:8011/sessions/<id>/submit \
|
|
||||||
-d '{"prompt": "list files in cwd"}'
|
|
||||||
|
|
||||||
# Poll chat
|
|
||||||
curl http://localhost:8011/sessions/<id>/chat?offset=0
|
|
||||||
```
|
|
||||||
|
|
@ -1,662 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
dbusService = "org.ollie.SessionManager"
|
|
||||||
dbusPath = "/org/ollie/SessionManager"
|
|
||||||
dbusIface = "org.ollie.SessionManager"
|
|
||||||
)
|
|
||||||
|
|
||||||
func dbusConn() (*dbus.Conn, error) {
|
|
||||||
return dbus.ConnectSessionBus()
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusCall(method string, args ...interface{}) (*dbus.Call, error) {
|
|
||||||
conn, err := dbusConn()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("dbus connect: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
obj := conn.Object(dbusService, dbusPath)
|
|
||||||
call := obj.Call(dbusIface+"."+method, 0, args...)
|
|
||||||
if call.Err != nil {
|
|
||||||
return nil, call.Err
|
|
||||||
}
|
|
||||||
return call, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// dbusHandler routes HTTP requests to D-Bus method calls.
|
|
||||||
//
|
|
||||||
// API:
|
|
||||||
//
|
|
||||||
// GET /sessions - ListSessions
|
|
||||||
// POST /sessions - CreateSession
|
|
||||||
// DELETE /sessions/{id} - KillSession
|
|
||||||
// PATCH /sessions/{id} - RenameSession
|
|
||||||
//
|
|
||||||
// POST /sessions/{id}/submit - Submit prompt
|
|
||||||
// POST /sessions/{id}/interrupt - Interrupt
|
|
||||||
//
|
|
||||||
// GET /sessions/{id}/state - GetState
|
|
||||||
// GET /sessions/{id}/chat - GetChat (query: offset)
|
|
||||||
// GET /sessions/{id}/usage - GetUsage
|
|
||||||
// GET /sessions/{id}/cost - GetCost
|
|
||||||
// GET /sessions/{id}/config - GetConfig
|
|
||||||
// POST /sessions/{id}/config - SetConfig
|
|
||||||
// GET /sessions/{id}/context - GetContext
|
|
||||||
//
|
|
||||||
// GET /backends - ListBackends
|
|
||||||
// GET /sessions/{id}/models - ListModels
|
|
||||||
// GET /agents - ListAgents
|
|
||||||
//
|
|
||||||
// GET /sessions/{id}/peers - PeerList
|
|
||||||
// POST /sessions/{id}/peers - PeerAdd
|
|
||||||
// DELETE /sessions/{id}/peers/{peer_id} - PeerRemove
|
|
||||||
// POST /sessions/{id}/peers/{peer_id}/submit - PeerSubmit
|
|
||||||
func dbusHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
p := strings.TrimPrefix(r.URL.Path, "/")
|
|
||||||
p = strings.TrimSuffix(p, "/")
|
|
||||||
|
|
||||||
parts := strings.Split(p, "/")
|
|
||||||
|
|
||||||
switch {
|
|
||||||
// GET/POST /sessions
|
|
||||||
case len(parts) == 1 && parts[0] == "sessions":
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
dbusListSessions(w)
|
|
||||||
case http.MethodPost:
|
|
||||||
dbusCreateSession(w, r)
|
|
||||||
default:
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
|
|
||||||
}
|
|
||||||
|
|
||||||
// /sessions/{id}
|
|
||||||
case len(parts) == 2 && parts[0] == "sessions":
|
|
||||||
sessionID := parts[1]
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
dbusGetSession(w, sessionID)
|
|
||||||
case http.MethodDelete:
|
|
||||||
dbusKillSession(w, sessionID)
|
|
||||||
case http.MethodPatch:
|
|
||||||
dbusRenameSession(w, r, sessionID)
|
|
||||||
default:
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
|
|
||||||
}
|
|
||||||
|
|
||||||
// /sessions/{id}/{action}
|
|
||||||
case len(parts) == 3 && parts[0] == "sessions":
|
|
||||||
sessionID := parts[1]
|
|
||||||
action := parts[2]
|
|
||||||
switch action {
|
|
||||||
case "submit":
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "POST only"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dbusSubmit(w, r, sessionID)
|
|
||||||
case "interrupt":
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "POST only"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dbusInterrupt(w, sessionID)
|
|
||||||
case "state":
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "GET only"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dbusGetState(w, sessionID)
|
|
||||||
case "chat":
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "GET only"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dbusGetChat(w, r, sessionID)
|
|
||||||
case "usage":
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "GET only"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dbusGetUsage(w, sessionID)
|
|
||||||
case "cost":
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "GET only"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dbusGetCost(w, sessionID)
|
|
||||||
case "config":
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
dbusGetConfig(w, sessionID)
|
|
||||||
case http.MethodPost:
|
|
||||||
dbusSetConfig(w, r, sessionID)
|
|
||||||
default:
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "GET or POST"})
|
|
||||||
}
|
|
||||||
case "context":
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "GET only"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dbusGetContext(w, sessionID)
|
|
||||||
case "models":
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "GET only"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dbusListModels(w, sessionID)
|
|
||||||
case "peers":
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
dbusPeerList(w, sessionID)
|
|
||||||
case http.MethodPost:
|
|
||||||
dbusPeerAdd(w, r, sessionID)
|
|
||||||
default:
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "GET or POST"})
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "not found"})
|
|
||||||
}
|
|
||||||
|
|
||||||
// /sessions/{id}/peers/{peer_id}
|
|
||||||
case len(parts) == 4 && parts[0] == "sessions" && parts[2] == "peers":
|
|
||||||
sessionID := parts[1]
|
|
||||||
peerID := parts[3]
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodDelete:
|
|
||||||
dbusPeerRemove(w, sessionID, peerID)
|
|
||||||
default:
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "DELETE only"})
|
|
||||||
}
|
|
||||||
|
|
||||||
// /sessions/{id}/peers/{peer_id}/submit
|
|
||||||
case len(parts) == 5 && parts[0] == "sessions" && parts[2] == "peers" && parts[4] == "submit":
|
|
||||||
sessionID := parts[1]
|
|
||||||
peerID := parts[3]
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "POST only"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dbusPeerSubmit(w, r, sessionID, peerID)
|
|
||||||
|
|
||||||
// GET /backends
|
|
||||||
case len(parts) == 1 && parts[0] == "backends":
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "GET only"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dbusListBackends(w)
|
|
||||||
|
|
||||||
// GET /agents
|
|
||||||
case len(parts) == 1 && parts[0] == "agents":
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "GET only"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dbusListAgents(w)
|
|
||||||
|
|
||||||
default:
|
|
||||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "not found"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Sessions ---
|
|
||||||
|
|
||||||
func dbusListSessions(w http.ResponseWriter) {
|
|
||||||
call, err := dbusCall("ListSessions")
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var sessions []string
|
|
||||||
if err := call.Store(&sessions); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var result []map[string]string
|
|
||||||
for _, entry := range sessions {
|
|
||||||
parts := strings.SplitN(entry, "\t", 4)
|
|
||||||
if len(parts) >= 4 {
|
|
||||||
result = append(result, map[string]string{
|
|
||||||
"id": parts[0],
|
|
||||||
"state": parts[1],
|
|
||||||
"model": parts[2],
|
|
||||||
"agent": parts[3],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
result = []map[string]string{}
|
|
||||||
}
|
|
||||||
sort.Slice(result, func(i, j int) bool {
|
|
||||||
return result[i]["id"] < result[j]["id"]
|
|
||||||
})
|
|
||||||
writeJSON(w, http.StatusOK, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusCreateSession(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
CWD string `json:"cwd"`
|
|
||||||
Backend string `json:"backend"`
|
|
||||||
Model string `json:"model"`
|
|
||||||
Agent string `json:"agent"`
|
|
||||||
Remote string `json:"remote"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil && err != io.EOF {
|
|
||||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
call, err := dbusCall("CreateSession", req.CWD, req.Backend, req.Model, req.Agent, "", req.Remote)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var sessionID string
|
|
||||||
if err := call.Store(&sessionID); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if sessionID == "" {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "session creation failed"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename if a name was provided
|
|
||||||
if req.Name != "" {
|
|
||||||
if call, err := dbusCall("RenameSession", sessionID, req.Name); err == nil {
|
|
||||||
var success bool
|
|
||||||
if call.Store(&success) == nil && success {
|
|
||||||
sessionID = req.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeJSON(w, http.StatusCreated, map[string]string{"id": sessionID})
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusGetSession(w http.ResponseWriter, sessionID string) {
|
|
||||||
// Return config as the session detail view
|
|
||||||
dbusGetConfig(w, sessionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusKillSession(w http.ResponseWriter, sessionID string) {
|
|
||||||
call, err := dbusCall("KillSession", sessionID)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var success bool
|
|
||||||
if err := call.Store(&success); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !success {
|
|
||||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "session not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusRenameSession(w http.ResponseWriter, r *http.Request, sessionID string) {
|
|
||||||
var req struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
call, err := dbusCall("RenameSession", sessionID, req.Name)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var success bool
|
|
||||||
if err := call.Store(&success); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !success {
|
|
||||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "rename failed"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Interaction ---
|
|
||||||
|
|
||||||
func dbusSubmit(w http.ResponseWriter, r *http.Request, sessionID string) {
|
|
||||||
var req struct {
|
|
||||||
Prompt string `json:"prompt"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
call, err := dbusCall("Submit", sessionID, req.Prompt)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var success bool
|
|
||||||
if err := call.Store(&success); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !success {
|
|
||||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "session not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, map[string]bool{"submitted": true})
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusInterrupt(w http.ResponseWriter, sessionID string) {
|
|
||||||
call, err := dbusCall("Interrupt", sessionID)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var success bool
|
|
||||||
if err := call.Store(&success); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !success {
|
|
||||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "session not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, map[string]bool{"interrupted": true})
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- State queries ---
|
|
||||||
|
|
||||||
func dbusGetState(w http.ResponseWriter, sessionID string) {
|
|
||||||
call, err := dbusCall("GetState", sessionID)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var state string
|
|
||||||
if err := call.Store(&state); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, map[string]string{"state": state})
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusGetChat(w http.ResponseWriter, r *http.Request, sessionID string) {
|
|
||||||
offsetStr := r.URL.Query().Get("offset")
|
|
||||||
var offset int64
|
|
||||||
if offsetStr != "" {
|
|
||||||
offset, _ = strconv.ParseInt(offsetStr, 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := dbusConn()
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
obj := conn.Object(dbusService, dbusPath)
|
|
||||||
call := obj.Call(dbusIface+".GetChat", 0, sessionID, offset)
|
|
||||||
if call.Err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": call.Err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var text string
|
|
||||||
var newOffset int64
|
|
||||||
if err := call.Store(&text, &newOffset); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
w.Header().Set("X-Chat-Offset", strconv.FormatInt(newOffset, 10))
|
|
||||||
w.Write([]byte(text))
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusGetUsage(w http.ResponseWriter, sessionID string) {
|
|
||||||
call, err := dbusCall("GetUsage", sessionID)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var usage string
|
|
||||||
if err := call.Store(&usage); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, map[string]string{"usage": usage})
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusGetCost(w http.ResponseWriter, sessionID string) {
|
|
||||||
call, err := dbusCall("GetCost", sessionID)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var cost string
|
|
||||||
if err := call.Store(&cost); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, map[string]string{"cost": cost})
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Config ---
|
|
||||||
|
|
||||||
func dbusGetConfig(w http.ResponseWriter, sessionID string) {
|
|
||||||
call, err := dbusCall("GetConfig", sessionID)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var config string
|
|
||||||
if err := call.Store(&config); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Parse key=value\n into JSON object
|
|
||||||
m := make(map[string]string)
|
|
||||||
for _, line := range strings.Split(strings.TrimRight(config, "\n"), "\n") {
|
|
||||||
if line == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
k, v, _ := strings.Cut(line, "=")
|
|
||||||
m[k] = v
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusSetConfig(w http.ResponseWriter, r *http.Request, sessionID string) {
|
|
||||||
var req struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
call, err := dbusCall("SetConfig", sessionID, req.Key, req.Value)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var success bool
|
|
||||||
if err := call.Store(&success); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !success {
|
|
||||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "set config failed"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, map[string]bool{"success": true})
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Context ---
|
|
||||||
|
|
||||||
func dbusGetContext(w http.ResponseWriter, sessionID string) {
|
|
||||||
call, err := dbusCall("GetContext", sessionID)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var context string
|
|
||||||
if err := call.Store(&context); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Already JSONL — return as-is with appropriate content type
|
|
||||||
w.Header().Set("Content-Type", "application/x-ndjson")
|
|
||||||
w.Write([]byte(context))
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Backends/Models/Agents ---
|
|
||||||
|
|
||||||
func dbusListBackends(w http.ResponseWriter) {
|
|
||||||
call, err := dbusCall("ListBackends")
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var backends []string
|
|
||||||
if err := call.Store(&backends); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, backends)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusListModels(w http.ResponseWriter, sessionID string) {
|
|
||||||
call, err := dbusCall("ListModels", sessionID)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var models []string
|
|
||||||
if err := call.Store(&models); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, models)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusListAgents(w http.ResponseWriter) {
|
|
||||||
call, err := dbusCall("ListAgents")
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var agents []string
|
|
||||||
if err := call.Store(&agents); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, agents)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Peers ---
|
|
||||||
|
|
||||||
func dbusPeerList(w http.ResponseWriter, sessionID string) {
|
|
||||||
call, err := dbusCall("PeerList", sessionID)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var peers []string
|
|
||||||
if err := call.Store(&peers); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if peers == nil {
|
|
||||||
peers = []string{}
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, peers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusPeerAdd(w http.ResponseWriter, r *http.Request, sessionID string) {
|
|
||||||
var req struct {
|
|
||||||
PeerID string `json:"peer_id"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
call, err := dbusCall("PeerAdd", sessionID, req.PeerID)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var success bool
|
|
||||||
if err := call.Store(&success); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !success {
|
|
||||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "peer add failed"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusCreated, map[string]bool{"added": true})
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusPeerRemove(w http.ResponseWriter, sessionID, peerID string) {
|
|
||||||
call, err := dbusCall("PeerRemove", sessionID, peerID)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var success bool
|
|
||||||
if err := call.Store(&success); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !success {
|
|
||||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "peer not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbusPeerSubmit(w http.ResponseWriter, r *http.Request, sessionID, peerID string) {
|
|
||||||
var req struct {
|
|
||||||
Prompt string `json:"prompt"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
call, err := dbusCall("PeerSubmit", sessionID, peerID, req.Prompt)
|
|
||||||
if err != nil {
|
|
||||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var success bool
|
|
||||||
if err := call.Store(&success); err != nil {
|
|
||||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !success {
|
|
||||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "peer submit failed"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, map[string]bool{"submitted": true})
|
|
||||||
}
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
// ollie-httpgw - HTTP gateway for ollie session management.
|
|
||||||
//
|
|
||||||
// Translates HTTP requests to D-Bus calls against org.ollie.SessionManager.
|
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// ollie-httpgw [start|fgstart|stop|status]
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed openapi.json
|
|
||||||
var openapiSpec []byte
|
|
||||||
|
|
||||||
var listen = flag.String("listen", ":8011", "HTTP listen address")
|
|
||||||
|
|
||||||
const pidFile = "ollie-httpgw.pid"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
args := os.Args[1:]
|
|
||||||
|
|
||||||
if len(args) >= 1 {
|
|
||||||
subcmd := args[0]
|
|
||||||
switch subcmd {
|
|
||||||
case "start", "fgstart", "stop", "status":
|
|
||||||
flag.CommandLine.Parse(args[1:]) //nolint:errcheck
|
|
||||||
ns := os.Getenv("NAMESPACE")
|
|
||||||
if ns == "" {
|
|
||||||
ns = fmt.Sprintf("/tmp/ns.%s.:0", os.Getenv("USER"))
|
|
||||||
}
|
|
||||||
pidPath := filepath.Join(ns, pidFile)
|
|
||||||
switch subcmd {
|
|
||||||
case "start":
|
|
||||||
if gwRunning() {
|
|
||||||
fmt.Println("ollie-httpgw already running")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
daemonize()
|
|
||||||
case "fgstart":
|
|
||||||
if gwRunning() {
|
|
||||||
fmt.Println("ollie-httpgw already running")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
runGateway(pidPath)
|
|
||||||
case "stop":
|
|
||||||
stopGateway(pidPath)
|
|
||||||
case "status":
|
|
||||||
if gwRunning() {
|
|
||||||
fmt.Println("ollie-httpgw running")
|
|
||||||
} else {
|
|
||||||
fmt.Println("ollie-httpgw not running")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No subcommand — run in foreground directly (no pid file).
|
|
||||||
flag.CommandLine.Parse(args) //nolint:errcheck
|
|
||||||
serve()
|
|
||||||
}
|
|
||||||
|
|
||||||
func gwRunning() bool {
|
|
||||||
conn, err := net.Dial("tcp", *listen)
|
|
||||||
if err == nil {
|
|
||||||
conn.Close()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func daemonize() {
|
|
||||||
exe, _ := os.Executable()
|
|
||||||
args := []string{"fgstart", "-listen", *listen}
|
|
||||||
cmd := exec.Command(exe, args...)
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed to start: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("ollie-httpgw started (pid %d)\n", cmd.Process.Pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopGateway(pidPath string) {
|
|
||||||
data, err := os.ReadFile(pidPath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("ollie-httpgw not running")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var pid int
|
|
||||||
fmt.Sscanf(string(data), "%d", &pid)
|
|
||||||
if pid > 0 {
|
|
||||||
syscall.Kill(pid, syscall.SIGTERM) //nolint:errcheck
|
|
||||||
}
|
|
||||||
os.Remove(pidPath) //nolint:errcheck
|
|
||||||
fmt.Println("ollie-httpgw stopped")
|
|
||||||
}
|
|
||||||
|
|
||||||
func runGateway(pidPath string) {
|
|
||||||
os.WriteFile(pidPath, fmt.Appendf(nil, "%d", os.Getpid()), 0644) //nolint:errcheck
|
|
||||||
serve()
|
|
||||||
os.Remove(pidPath) //nolint:errcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
func serve() {
|
|
||||||
http.HandleFunc("/openapi.json", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Write(openapiSpec)
|
|
||||||
})
|
|
||||||
|
|
||||||
http.HandleFunc("/", dbusHandler)
|
|
||||||
|
|
||||||
log.Printf("ollie-httpgw listening on %s", *listen)
|
|
||||||
log.Fatal(http.ListenAndServe(*listen, nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeJSON(w http.ResponseWriter, status int, v any) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(status)
|
|
||||||
json.NewEncoder(w).Encode(v)
|
|
||||||
}
|
|
||||||
|
|
@ -1,366 +0,0 @@
|
||||||
{
|
|
||||||
"openapi": "3.0.3",
|
|
||||||
"info": {
|
|
||||||
"title": "ollie-httpgw",
|
|
||||||
"description": "HTTP gateway for ollie session management. Supports D-Bus (default) and 9P backend modes.",
|
|
||||||
"version": "2.0.0"
|
|
||||||
},
|
|
||||||
"servers": [
|
|
||||||
{ "url": "http://localhost:8011", "description": "Default local gateway" }
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"/sessions": {
|
|
||||||
"get": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "List sessions",
|
|
||||||
"description": "Returns all active sessions with their id, state, model, and agent.",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Session list",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": { "$ref": "#/components/schemas/SessionSummary" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"502": { "$ref": "#/components/responses/Error" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"post": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Create a new session",
|
|
||||||
"requestBody": {
|
|
||||||
"required": false,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": { "$ref": "#/components/schemas/CreateSessionRequest" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"201": {
|
|
||||||
"description": "Session created",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": { "id": { "type": "string" } },
|
|
||||||
"required": ["id"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": { "$ref": "#/components/responses/Error" },
|
|
||||||
"502": { "$ref": "#/components/responses/Error" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}": {
|
|
||||||
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
||||||
"get": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Get session config/detail",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Session config as key-value object",
|
|
||||||
"content": { "application/json": { "schema": { "type": "object", "additionalProperties": { "type": "string" } } } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Kill session",
|
|
||||||
"responses": {
|
|
||||||
"204": { "description": "Session killed" },
|
|
||||||
"404": { "$ref": "#/components/responses/Error" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"patch": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Rename session",
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": { "name": { "type": "string" } },
|
|
||||||
"required": ["name"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"204": { "description": "Renamed" },
|
|
||||||
"404": { "$ref": "#/components/responses/Error" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}/submit": {
|
|
||||||
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
||||||
"post": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Submit a prompt to the session",
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": { "prompt": { "type": "string" } },
|
|
||||||
"required": ["prompt"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "Submitted", "content": { "application/json": { "schema": { "type": "object", "properties": { "submitted": { "type": "boolean" } } } } } },
|
|
||||||
"404": { "$ref": "#/components/responses/Error" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}/interrupt": {
|
|
||||||
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
||||||
"post": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Interrupt the running session",
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "Interrupted", "content": { "application/json": { "schema": { "type": "object", "properties": { "interrupted": { "type": "boolean" } } } } } },
|
|
||||||
"404": { "$ref": "#/components/responses/Error" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}/state": {
|
|
||||||
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
||||||
"get": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Get session state",
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "State", "content": { "application/json": { "schema": { "type": "object", "properties": { "state": { "type": "string" } } } } } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}/chat": {
|
|
||||||
"parameters": [
|
|
||||||
{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
|
|
||||||
{ "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "format": "int64", "default": 0 }, "description": "Byte offset to resume from" }
|
|
||||||
],
|
|
||||||
"get": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Get chat transcript",
|
|
||||||
"description": "Returns chat text from the given offset. The X-Chat-Offset response header contains the new offset for subsequent requests.",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Chat text",
|
|
||||||
"headers": {
|
|
||||||
"X-Chat-Offset": { "schema": { "type": "integer", "format": "int64" }, "description": "New byte offset for next poll" }
|
|
||||||
},
|
|
||||||
"content": { "text/plain": { "schema": { "type": "string" } } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}/usage": {
|
|
||||||
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
||||||
"get": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Get token usage",
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "Usage string", "content": { "application/json": { "schema": { "type": "object", "properties": { "usage": { "type": "string" } } } } } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}/cost": {
|
|
||||||
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
||||||
"get": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Get session cost",
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "Cost string", "content": { "application/json": { "schema": { "type": "object", "properties": { "cost": { "type": "string" } } } } } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}/config": {
|
|
||||||
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
||||||
"get": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Get session config",
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "Config as key-value", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"post": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Set a config value",
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"key": { "type": "string" },
|
|
||||||
"value": { "type": "string" }
|
|
||||||
},
|
|
||||||
"required": ["key", "value"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "Success", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } } } } },
|
|
||||||
"400": { "$ref": "#/components/responses/Error" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}/context": {
|
|
||||||
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
||||||
"get": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Get message context (JSONL)",
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "JSONL messages", "content": { "application/x-ndjson": { "schema": { "type": "string" } } } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}/models": {
|
|
||||||
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
||||||
"get": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "List models for session's backend",
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "Model list", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string" } } } } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}/peers": {
|
|
||||||
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
||||||
"get": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "List peers",
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "Peer IDs", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string" } } } } }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"post": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Add a peer",
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": { "peer_id": { "type": "string" } },
|
|
||||||
"required": ["peer_id"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"201": { "description": "Peer added" },
|
|
||||||
"400": { "$ref": "#/components/responses/Error" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}/peers/{peer_id}": {
|
|
||||||
"parameters": [
|
|
||||||
{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
|
|
||||||
{ "name": "peer_id", "in": "path", "required": true, "schema": { "type": "string" } }
|
|
||||||
],
|
|
||||||
"delete": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Remove a peer",
|
|
||||||
"responses": {
|
|
||||||
"204": { "description": "Peer removed" },
|
|
||||||
"404": { "$ref": "#/components/responses/Error" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/sessions/{id}/peers/{peer_id}/submit": {
|
|
||||||
"parameters": [
|
|
||||||
{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
|
|
||||||
{ "name": "peer_id", "in": "path", "required": true, "schema": { "type": "string" } }
|
|
||||||
],
|
|
||||||
"post": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "Submit prompt to a peer session",
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": { "prompt": { "type": "string" } },
|
|
||||||
"required": ["prompt"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "Submitted" },
|
|
||||||
"404": { "$ref": "#/components/responses/Error" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/backends": {
|
|
||||||
"get": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "List available backends",
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "Backend names", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string" } } } } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/agents": {
|
|
||||||
"get": {
|
|
||||||
"tags": ["dbus"],
|
|
||||||
"summary": "List available agents",
|
|
||||||
"responses": {
|
|
||||||
"200": { "description": "Agent names", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string" } } } } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"schemas": {
|
|
||||||
"SessionSummary": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"id": { "type": "string" },
|
|
||||||
"state": { "type": "string" },
|
|
||||||
"model": { "type": "string" },
|
|
||||||
"agent": { "type": "string" }
|
|
||||||
},
|
|
||||||
"required": ["id", "state", "model", "agent"]
|
|
||||||
},
|
|
||||||
"CreateSessionRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"cwd": { "type": "string", "description": "Working directory (defaults to $HOME)" },
|
|
||||||
"backend": { "type": "string", "description": "Backend name (e.g. ollama, anthropic)" },
|
|
||||||
"model": { "type": "string", "description": "Model name" },
|
|
||||||
"agent": { "type": "string", "description": "Agent name (default: 'default')" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Error": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": { "error": { "type": "string" } },
|
|
||||||
"required": ["error"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"Error": {
|
|
||||||
"description": "Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": { "$ref": "#/components/schemas/Error" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +1,15 @@
|
||||||
# ollie Architecture
|
# ollie Architecture
|
||||||
|
|
||||||
## Philosophy
|
## Philosophy
|
||||||
|
|
||||||
ollie's design philosophy is Emacs: a small, extensible core. The Go runtime (`agent.Agent`) defines what an agent *is* — an event loop, a backend connection, a message history, and three built-in primitives (`shell`, tool registry, skill registry). Everything else — orchestration, scheduling, workflows, UIs — is pushed out to the surrounding environment.
|
ollie's design philosophy is Emacs: a small, extensible core. The Go runtime (`agent.Agent`) defines what an agent *is* — an event loop, a backend connection, a message history, and three built-in primitives (`shell`, tool registry, skill registry). Everything else — orchestration, scheduling, workflows, UIs — is pushed out to the surrounding environment.
|
||||||
|
|
||||||
The primary integration philosophy is Plan 9's "everything is a file." `olliesrv` exposes agent state and behaviors as files in a 9P namespace. Any program that can read and write files can drive an agent: shell scripts, editors, web apps, cron, containers.
|
The primary integration philosophy is Plan 9's "everything is a file." `olliesrv` exposes agent state and behaviors as files in a 9P namespace. Any program that can read and write files can drive an agent: shell scripts, editors, web apps, cron, containers.
|
||||||
|
|
||||||
For desktop-native integration, `olliesrv` embeds a D-Bus adapter (`org.ollie.SessionManager`) alongside its 9P interface. The KDE GUI, plasmoid, and web UI connect this way. For D-Bus-only deployments, `-no9p` disables the 9P listener entirely.
|
For desktop-native integration, `olliesrv` embeds a D-Bus adapter (`org.ollie.SessionManager`) alongside its 9P interface. The KDE GUI, plasmoid, and web UI connect this way. For D-Bus-only deployments, `-no9p` disables the 9P listener entirely.
|
||||||
|
|
||||||
Design principles:
|
Design principles:
|
||||||
- **Small extensible core.** The agent runtime is minimal; capabilities come from composing external scripts.
|
- **Small extensible core.** The agent runtime is minimal; capabilities come from composing external scripts.
|
||||||
- **Three built-in primitives.** `shell`, tool registry (`tool_load`/`tool_list`/`tool_active`), and skill registry (`skill_load`/`skill_list`/`skill_active`) are the only tools compiled into the core. Everything else is a script.
|
- **Three built-in primitives.** `shell`, tool registry (`tool_load`/`tool_list`/`tool_active`), and skill registry (`skill_load`/`skill_list`/`skill_active`) are the only tools compiled into the core. Everything else is a script.
|
||||||
- **Two integration paths.** 9P filesystem (canonical) and/or D-Bus (conventional). Same core, different surfaces, selectable via flags.
|
- **Two integration paths.** 9P filesystem (canonical) and/or D-Bus (conventional). Same core, different surfaces, selectable via flags.
|
||||||
- **No framework lock-in.** Frontends are decoupled via whichever interface they prefer; the core doesn't know or care.
|
- **No framework lock-in.** Frontends are decoupled via whichever interface they prefer; the core doesn't know or care.
|
||||||
|
|
||||||
## Repository Structure
|
## Repository Structure
|
||||||
|
|
||||||
Single Go module (`ollie`) with two Git submodules for decoupled frontends:
|
Single Go module (`ollie`) with two Git submodules for decoupled frontends:
|
||||||
|
|
||||||
```
|
```
|
||||||
ollie/
|
ollie/
|
||||||
├── agent/ Agent loop, history, hooks, prompt resolution, commands
|
├── agent/ Agent loop, history, hooks, prompt resolution, commands
|
||||||
|
|
@ -36,10 +28,8 @@ ollie/
|
||||||
├── cmd/ Binaries:
|
├── cmd/ Binaries:
|
||||||
│ ├── olliesrv/ 9P server
|
│ ├── olliesrv/ 9P server
|
||||||
│ ├── ollie-9p/ 9P client
|
│ ├── ollie-9p/ 9P client
|
||||||
│ ├── ollie-httpgw/ HTTP-to-9P gateway
|
|
||||||
│ └── ollie-remote/ Remote execution server
|
│ └── ollie-remote/ Remote execution server
|
||||||
├── kde/ KDE integration (submodule) — plasmoid, GUI, Kate plugin, KRunner, tray
|
├── kde/ KDE integration (submodule) — plasmoid, GUI, Kate plugin, KRunner, tray
|
||||||
├── webui/ Preact SPA browser frontend (submodule)
|
|
||||||
├── agents/ Agent config JSONs (default, coding, orchestrator, worker, ...)
|
├── agents/ Agent config JSONs (default, coding, orchestrator, worker, ...)
|
||||||
├── prompts/ Prompt templates (markdown)
|
├── prompts/ Prompt templates (markdown)
|
||||||
├── tools/ Tool scripts (file_read, lsp_*, memory_*, subagent_*, ...)
|
├── tools/ Tool scripts (file_read, lsp_*, memory_*, subagent_*, ...)
|
||||||
|
|
@ -48,9 +38,7 @@ ollie/
|
||||||
├── sandbox/ Sandbox config YAML files
|
├── sandbox/ Sandbox config YAML files
|
||||||
└── doc/ Documentation
|
└── doc/ Documentation
|
||||||
```
|
```
|
||||||
|
|
||||||
## System Overview
|
## System Overview
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
subgraph Frontends["Frontends"]
|
subgraph Frontends["Frontends"]
|
||||||
|
|
@ -58,27 +46,22 @@ flowchart TB
|
||||||
ACME["acme (Plan 9)"]
|
ACME["acme (Plan 9)"]
|
||||||
ELLIE["ellie (Emacs)"]
|
ELLIE["ellie (Emacs)"]
|
||||||
KDE["KDE GUI / Plasmoid"]
|
KDE["KDE GUI / Plasmoid"]
|
||||||
WEB["Web UI"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Integration["Integration Layer"]
|
subgraph Integration["Integration Layer"]
|
||||||
direction LR
|
direction LR
|
||||||
P9["9P Filesystem"]
|
P9["9P Filesystem"]
|
||||||
DBUS["org.ollie.SessionManager (D-Bus)"]
|
DBUS["org.ollie.SessionManager (D-Bus)"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Server["olliesrv"]
|
subgraph Server["olliesrv"]
|
||||||
direction TB
|
direction TB
|
||||||
NS["9P Namespace\nsession/ · backends · models"]
|
NS["9P Namespace\nsession/ · backends · models"]
|
||||||
DBA["Embedded D-Bus Adapter"]
|
DBA["Embedded D-Bus Adapter"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Core["Agent Engine (per session)"]
|
subgraph Core["Agent Engine (per session)"]
|
||||||
LOOP["Agent Loop\n(agent/loop.go)"]
|
LOOP["Agent Loop\n(agent/loop.go)"]
|
||||||
SESS["Session State\n(agent/history.go)"]
|
SESS["Session State\n(agent/history.go)"]
|
||||||
TRV["toolsrv.Server\n· shell execution\n· tool registry\n· landlock sandbox"]
|
TRV["toolsrv.Server\n· shell execution\n· tool registry\n· landlock sandbox"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Backends["LLM Backends"]
|
subgraph Backends["LLM Backends"]
|
||||||
OLLAMA["Ollama"]
|
OLLAMA["Ollama"]
|
||||||
OPENAI["OpenAI / OpenRouter"]
|
OPENAI["OpenAI / OpenRouter"]
|
||||||
|
|
@ -86,33 +69,24 @@ flowchart TB
|
||||||
GEMINI["Gemini"]
|
GEMINI["Gemini"]
|
||||||
CW["CodeWhisperer"]
|
CW["CodeWhisperer"]
|
||||||
end
|
end
|
||||||
|
|
||||||
TUI --> P9
|
TUI --> P9
|
||||||
ACME --> P9
|
ACME --> P9
|
||||||
ELLIE --> P9
|
ELLIE --> P9
|
||||||
KDE --> DBUS
|
KDE --> DBUS
|
||||||
WEB --> DBUS
|
WEB --> DBUS
|
||||||
|
|
||||||
P9 --> NS
|
P9 --> NS
|
||||||
DBUS --> DBA
|
DBUS --> DBA
|
||||||
|
|
||||||
NS --> LOOP
|
NS --> LOOP
|
||||||
DBA --> LOOP
|
DBA --> LOOP
|
||||||
|
|
||||||
LOOP <--> SESS
|
LOOP <--> SESS
|
||||||
LOOP --> TRV
|
LOOP --> TRV
|
||||||
LOOP <--> Backends
|
LOOP <--> Backends
|
||||||
|
|
||||||
TRV --> Shell["sandboxed shell\n(landrun)"]
|
TRV --> Shell["sandboxed shell\n(landrun)"]
|
||||||
TRV --> Tools["tool scripts\n(OLLIE_TOOLS_PATH)"]
|
TRV --> Tools["tool scripts\n(OLLIE_TOOLS_PATH)"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Core Library
|
## Core Library
|
||||||
|
|
||||||
The core is a single Go module (`ollie`) with no binary. Binaries live in `cmd/`.
|
The core is a single Go module (`ollie`) with no binary. Binaries live in `cmd/`.
|
||||||
|
|
||||||
### Package Layout
|
### Package Layout
|
||||||
|
|
||||||
| Package | Purpose |
|
| Package | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `agent/` | Agent struct, loop, history, compaction, hooks, commands, prompt resolution, state |
|
| `agent/` | Agent struct, loop, history, compaction, hooks, commands, prompt resolution, state |
|
||||||
|
|
@ -125,15 +99,12 @@ The core is a single Go module (`ollie`) with no binary. Binaries live in `cmd/`
|
||||||
| `env/` | Session environment helpers |
|
| `env/` | Session environment helpers |
|
||||||
| `log/` | Structured logging |
|
| `log/` | Structured logging |
|
||||||
| `paths/` | XDG path resolution |
|
| `paths/` | XDG path resolution |
|
||||||
|
|
||||||
### Key Types
|
### Key Types
|
||||||
|
|
||||||
**`agent.Agent`** — the concrete struct frontends drive:
|
**`agent.Agent`** — the concrete struct frontends drive:
|
||||||
```go
|
```go
|
||||||
type Agent struct {
|
type Agent struct {
|
||||||
// unexported fields — no public interface
|
// unexported fields — no public interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg AgentConfig) *Agent
|
func New(cfg AgentConfig) *Agent
|
||||||
func (a *Agent) Submit(ctx context.Context, input string)
|
func (a *Agent) Submit(ctx context.Context, input string)
|
||||||
func (a *Agent) Interrupt(cause error) bool
|
func (a *Agent) Interrupt(cause error) bool
|
||||||
|
|
@ -143,7 +114,6 @@ func (a *Agent) WaitChange(ctx context.Context, field, current string) (string,
|
||||||
func (a *Agent) Close()
|
func (a *Agent) Close()
|
||||||
// ... model/backend info, CWD, usage, cost, etc.
|
// ... model/backend info, CWD, usage, cost, etc.
|
||||||
```
|
```
|
||||||
|
|
||||||
**`backend.Backend`** — LLM provider contract:
|
**`backend.Backend`** — LLM provider contract:
|
||||||
```go
|
```go
|
||||||
type Backend interface {
|
type Backend interface {
|
||||||
|
|
@ -155,7 +125,6 @@ type Backend interface {
|
||||||
Models(ctx context.Context) []string
|
Models(ctx context.Context) []string
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**`toolsrv.Runner`** — minimal tool execution interface:
|
**`toolsrv.Runner`** — minimal tool execution interface:
|
||||||
```go
|
```go
|
||||||
type Runner interface {
|
type Runner interface {
|
||||||
|
|
@ -163,21 +132,16 @@ type Runner interface {
|
||||||
CallTool(ctx context.Context, tool string, args json.RawMessage) (json.RawMessage, error)
|
CallTool(ctx context.Context, tool string, args json.RawMessage) (json.RawMessage, error)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**`toolsrv.Server`** — the concrete tool server (implements Runner):
|
**`toolsrv.Server`** — the concrete tool server (implements Runner):
|
||||||
```go
|
```go
|
||||||
type Server struct { /* ... */ }
|
type Server struct { /* ... */ }
|
||||||
```
|
```
|
||||||
|
|
||||||
### Agent Loop (`agent/loop.go`)
|
### Agent Loop (`agent/loop.go`)
|
||||||
|
|
||||||
The loop is the heart of the system. On each turn:
|
The loop is the heart of the system. On each turn:
|
||||||
|
|
||||||
1. **Stream** — call `backend.ChatStream()` with messages + tool definitions
|
1. **Stream** — call `backend.ChatStream()` with messages + tool definitions
|
||||||
2. **Dispatch** — if the model returns tool calls, execute them via `toolsrv.Server`
|
2. **Dispatch** — if the model returns tool calls, execute them via `toolsrv.Server`
|
||||||
3. **Update** — append assistant message + tool results to session history
|
3. **Update** — append assistant message + tool results to session history
|
||||||
4. **Repeat** — loop until the model produces a final text response (no tool calls)
|
4. **Repeat** — loop until the model produces a final text response (no tool calls)
|
||||||
|
|
||||||
Safety mechanisms:
|
Safety mechanisms:
|
||||||
- **Consecutive error limits**: soft nudge at 5, hard abort at 10
|
- **Consecutive error limits**: soft nudge at 5, hard abort at 10
|
||||||
- **Replan gate**: after 8 rounds without a PLAN: block, inject a replan nudge
|
- **Replan gate**: after 8 rounds without a PLAN: block, inject a replan nudge
|
||||||
|
|
@ -185,11 +149,8 @@ Safety mechanisms:
|
||||||
- **Max steps**: configurable per-agent; soft exit when reached
|
- **Max steps**: configurable per-agent; soft exit when reached
|
||||||
- **Transient retry**: up to 3 retries with exponential backoff for rate limits and 5xx
|
- **Transient retry**: up to 3 retries with exponential backoff for rate limits and 5xx
|
||||||
- **Context overflow**: triggers automatic compaction and retry
|
- **Context overflow**: triggers automatic compaction and retry
|
||||||
|
|
||||||
### Session & Context Management (`agent/history.go`)
|
### Session & Context Management (`agent/history.go`)
|
||||||
|
|
||||||
`History` owns the message history as a flat `[]backend.Message` slice. Key behaviors:
|
`History` owns the message history as a flat `[]backend.Message` slice. Key behaviors:
|
||||||
|
|
||||||
- **TaskState**: structured JSON overlay (objective, plan_step, constraints, last_action, next_decision) injected at the top of every turn
|
- **TaskState**: structured JSON overlay (objective, plan_step, constraints, last_action, next_decision) injected at the top of every turn
|
||||||
- **Compaction**: three-zone strategy when context reaches 50% of model limit:
|
- **Compaction**: three-zone strategy when context reaches 50% of model limit:
|
||||||
- **Cold zone**: structured task state summary
|
- **Cold zone**: structured task state summary
|
||||||
|
|
@ -197,11 +158,8 @@ Safety mechanisms:
|
||||||
- **Hot zone**: last 8 messages verbatim
|
- **Hot zone**: last 8 messages verbatim
|
||||||
- **Result tiers**: tool results classified as Hot (verbatim), Warm (summarized on compaction), or Cold (immediately collapsed)
|
- **Result tiers**: tool results classified as Hot (verbatim), Warm (summarized on compaction), or Cold (immediately collapsed)
|
||||||
- **Persistence**: sessions saved as JSON to `{sessionsDir}/{id}.json`
|
- **Persistence**: sessions saved as JSON to `{sessionsDir}/{id}.json`
|
||||||
|
|
||||||
### Hooks (`agent/hooks.go`)
|
### Hooks (`agent/hooks.go`)
|
||||||
|
|
||||||
Lifecycle callbacks executed as shell commands with JSON payload on stdin:
|
Lifecycle callbacks executed as shell commands with JSON payload on stdin:
|
||||||
|
|
||||||
| Hook | When | Exit codes |
|
| Hook | When | Exit codes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `agentSpawn` | Session creation | 0=ok, 2=block |
|
| `agentSpawn` | Session creation | 0=ok, 2=block |
|
||||||
|
|
@ -212,17 +170,11 @@ Lifecycle callbacks executed as shell commands with JSON payload on stdin:
|
||||||
| `preCompact` | Before compaction | 0=ok |
|
| `preCompact` | Before compaction | 0=ok |
|
||||||
| `postCompact` | After compaction | 0=ok |
|
| `postCompact` | After compaction | 0=ok |
|
||||||
| `turnError` | On backend error | 0=handled (skip retries) |
|
| `turnError` | On backend error | 0=handled (skip retries) |
|
||||||
|
|
||||||
### Prompt Resolution (`agent/prompt_resolver.go`)
|
### Prompt Resolution (`agent/prompt_resolver.go`)
|
||||||
|
|
||||||
Agent configs declare prompts as a JSON array of shell commands. Each command is executed with `$OLLIE` and `$PWD` available; stdout is concatenated to form the system prompt. This enables dynamic, composable prompts assembled from template fragments.
|
Agent configs declare prompts as a JSON array of shell commands. Each command is executed with `$OLLIE` and `$PWD` available; stdout is concatenated to form the system prompt. This enables dynamic, composable prompts assembled from template fragments.
|
||||||
|
|
||||||
## 9P Server (`cmd/olliesrv/`)
|
## 9P Server (`cmd/olliesrv/`)
|
||||||
|
|
||||||
`olliesrv` is the central daemon. It implements a 9P2000 file server that exposes the entire agent namespace:
|
`olliesrv` is the central daemon. It implements a 9P2000 file server that exposes the entire agent namespace:
|
||||||
|
|
||||||
### Namespace Layout
|
### Namespace Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
session/
|
session/
|
||||||
├── new write key=value to create session
|
├── new write key=value to create session
|
||||||
|
|
@ -261,24 +213,18 @@ session/
|
||||||
│ ├── tail exec helper
|
│ ├── tail exec helper
|
||||||
│ └── proc/ detached background processes
|
│ └── proc/ detached background processes
|
||||||
```
|
```
|
||||||
|
|
||||||
### 9P Interaction Model
|
### 9P Interaction Model
|
||||||
|
|
||||||
All agent interaction uses the `ollie-9p` client, which auto-discovers the server via `$NAMESPACE` and identifies the caller via `$OLLIE_UNAME`. The 9P filesystem exposes session state, control, and inter-agent communication as files:
|
All agent interaction uses the `ollie-9p` client, which auto-discovers the server via `$NAMESPACE` and identifies the caller via `$OLLIE_UNAME`. The 9P filesystem exposes session state, control, and inter-agent communication as files:
|
||||||
- Sessions are directories under `session/`
|
- Sessions are directories under `session/`
|
||||||
- Prompts, state, chat history, and config are readable/writable files
|
- Prompts, state, chat history, and config are readable/writable files
|
||||||
- Permission enforcement uses 9P user principals
|
- Permission enforcement uses 9P user principals
|
||||||
|
|
||||||
### Permission Model (`fs/session/perm.go`)
|
### Permission Model (`fs/session/perm.go`)
|
||||||
|
|
||||||
All permissions are declared in a single registry (`Perms`). Key design:
|
All permissions are declared in a single registry (`Perms`). Key design:
|
||||||
- `prompt` is mode `0666` — group+other can write, owner (the agent) cannot
|
- `prompt` is mode `0666` — group+other can write, owner (the agent) cannot
|
||||||
- `chat` is mode `0444` — read-only for everyone
|
- `chat` is mode `0444` — read-only for everyone
|
||||||
- `ctl` is mode `0666` — anyone can send control commands
|
- `ctl` is mode `0666` — anyone can send control commands
|
||||||
- `plan` is mode `0666` — readable by peer agents
|
- `plan` is mode `0666` — readable by peer agents
|
||||||
|
|
||||||
### Session Management (`fs/session/`)
|
### Session Management (`fs/session/`)
|
||||||
|
|
||||||
The `*fs.Tree` IS the session collection. Package functions manage lifecycle:
|
The `*fs.Tree` IS the session collection. Package functions manage lifecycle:
|
||||||
- `NewRoot(cfg)` — create the root tree
|
- `NewRoot(cfg)` — create the root tree
|
||||||
- `Lookup(tree, id)` — find a session by ID
|
- `Lookup(tree, id)` — find a session by ID
|
||||||
|
|
@ -287,25 +233,16 @@ The `*fs.Tree` IS the session collection. Package functions manage lifecycle:
|
||||||
- `KillFromRoot(tree, id)` — kill a session
|
- `KillFromRoot(tree, id)` — kill a session
|
||||||
- `RenameFromRoot(tree, old, new)` — rename a session
|
- `RenameFromRoot(tree, old, new)` — rename a session
|
||||||
- `Shutdown(tree)` — clean shutdown
|
- `Shutdown(tree)` — clean shutdown
|
||||||
|
|
||||||
### Embedded D-Bus Adapter
|
### Embedded D-Bus Adapter
|
||||||
|
|
||||||
`olliesrv` includes an embedded D-Bus adapter that claims `org.ollie.SessionManager` on the session bus. This provides:
|
`olliesrv` includes an embedded D-Bus adapter that claims `org.ollie.SessionManager` on the session bus. This provides:
|
||||||
- The same method/signal interface used by KDE frontends
|
- The same method/signal interface used by KDE frontends
|
||||||
- `SessionCreated`, `SessionKilled`, `StateChanged`, `ChatUpdated` signals
|
- `SessionCreated`, `SessionKilled`, `StateChanged`, `ChatUpdated` signals
|
||||||
- Allows KDE GUI, plasmoid, and web UI (via `httpgw`) to interact with sessions without speaking 9P
|
|
||||||
- Lifecycle watchers that bridge session state changes to D-Bus signals
|
- Lifecycle watchers that bridge session state changes to D-Bus signals
|
||||||
|
|
||||||
The adapter is best-effort: if no session bus is available (e.g., headless/container), `olliesrv` continues functioning as a pure 9P server.
|
The adapter is best-effort: if no session bus is available (e.g., headless/container), `olliesrv` continues functioning as a pure 9P server.
|
||||||
|
|
||||||
## D-Bus Adapter (`dbus/`)
|
## D-Bus Adapter (`dbus/`)
|
||||||
|
|
||||||
The D-Bus adapter is embedded in `olliesrv` (no separate daemon). For D-Bus-only deployments, run `olliesrv -no9p`. This disables the filesystem listener while keeping the full D-Bus interface. Session persistence uses the same `~/.local/share/ollie/sessions/active/` directory.
|
The D-Bus adapter is embedded in `olliesrv` (no separate daemon). For D-Bus-only deployments, run `olliesrv -no9p`. This disables the filesystem listener while keeping the full D-Bus interface. Session persistence uses the same `~/.local/share/ollie/sessions/active/` directory.
|
||||||
|
|
||||||
## KDE Integration (`kde/`)
|
## KDE Integration (`kde/`)
|
||||||
|
|
||||||
The KDE submodule provides several Qt/QML components that connect to `org.ollie.SessionManager` over D-Bus:
|
The KDE submodule provides several Qt/QML components that connect to `org.ollie.SessionManager` over D-Bus:
|
||||||
|
|
||||||
| Component | Path | Purpose |
|
| Component | Path | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| GUI | `kde/gui/` | Standalone Qt window for chat interaction |
|
| GUI | `kde/gui/` | Standalone Qt window for chat interaction |
|
||||||
|
|
@ -313,27 +250,18 @@ The KDE submodule provides several Qt/QML components that connect to `org.ollie.
|
||||||
| Kate plugin | `kde/kate/` | In-editor chat pane and ghost completion |
|
| Kate plugin | `kde/kate/` | In-editor chat pane and ghost completion |
|
||||||
| KRunner | `kde/krunner/` | Quick-launch sessions from KRunner |
|
| KRunner | `kde/krunner/` | Quick-launch sessions from KRunner |
|
||||||
| System tray | `kde/tray/` | Tray icon with session status |
|
| System tray | `kde/tray/` | Tray icon with session status |
|
||||||
|
|
||||||
All KDE components use `QDBusServiceWatcher` to handle daemon restarts gracefully — recreating the `QDBusInterface` when the service reappears and refreshing the session list.
|
All KDE components use `QDBusServiceWatcher` to handle daemon restarts gracefully — recreating the `QDBusInterface` when the service reappears and refreshing the session list.
|
||||||
|
|
||||||
## Tool System
|
## Tool System
|
||||||
|
|
||||||
### Architecture
|
### Architecture
|
||||||
|
|
||||||
The tool system has exactly one built-in server (`toolsrv.Server`) that exposes three primitives:
|
The tool system has exactly one built-in server (`toolsrv.Server`) that exposes three primitives:
|
||||||
|
|
||||||
| Primitive | Purpose |
|
| Primitive | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `shell` | Run a bash command in a sandbox |
|
| `shell` | Run a bash command in a sandbox |
|
||||||
| `tool_load`/`tool_list`/`tool_active` | Dynamic tool registry |
|
| `tool_load`/`tool_list`/`tool_active` | Dynamic tool registry |
|
||||||
| `skill_load`/`skill_list`/`skill_active` | Dynamic skill registry |
|
| `skill_load`/`skill_list`/`skill_active` | Dynamic skill registry |
|
||||||
|
|
||||||
All other capabilities are **tool scripts** — executable files discovered from `OLLIE_TOOLS_PATH` (default: `~/.config/ollie/tools/`).
|
All other capabilities are **tool scripts** — executable files discovered from `OLLIE_TOOLS_PATH` (default: `~/.config/ollie/tools/`).
|
||||||
|
|
||||||
### Tool Scripts
|
### Tool Scripts
|
||||||
|
|
||||||
Tool scripts are plain executables with a structured header comment declaring their name, description, parameters, and parallelism annotation:
|
Tool scripts are plain executables with a structured header comment declaring their name, description, parameters, and parallelism annotation:
|
||||||
|
|
||||||
- `file_read`, `file_write`, `file_edit`, `file_glob`, `file_grep` — filesystem I/O
|
- `file_read`, `file_write`, `file_edit`, `file_glob`, `file_grep` — filesystem I/O
|
||||||
- `lsp_definition`, `lsp_references`, `lsp_rename`, `lsp_symbols`, `lsp_diagnostics`, `lsp_hover`, `lsp_completion` — LSP bridge (Python)
|
- `lsp_definition`, `lsp_references`, `lsp_rename`, `lsp_symbols`, `lsp_diagnostics`, `lsp_hover`, `lsp_completion` — LSP bridge (Python)
|
||||||
- `memory_remember`, `memory_recall` — persistent memory
|
- `memory_remember`, `memory_recall` — persistent memory
|
||||||
|
|
@ -341,13 +269,9 @@ Tool scripts are plain executables with a structured header comment declaring th
|
||||||
- `subagent_spawn`, `subagent_generate` — sub-agent lifecycle
|
- `subagent_spawn`, `subagent_generate` — sub-agent lifecycle
|
||||||
- `route` — orchestrator dispatch
|
- `route` — orchestrator dispatch
|
||||||
- `gui_*` — KDE desktop automation (screenshot, windows, clipboard, input, ...)
|
- `gui_*` — KDE desktop automation (screenshot, windows, clipboard, input, ...)
|
||||||
|
|
||||||
Scripts annotated `ollie:parallel read` can be fanned out concurrently by the agent loop.
|
Scripts annotated `ollie:parallel read` can be fanned out concurrently by the agent loop.
|
||||||
|
|
||||||
### Sandboxing
|
### Sandboxing
|
||||||
|
|
||||||
Every code execution step is wrapped with [landrun](https://github.com/landlock-lsm/landrun) (Landlock LSM). Configuration is YAML-based:
|
Every code execution step is wrapped with [landrun](https://github.com/landlock-lsm/landrun) (Landlock LSM). Configuration is YAML-based:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
general:
|
general:
|
||||||
best_effort: true
|
best_effort: true
|
||||||
|
|
@ -359,13 +283,9 @@ network:
|
||||||
enabled: true
|
enabled: true
|
||||||
unrestricted: true
|
unrestricted: true
|
||||||
```
|
```
|
||||||
|
|
||||||
Template variables (`{CWD}`, `{HOME}`, `{XDG_*}`, `{OLLIE_*}`) are expanded at runtime. **landrun is mandatory** — there is no unsandboxed fallback. Steps marked `elevated: true` bypass the sandbox via the elevation backend (`x/elevate`).
|
Template variables (`{CWD}`, `{HOME}`, `{XDG_*}`, `{OLLIE_*}`) are expanded at runtime. **landrun is mandatory** — there is no unsandboxed fallback. Steps marked `elevated: true` bypass the sandbox via the elevation backend (`x/elevate`).
|
||||||
|
|
||||||
## LLM Backends (`backend/`)
|
## LLM Backends (`backend/`)
|
||||||
|
|
||||||
All backends implement the streaming-only `Backend` interface:
|
All backends implement the streaming-only `Backend` interface:
|
||||||
|
|
||||||
| Backend | Wire Format | Notes |
|
| Backend | Wire Format | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `OllamaBackend` | `/api/chat` | Local models, context length from `/api/show` |
|
| `OllamaBackend` | `/api/chat` | Local models, context length from `/api/show` |
|
||||||
|
|
@ -374,63 +294,43 @@ All backends implement the streaming-only `Backend` interface:
|
||||||
| `GeminiBackend` | Gemini API | Google Gemini models |
|
| `GeminiBackend` | Gemini API | Google Gemini models |
|
||||||
| `CopilotBackend` | GitHub Copilot API | Copilot Chat |
|
| `CopilotBackend` | GitHub Copilot API | Copilot Chat |
|
||||||
| `CodeWhispererBackend` | CodeWhisperer API | AWS CodeWhisperer |
|
| `CodeWhispererBackend` | CodeWhisperer API | AWS CodeWhisperer |
|
||||||
|
|
||||||
Shared infrastructure:
|
Shared infrastructure:
|
||||||
- `streamRequest()` — common HTTP streaming with error classification
|
- `streamRequest()` — common HTTP streaming with error classification
|
||||||
- `RateLimitError`, `TransientError`, `ContextOverflowError`, `ToolUnsupportedError` — typed errors for loop control
|
- `RateLimitError`, `TransientError`, `ContextOverflowError`, `ToolUnsupportedError` — typed errors for loop control
|
||||||
- `GenerationParams` — unified sampling parameters across all backends
|
- `GenerationParams` — unified sampling parameters across all backends
|
||||||
|
|
||||||
## Multi-Agent System
|
## Multi-Agent System
|
||||||
|
|
||||||
Multi-agent coordination is built entirely on the filesystem primitive:
|
Multi-agent coordination is built entirely on the filesystem primitive:
|
||||||
|
|
||||||
### Mechanism
|
### Mechanism
|
||||||
|
|
||||||
1. **Spawn**: write session spec to `session/new` → new session directory appears
|
1. **Spawn**: write session spec to `session/new` → new session directory appears
|
||||||
2. **Prompt**: write task to `session/{id}/prompt`
|
2. **Prompt**: write task to `session/{id}/prompt`
|
||||||
3. **Observe**: read `session/{id}/state` or block on `session/{id}/statewait`
|
3. **Observe**: read `session/{id}/state` or block on `session/{id}/statewait`
|
||||||
4. **Result**: child writes to `session/{parent_id}/prompt` when done
|
4. **Result**: child writes to `session/{parent_id}/prompt` when done
|
||||||
|
|
||||||
### Delegation Tools
|
### Delegation Tools
|
||||||
|
|
||||||
| Tool | Semantics |
|
| Tool | Semantics |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `route` | Orchestrator dispatch; selects backend+model for a task |
|
| `route` | Orchestrator dispatch; selects backend+model for a task |
|
||||||
| `subagent_spawn` | Raw session creation with full control |
|
| `subagent_spawn` | Raw session creation with full control |
|
||||||
| `subagent_generate` | JIT agent identity generation |
|
| `subagent_generate` | JIT agent identity generation |
|
||||||
|
|
||||||
### Callback Protocol
|
### Callback Protocol
|
||||||
|
|
||||||
```
|
```
|
||||||
[from={session_id}]
|
[from={session_id}]
|
||||||
STATUS: done|error
|
STATUS: done|error
|
||||||
SUMMARY: <result>
|
SUMMARY: <result>
|
||||||
ARTIFACTS: <paths>
|
ARTIFACTS: <paths>
|
||||||
```
|
```
|
||||||
|
|
||||||
The parent never actively waits — results arrive as queued prompts.
|
The parent never actively waits — results arrive as queued prompts.
|
||||||
|
|
||||||
## HTTP Gateway (`cmd/ollie-httpgw/`)
|
|
||||||
|
|
||||||
`ollie-httpgw` bridges HTTP clients (like the web UI) to the 9P filesystem. It translates REST-style requests into 9P filesystem operations and streams events back as Server-Sent Events.
|
|
||||||
|
|
||||||
This is the secondary integration path for non-LLM consumers (web UIs, dashboards, external tools) that want a conventional HTTP API without speaking 9P directly.
|
This is the secondary integration path for non-LLM consumers (web UIs, dashboards, external tools) that want a conventional HTTP API without speaking 9P directly.
|
||||||
|
|
||||||
## Frontends
|
## Frontends
|
||||||
|
|
||||||
Frontends connect via either 9P (filesystem) or D-Bus, depending on preference:
|
Frontends connect via either 9P (filesystem) or D-Bus, depending on preference:
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
subgraph Core["agent.Agent"]
|
subgraph Core["agent.Agent"]
|
||||||
AG["Agent Engine"]
|
AG["Agent Engine"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Surfaces["Integration Surfaces"]
|
subgraph Surfaces["Integration Surfaces"]
|
||||||
P9["9P Filesystem\n(session/ namespace)"]
|
P9["9P Filesystem\n(session/ namespace)"]
|
||||||
DB["D-Bus\n(org.ollie.SessionManager)"]
|
DB["D-Bus\n(org.ollie.SessionManager)"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Frontends
|
subgraph Frontends
|
||||||
SH["s/sh (terminal)"]
|
SH["s/sh (terminal)"]
|
||||||
ACME["acme (Plan 9)"]
|
ACME["acme (Plan 9)"]
|
||||||
|
|
@ -439,41 +339,30 @@ flowchart LR
|
||||||
KP["KDE Plasmoid"]
|
KP["KDE Plasmoid"]
|
||||||
KK["Kate Plugin"]
|
KK["Kate Plugin"]
|
||||||
KR["KRunner"]
|
KR["KRunner"]
|
||||||
WEB["Web UI"]
|
|
||||||
HTTP["curl / scripts"]
|
HTTP["curl / scripts"]
|
||||||
end
|
end
|
||||||
|
|
||||||
AG --> P9
|
AG --> P9
|
||||||
AG --> DB
|
AG --> DB
|
||||||
|
|
||||||
SH --> P9
|
SH --> P9
|
||||||
ACME --> P9
|
ACME --> P9
|
||||||
EL --> P9
|
EL --> P9
|
||||||
HTTP --> P9
|
HTTP --> P9
|
||||||
|
|
||||||
KG --> DB
|
KG --> DB
|
||||||
KP --> DB
|
KP --> DB
|
||||||
KK --> DB
|
KK --> DB
|
||||||
KR --> DB
|
KR --> DB
|
||||||
WEB --> DB
|
WEB --> DB
|
||||||
```
|
```
|
||||||
|
|
||||||
Frontends are fully decoupled — the core doesn't know which interface they use.
|
Frontends are fully decoupled — the core doesn't know which interface they use.
|
||||||
|
|
||||||
## Script Namespaces
|
## Script Namespaces
|
||||||
|
|
||||||
User-facing scripts installed to `~/.config/ollie/scripts/{ns}/`:
|
User-facing scripts installed to `~/.config/ollie/scripts/{ns}/`:
|
||||||
|
|
||||||
| Namespace | Purpose | Key Scripts |
|
| Namespace | Purpose | Key Scripts |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `s/` | Session management | `sh` (interactive), `b` (one-shot), `bfg` (blocking), `bbg` (background) |
|
| `s/` | Session management | `sh` (interactive), `b` (one-shot), `bfg` (blocking), `bbg` (background) |
|
||||||
| `u/` | Utility compositions | `complete` (code completion), `optimize` (prompt optimization), `cascade`, `escalate` |
|
| `u/` | Utility compositions | `complete` (code completion), `optimize` (prompt optimization), `cascade`, `escalate` |
|
||||||
| `x/` | System plugins | `elevate` (privilege escalation), `prime` (prompt template renderer), `freeloader` |
|
| `x/` | System plugins | `elevate` (privilege escalation), `prime` (prompt template renderer), `freeloader` |
|
||||||
|
|
||||||
## Agent Configuration
|
## Agent Configuration
|
||||||
|
|
||||||
Agent configs are JSON files in `~/.config/ollie/agents/`:
|
Agent configs are JSON files in `~/.config/ollie/agents/`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"prompt": [
|
"prompt": [
|
||||||
|
|
@ -492,11 +381,8 @@ Agent configs are JSON files in `~/.config/ollie/agents/`:
|
||||||
"maxSteps": 50
|
"maxSteps": 50
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `prompt` array is the key innovation: each element is a shell command whose stdout is concatenated to form the system prompt. This makes prompts composable, dynamic, and environment-aware.
|
The `prompt` array is the key innovation: each element is a shell command whose stdout is concatenated to form the system prompt. This makes prompts composable, dynamic, and environment-aware.
|
||||||
|
|
||||||
## Data Flow
|
## Data Flow
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant F as Frontend
|
participant F as Frontend
|
||||||
|
|
@ -505,17 +391,14 @@ sequenceDiagram
|
||||||
participant B as LLM Backend
|
participant B as LLM Backend
|
||||||
participant T as toolsrv.Server
|
participant T as toolsrv.Server
|
||||||
participant SH as sandbox (landrun)
|
participant SH as sandbox (landrun)
|
||||||
|
|
||||||
F->>S: write to session/{id}/prompt
|
F->>S: write to session/{id}/prompt
|
||||||
S->>A: Submit()
|
S->>A: Submit()
|
||||||
activate A
|
activate A
|
||||||
|
|
||||||
loop Agent Loop
|
loop Agent Loop
|
||||||
A->>B: ChatStream(messages, tools)
|
A->>B: ChatStream(messages, tools)
|
||||||
activate B
|
activate B
|
||||||
B-->>A: stream events
|
B-->>A: stream events
|
||||||
deactivate B
|
deactivate B
|
||||||
|
|
||||||
alt has tool calls
|
alt has tool calls
|
||||||
A->>T: Dispatch(tool, args)
|
A->>T: Dispatch(tool, args)
|
||||||
activate T
|
activate T
|
||||||
|
|
@ -529,14 +412,11 @@ sequenceDiagram
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
A-->>S: turn complete
|
A-->>S: turn complete
|
||||||
deactivate A
|
deactivate A
|
||||||
S-->>F: readable via session/{id}/chat
|
S-->>F: readable via session/{id}/chat
|
||||||
```
|
```
|
||||||
|
|
||||||
## Extension Points
|
## Extension Points
|
||||||
|
|
||||||
| What | How |
|
| What | How |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Add an LLM backend | Implement `backend.Backend` |
|
| Add an LLM backend | Implement `backend.Backend` |
|
||||||
|
|
@ -546,9 +426,7 @@ sequenceDiagram
|
||||||
| Add a skill | Drop a markdown file into `skills/` |
|
| Add a skill | Drop a markdown file into `skills/` |
|
||||||
| Add an agent persona | Create a JSON config in `agents/` |
|
| Add an agent persona | Create a JSON config in `agents/` |
|
||||||
| Customize sandbox | Create a YAML in `~/.config/ollie/sandbox/` |
|
| Customize sandbox | Create a YAML in `~/.config/ollie/sandbox/` |
|
||||||
|
|
||||||
## Key Design Decisions
|
## Key Design Decisions
|
||||||
|
|
||||||
1. **Two integration surfaces** — 9P filesystem (canonical, Unix philosophy) and D-Bus (conventional, for GUI/web consumers). Both are thin adapters over the same core.
|
1. **Two integration surfaces** — 9P filesystem (canonical, Unix philosophy) and D-Bus (conventional, for GUI/web consumers). Both are thin adapters over the same core.
|
||||||
2. **Three built-in primitives** — `shell`, tool registry, skill registry. Everything else is a script. This keeps the core small and makes all other tools equal citizens.
|
2. **Three built-in primitives** — `shell`, tool registry, skill registry. Everything else is a script. This keeps the core small and makes all other tools equal citizens.
|
||||||
3. **Mandatory sandboxing** — no unsandboxed fallback. Security is not optional.
|
3. **Mandatory sandboxing** — no unsandboxed fallback. Security is not optional.
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,34 @@
|
||||||
# Architectural Evolution
|
# Architectural Evolution
|
||||||
|
|
||||||
How the Ollie system-of-systems emerged and evolved.
|
How the Ollie system-of-systems emerged and evolved.
|
||||||
~1300 commits over ~3.5 months (Apr 11 – Jul 30, 2026).
|
~1300 commits over ~3.5 months (Apr 11 – Jul 30, 2026).
|
||||||
|
|
||||||
## Timeline
|
## Timeline
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
gantt
|
gantt
|
||||||
title Ollie Evolution
|
title Ollie Evolution
|
||||||
dateFormat YYYY-MM-DD
|
dateFormat YYYY-MM-DD
|
||||||
axisFormat %b %d
|
axisFormat %b %d
|
||||||
|
|
||||||
section Foundation
|
section Foundation
|
||||||
Monorepo + submodules :done, 2026-04-11, 14d
|
Monorepo + submodules :done, 2026-04-11, 14d
|
||||||
9P filesystem :done, 2026-04-14, 28d
|
9P filesystem :done, 2026-04-14, 28d
|
||||||
|
|
||||||
section Core Primitives
|
section Core Primitives
|
||||||
Session lifecycle (session/) :done, 2026-04-20, 30d
|
Session lifecycle (session/) :done, 2026-04-20, 30d
|
||||||
Tool scripts :done, 2026-05-01, 20d
|
Tool scripts :done, 2026-05-01, 20d
|
||||||
Skills system :done, 2026-05-10, 14d
|
Skills system :done, 2026-05-10, 14d
|
||||||
|
|
||||||
section Execution
|
section Execution
|
||||||
execute_code / shell :done, 2026-05-08, 20d
|
execute_code / shell :done, 2026-05-08, 20d
|
||||||
Batch jobs (b/ → session/bfg) :done, 2026-05-15, 25d
|
Batch jobs (b/ → session/bfg) :done, 2026-05-15, 25d
|
||||||
Sandbox (landrun) :done, 2026-05-20, 40d
|
Sandbox (landrun) :done, 2026-05-20, 40d
|
||||||
|
|
||||||
section Multi-Agent
|
section Multi-Agent
|
||||||
Subagent spawn :done, 2026-05-25, 15d
|
Subagent spawn :done, 2026-05-25, 15d
|
||||||
Cascade orchestrator :done, 2026-06-05, 20d
|
Cascade orchestrator :done, 2026-06-05, 20d
|
||||||
JIT agent generation :done, 2026-07-10, 10d
|
JIT agent generation :done, 2026-07-10, 10d
|
||||||
|
|
||||||
section Frontends
|
section Frontends
|
||||||
TUI + Emacs (ellie) :done, 2026-04-15, 45d
|
TUI + Emacs (ellie) :done, 2026-04-15, 45d
|
||||||
Acme (Plan 9) :done, 2026-05-20, 30d
|
Acme (Plan 9) :done, 2026-05-20, 30d
|
||||||
Web UI + httpgw :done, 2026-06-01, 25d
|
|
||||||
KDE/Plasma :done, 2026-06-10, 40d
|
KDE/Plasma :done, 2026-06-10, 40d
|
||||||
|
|
||||||
section Control Planes
|
section Control Planes
|
||||||
D-Bus adapter (ollied) :done, 2026-06-25, 20d
|
D-Bus adapter (ollied) :done, 2026-06-25, 20d
|
||||||
D-Bus embedded in srv :done, 2026-07-15, 10d
|
D-Bus embedded in srv :done, 2026-07-15, 10d
|
||||||
|
|
||||||
section Late Evolution
|
section Late Evolution
|
||||||
Remote execution :done, 2026-07-05, 15d
|
Remote execution :done, 2026-07-05, 15d
|
||||||
Elevation broker :done, 2026-07-10, 15d
|
Elevation broker :done, 2026-07-10, 15d
|
||||||
|
|
@ -48,52 +37,35 @@ gantt
|
||||||
FUSE → 9P client :done, 2026-07-25, 5d
|
FUSE → 9P client :done, 2026-07-25, 5d
|
||||||
The Great Flattening :done, 2026-07-29, 2d
|
The Great Flattening :done, 2026-07-29, 2d
|
||||||
```
|
```
|
||||||
|
|
||||||
## Phase 1: Monorepo Bootstrap (Apr 11)
|
## Phase 1: Monorepo Bootstrap (Apr 11)
|
||||||
|
|
||||||
Started as independent git repos unified under a monorepo with submodules.
|
Started as independent git repos unified under a monorepo with submodules.
|
||||||
Initial components:
|
Initial components:
|
||||||
|
|
||||||
- **agent** — agent loop (Go): backend dispatch, tool execution, session state
|
- **agent** — agent loop (Go): backend dispatch, tool execution, session state
|
||||||
- **fs/session** — 9P file server exposing agent sessions as a synthetic filesystem
|
- **fs/session** — 9P file server exposing agent sessions as a synthetic filesystem
|
||||||
- **tui** — terminal UI (later removed)
|
- **tui** — terminal UI (later removed)
|
||||||
- **el** — Emacs integration (ellie.el)
|
- **el** — Emacs integration (ellie.el)
|
||||||
|
|
||||||
Build system was `mkfile` (Plan 9 make), later `Makefile`, finally `justfile`.
|
Build system was `mkfile` (Plan 9 make), later `Makefile`, finally `justfile`.
|
||||||
|
|
||||||
## Phase 2: The 9P Decision (Apr 14–28)
|
## Phase 2: The 9P Decision (Apr 14–28)
|
||||||
|
|
||||||
The defining architectural choice: **every agent primitive is a file**.
|
The defining architectural choice: **every agent primitive is a file**.
|
||||||
|
|
||||||
Sessions are directories. Sending a prompt is writing to `session/{id}/prompt`.
|
Sessions are directories. Sending a prompt is writing to `session/{id}/prompt`.
|
||||||
Reading state is `cat session/{id}/state`. This made the control plane
|
Reading state is `cat session/{id}/state`. This made the control plane
|
||||||
protocol-agnostic — any program that can read/write files can be a frontend.
|
protocol-agnostic — any program that can read/write files can be a frontend.
|
||||||
|
|
||||||
Key files crystallized: `prompt`, `chat`, `state`, `ctl`, `cfg`, `statewait`.
|
Key files crystallized: `prompt`, `chat`, `state`, `ctl`, `cfg`, `statewait`.
|
||||||
|
|
||||||
The `session/new` file (write key=value pairs, read back session ID) became the
|
The `session/new` file (write key=value pairs, read back session ID) became the
|
||||||
session factory.
|
session factory.
|
||||||
|
|
||||||
## Phase 3: Tool System (Apr 28 – May 10)
|
## Phase 3: Tool System (Apr 28 – May 10)
|
||||||
|
|
||||||
Tools evolved through several incarnations:
|
Tools evolved through several incarnations:
|
||||||
|
|
||||||
1. **MCP servers** (denote-mcp, 9beads-mcp) — external processes, heavyweight
|
1. **MCP servers** (denote-mcp, 9beads-mcp) — external processes, heavyweight
|
||||||
2. **Shell/Python scripts** in `~/.config/ollie/tools/` — lightweight, sandboxed
|
2. **Shell/Python scripts** in `~/.config/ollie/tools/` — lightweight, sandboxed
|
||||||
3. **execute_code** as the single built-in tool — all scripts invoked through it
|
3. **execute_code** as the single built-in tool — all scripts invoked through it
|
||||||
4. **call_tool/pipe** — named tool dispatch and cross-tool pipelines
|
4. **call_tool/pipe** — named tool dispatch and cross-tool pipelines
|
||||||
5. **Tool registry** (final form) — dynamic lazy-loading via `tool_list`/`tool_load`/`tool_active`; `execute_code` renamed to `shell`; tools hot-reloadable without server restart
|
5. **Tool registry** (final form) — dynamic lazy-loading via `tool_list`/`tool_load`/`tool_active`; `execute_code` renamed to `shell`; tools hot-reloadable without server restart
|
||||||
|
|
||||||
The file tools (`file_read`, `file_write`, `file_edit`, `file_grep`, `file_glob`)
|
The file tools (`file_read`, `file_write`, `file_edit`, `file_grep`, `file_glob`)
|
||||||
were extracted into standalone Python scripts. Memory (`memory_remember`,
|
were extracted into standalone Python scripts. Memory (`memory_remember`,
|
||||||
`memory_recall`) and reasoning (`reasoning_think`) followed the same pattern.
|
`memory_recall`) and reasoning (`reasoning_think`) followed the same pattern.
|
||||||
|
|
||||||
MCP servers were removed early. Simple scripts won over complex daemons.
|
MCP servers were removed early. Simple scripts won over complex daemons.
|
||||||
|
|
||||||
## Phase 4: Planning System Churn (May – Jul)
|
## Phase 4: Planning System Churn (May – Jul)
|
||||||
|
|
||||||
Planning went through the most iterations of any subsystem:
|
Planning went through the most iterations of any subsystem:
|
||||||
|
|
||||||
1. `pl/` directory namespace with flat file-per-task
|
1. `pl/` directory namespace with flat file-per-task
|
||||||
2. `plan_create` / `plan_complete` tools
|
2. `plan_create` / `plan_complete` tools
|
||||||
3. **Beads** integration (external issue tracker) — made optional, then dropped
|
3. **Beads** integration (external issue tracker) — made optional, then dropped
|
||||||
|
|
@ -101,18 +73,13 @@ Planning went through the most iterations of any subsystem:
|
||||||
5. Inline markdown plans forbidden
|
5. Inline markdown plans forbidden
|
||||||
6. **Final form**: a single `session/{id}/plan` file (markdown checklist), written by
|
6. **Final form**: a single `session/{id}/plan` file (markdown checklist), written by
|
||||||
the agent, persisted across context compaction
|
the agent, persisted across context compaction
|
||||||
|
|
||||||
The lesson: planning needed to be simple, agent-controlled, and not bureaucratic.
|
The lesson: planning needed to be simple, agent-controlled, and not bureaucratic.
|
||||||
|
|
||||||
## Phase 5: Multi-Agent Coordination (May 25 – Jun 15)
|
## Phase 5: Multi-Agent Coordination (May 25 – Jun 15)
|
||||||
|
|
||||||
Three patterns emerged:
|
Three patterns emerged:
|
||||||
|
|
||||||
- **subagent_spawn** — fire-and-forget session creation, parent never blocks
|
- **subagent_spawn** — fire-and-forget session creation, parent never blocks
|
||||||
- **subagent_generate** — JIT agent config generation (role, constraints, tools)
|
- **subagent_generate** — JIT agent config generation (role, constraints, tools)
|
||||||
- **cascade** — script-driven fan-out with `-max-workers`, `-retries`, optional
|
- **cascade** — script-driven fan-out with `-max-workers`, `-retries`, optional
|
||||||
synthesis step
|
synthesis step
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
UP["User prompt"]
|
UP["User prompt"]
|
||||||
|
|
@ -123,7 +90,6 @@ flowchart TB
|
||||||
CASCADE["u/cascade\n(spawn + throttle)"]
|
CASCADE["u/cascade\n(spawn + throttle)"]
|
||||||
CW1["Cascade Worker"]
|
CW1["Cascade Worker"]
|
||||||
CW2["Cascade Worker"]
|
CW2["Cascade Worker"]
|
||||||
|
|
||||||
UP --> PARENT
|
UP --> PARENT
|
||||||
PARENT -- subagent_spawn --> W1
|
PARENT -- subagent_spawn --> W1
|
||||||
PARENT -- subagent_spawn --> W2
|
PARENT -- subagent_spawn --> W2
|
||||||
|
|
@ -135,23 +101,18 @@ flowchart TB
|
||||||
CASCADE --> CW1
|
CASCADE --> CW1
|
||||||
CASCADE --> CW2
|
CASCADE --> CW2
|
||||||
```
|
```
|
||||||
|
|
||||||
Inter-agent communication uses the filesystem: agents write to each other's
|
Inter-agent communication uses the filesystem: agents write to each other's
|
||||||
`prompt` file.
|
`prompt` file.
|
||||||
|
|
||||||
## Phase 6: Frontend Proliferation
|
## Phase 6: Frontend Proliferation
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
subgraph Core["agent.Agent"]
|
subgraph Core["agent.Agent"]
|
||||||
AG["Agent Engine"]
|
AG["Agent Engine"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Surfaces["Integration Surfaces"]
|
subgraph Surfaces["Integration Surfaces"]
|
||||||
P9["9P Filesystem\n(session/ namespace)"]
|
P9["9P Filesystem\n(session/ namespace)"]
|
||||||
DB["D-Bus\n(org.ollie.SessionManager)"]
|
DB["D-Bus\n(org.ollie.SessionManager)"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Frontends
|
subgraph Frontends
|
||||||
SH["s/sh (terminal)"]
|
SH["s/sh (terminal)"]
|
||||||
ACME["acme (Plan 9)"]
|
ACME["acme (Plan 9)"]
|
||||||
|
|
@ -160,35 +121,26 @@ flowchart TB
|
||||||
KP["KDE Plasmoid"]
|
KP["KDE Plasmoid"]
|
||||||
KK["Kate Plugin"]
|
KK["Kate Plugin"]
|
||||||
KR["KRunner"]
|
KR["KRunner"]
|
||||||
WEB["Web UI"]
|
|
||||||
HTTP["curl / scripts"]
|
HTTP["curl / scripts"]
|
||||||
end
|
end
|
||||||
|
|
||||||
AG --> P9
|
AG --> P9
|
||||||
AG --> DB
|
AG --> DB
|
||||||
|
|
||||||
SH --> P9
|
SH --> P9
|
||||||
ACME --> P9
|
ACME --> P9
|
||||||
EL --> P9
|
EL --> P9
|
||||||
HTTP --> P9
|
HTTP --> P9
|
||||||
|
|
||||||
KG --> DB
|
KG --> DB
|
||||||
KP --> DB
|
KP --> DB
|
||||||
KK --> DB
|
KK --> DB
|
||||||
KR --> DB
|
KR --> DB
|
||||||
WEB --> DB
|
WEB --> DB
|
||||||
```
|
```
|
||||||
|
|
||||||
- **s/sh** — shell script frontend (bash, briefly rc, back to bash)
|
- **s/sh** — shell script frontend (bash, briefly rc, back to bash)
|
||||||
- **acme** — Plan 9 editor integration with `Oi` (quick query), `Kmpl` (completion)
|
- **acme** — Plan 9 editor integration with `Oi` (quick query), `Kmpl` (completion)
|
||||||
- **ellie.el** — Emacs with ghost-text completion
|
- **ellie.el** — Emacs with ghost-text completion
|
||||||
- **httpgw** — HTTP→9P gateway
|
|
||||||
- **webui** — browser frontend via httpgw
|
|
||||||
- **ollie-kde** — full Plasma integration: plasmoid, KRunner, Kate plugin, standalone GUI, GUI automation tools
|
- **ollie-kde** — full Plasma integration: plasmoid, KRunner, Kate plugin, standalone GUI, GUI automation tools
|
||||||
- **TUI** — removed (Jul) in favor of s/sh and richer GUIs
|
- **TUI** — removed (Jul) in favor of s/sh and richer GUIs
|
||||||
|
|
||||||
## Phase 7: Security Model (May – Jul)
|
## Phase 7: Security Model (May – Jul)
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
subgraph Permissions["9P Permissions"]
|
subgraph Permissions["9P Permissions"]
|
||||||
|
|
@ -196,7 +148,6 @@ flowchart LR
|
||||||
AGENT["Agent → restricted"]
|
AGENT["Agent → restricted"]
|
||||||
PEERS["Peers → read-only"]
|
PEERS["Peers → read-only"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Execution["Execution Sandbox"]
|
subgraph Execution["Execution Sandbox"]
|
||||||
AGT["Agent"]
|
AGT["Agent"]
|
||||||
SANDBOX["landrun sandbox\n(restricted fs)"]
|
SANDBOX["landrun sandbox\n(restricted fs)"]
|
||||||
|
|
@ -204,59 +155,42 @@ flowchart LR
|
||||||
ELEV["x/elevate\n(socket broker)"]
|
ELEV["x/elevate\n(socket broker)"]
|
||||||
PRIV["Privileged Action"]
|
PRIV["Privileged Action"]
|
||||||
end
|
end
|
||||||
|
|
||||||
AGT --> SANDBOX --> TOOLS
|
AGT --> SANDBOX --> TOOLS
|
||||||
TOOLS -- needs escape --> ELEV --> PRIV
|
TOOLS -- needs escape --> ELEV --> PRIV
|
||||||
```
|
```
|
||||||
|
|
||||||
Evolution:
|
Evolution:
|
||||||
|
|
||||||
1. No sandboxing initially
|
1. No sandboxing initially
|
||||||
2. YAML-based sandbox configs (landrun) for execute_code
|
2. YAML-based sandbox configs (landrun) for execute_code
|
||||||
3. Per-session 9P identity — agents can't read other sessions' tools
|
3. Per-session 9P identity — agents can't read other sessions' tools
|
||||||
4. Elevation broker: socket-based, user-confirmed privilege escalation
|
4. Elevation broker: socket-based, user-confirmed privilege escalation
|
||||||
5. SSH agent proxy added then removed (too much attack surface)
|
5. SSH agent proxy added then removed (too much attack surface)
|
||||||
6. Final: integrated elevation broker replaces separate superpowerd adapter
|
6. Final: integrated elevation broker replaces separate superpowerd adapter
|
||||||
|
|
||||||
## Phase 8: D-Bus as Second Control Plane (Jun 25 – Jul)
|
## Phase 8: D-Bus as Second Control Plane (Jun 25 – Jul)
|
||||||
|
|
||||||
Originally everything was 9P-only. D-Bus was added for desktop integration:
|
Originally everything was 9P-only. D-Bus was added for desktop integration:
|
||||||
|
|
||||||
1. Separate `ollie-dbus` daemon (ollied)
|
1. Separate `ollie-dbus` daemon (ollied)
|
||||||
2. Embedded directly into `olliesrv` (9P server got `-no9p` flag)
|
2. Embedded directly into `olliesrv` (9P server got `-no9p` flag)
|
||||||
3. KDE uses D-Bus exclusively; acme/sh/el use 9P
|
3. KDE uses D-Bus exclusively; acme/sh/el use 9P
|
||||||
4. **9P and D-Bus do not share sessions** — independent stores, same core
|
4. **9P and D-Bus do not share sessions** — independent stores, same core
|
||||||
|
|
||||||
## Phase 9: Remote Execution (Jul 5–19)
|
## Phase 9: Remote Execution (Jul 5–19)
|
||||||
|
|
||||||
Split-brain architecture: orchestration stays local, code execution on a remote
|
Split-brain architecture: orchestration stays local, code execution on a remote
|
||||||
machine via SSH.
|
machine via SSH.
|
||||||
|
|
||||||
- `ollie-remote` binary auto-deployed via SSH bootstrap
|
- `ollie-remote` binary auto-deployed via SSH bootstrap
|
||||||
- Tools embedded in remote binary
|
- Tools embedded in remote binary
|
||||||
- Streaming output notifications back to local session
|
- Streaming output notifications back to local session
|
||||||
- Session persistence across remote restarts
|
- Session persistence across remote restarts
|
||||||
|
|
||||||
## Phase 10: Tool Registry (Jul 20–29)
|
## Phase 10: Tool Registry (Jul 20–29)
|
||||||
|
|
||||||
The final major architectural shift — from "agent knows all tools upfront" to
|
The final major architectural shift — from "agent knows all tools upfront" to
|
||||||
lazy discovery:
|
lazy discovery:
|
||||||
|
|
||||||
1. Tools embed their own `ollie:prompt` metadata blocks
|
1. Tools embed their own `ollie:prompt` metadata blocks
|
||||||
2. `tool_list` — discover available tools and descriptions
|
2. `tool_list` — discover available tools and descriptions
|
||||||
3. `tool_load` — promote a tool to a native callable
|
3. `tool_load` — promote a tool to a native callable
|
||||||
4. `tool_active` — introspect loaded tools
|
4. `tool_active` — introspect loaded tools
|
||||||
5. Skills get the same treatment: `skill_list`, `skill_load`, `skill_active`
|
5. Skills get the same treatment: `skill_list`, `skill_load`, `skill_active`
|
||||||
|
|
||||||
This solved system prompt bloat — only load what's needed per task.
|
This solved system prompt bloat — only load what's needed per task.
|
||||||
|
|
||||||
## Phase 11: The Great Flattening (Jul 29–30)
|
## Phase 11: The Great Flattening (Jul 29–30)
|
||||||
|
|
||||||
Largest single-day structural change: **−4,573 lines net** across the codebase.
|
Largest single-day structural change: **−4,573 lines net** across the codebase.
|
||||||
Eliminated accumulated abstractions that no longer served a purpose.
|
Eliminated accumulated abstractions that no longer served a purpose.
|
||||||
|
|
||||||
### Core (−4,400 lines)
|
### Core (−4,400 lines)
|
||||||
|
|
||||||
- **Killed `agent.Core` interface** — concrete `*agent.Agent` used directly.
|
- **Killed `agent.Core` interface** — concrete `*agent.Agent` used directly.
|
||||||
Nobody else implemented Core; the interface just added indirection.
|
Nobody else implemented Core; the interface just added indirection.
|
||||||
- **Killed `Dispatcher` indirection** in toolsrv — `toolsrv.Server` called directly.
|
- **Killed `Dispatcher` indirection** in toolsrv — `toolsrv.Server` called directly.
|
||||||
|
|
@ -268,9 +202,7 @@ Eliminated accumulated abstractions that no longer served a purpose.
|
||||||
- **Threaded context.Context** properly through daemon → session → agent
|
- **Threaded context.Context** properly through daemon → session → agent
|
||||||
(replaced ad-hoc interrupt mechanisms with cancellation).
|
(replaced ad-hoc interrupt mechanisms with cancellation).
|
||||||
- Dead code removal: `GlobalToolNames`, `ExtractReturnSchema`.
|
- Dead code removal: `GlobalToolNames`, `ExtractReturnSchema`.
|
||||||
|
|
||||||
### 9P server (−214 lines)
|
### 9P server (−214 lines)
|
||||||
|
|
||||||
- **Killed `mgr/` package entirely** — replaced `Manager` struct with package
|
- **Killed `mgr/` package entirely** — replaced `Manager` struct with package
|
||||||
functions in `fs/session/`. The `*fs.Tree` IS the session collection; `rootState`
|
functions in `fs/session/`. The `*fs.Tree` IS the session collection; `rootState`
|
||||||
(unexported) lives in `tree.Data`. CRUD via `NewRoot`, `Lookup`, `Create`,
|
(unexported) lives in `tree.Data`. CRUD via `NewRoot`, `Lookup`, `Create`,
|
||||||
|
|
@ -283,17 +215,13 @@ Eliminated accumulated abstractions that no longer served a purpose.
|
||||||
files, FUSE kernel saw 0 bytes and never issued a read.
|
files, FUSE kernel saw 0 bytes and never issued a read.
|
||||||
- **Fixed `session.All()`** — was looking at `root.Children()` (empty) instead
|
- **Fixed `session.All()`** — was looking at `root.Children()` (empty) instead
|
||||||
of `rootState.sessions`. Broke D-Bus ListSessions and GUI session restore.
|
of `rootState.sessions`. Broke D-Bus ListSessions and GUI session restore.
|
||||||
|
|
||||||
### Frontends
|
### Frontends
|
||||||
|
|
||||||
- **acme**: paths updated `s/` → `session/`, agent files route through
|
- **acme**: paths updated `s/` → `session/`, agent files route through
|
||||||
`session/{id}/agent/{aid}/`.
|
`session/{id}/agent/{aid}/`.
|
||||||
- **ellie.el**: same path migration, added `ellie--agent-dir` for agent ID
|
- **ellie.el**: same path migration, added `ellie--agent-dir` for agent ID
|
||||||
discovery.
|
discovery.
|
||||||
- **KDE GUI**: no changes needed (D-Bus operates on Go objects, not paths).
|
- **KDE GUI**: no changes needed (D-Bus operates on Go objects, not paths).
|
||||||
|
|
||||||
### New filesystem layout
|
### New filesystem layout
|
||||||
|
|
||||||
```
|
```
|
||||||
session/
|
session/
|
||||||
├── new write key=value to create session
|
├── new write key=value to create session
|
||||||
|
|
@ -325,14 +253,11 @@ session/
|
||||||
│ ├── tail exec helper
|
│ ├── tail exec helper
|
||||||
│ └── proc/ detached process output
|
│ └── proc/ detached process output
|
||||||
```
|
```
|
||||||
|
|
||||||
### Architecture after (single Go module, no submodules for core/9p)
|
### Architecture after (single Go module, no submodules for core/9p)
|
||||||
|
|
||||||
```
|
```
|
||||||
*fs.Tree (root) ← IS the session collection
|
*fs.Tree (root) ← IS the session collection
|
||||||
└── .Data = *rootState ← sessions map, config, nextUID
|
└── .Data = *rootState ← sessions map, config, nextUID
|
||||||
└── sessions[id] = *Session ← owns Agent, context, cancel
|
└── sessions[id] = *Session ← owns Agent, context, cancel
|
||||||
|
|
||||||
Package functions (no Manager):
|
Package functions (no Manager):
|
||||||
session.NewRoot(cfg) → *fs.Tree
|
session.NewRoot(cfg) → *fs.Tree
|
||||||
session.Lookup(tree, id) → *Session
|
session.Lookup(tree, id) → *Session
|
||||||
|
|
@ -342,21 +267,15 @@ Package functions (no Manager):
|
||||||
session.RenameFromRoot(tree, old, new)
|
session.RenameFromRoot(tree, old, new)
|
||||||
session.Shutdown(tree)
|
session.Shutdown(tree)
|
||||||
```
|
```
|
||||||
|
|
||||||
### SLOC after flattening
|
### SLOC after flattening
|
||||||
|
|
||||||
| Component | Lines |
|
| Component | Lines |
|
||||||
|-----------|-------|
|
|-----------|-------|
|
||||||
| agent + backend + toolsrv + session | 10,437 |
|
| agent + backend + toolsrv + session | 10,437 |
|
||||||
| fs/session + cmd/olliesrv | 7,925 |
|
| fs/session + cmd/olliesrv | 7,925 |
|
||||||
| kde gui | 12,331 |
|
| kde gui | 12,331 |
|
||||||
| el + acme + httpgw | 1,845 |
|
|
||||||
| **Total** | **32,538** |
|
| **Total** | **32,538** |
|
||||||
|
|
||||||
Zero dead exported functions remain (verified via LSP + grep across all repos).
|
Zero dead exported functions remain (verified via LSP + grep across all repos).
|
||||||
|
|
||||||
## Current Topology
|
## Current Topology
|
||||||
|
|
||||||
```
|
```
|
||||||
ollie/ ← single Go module
|
ollie/ ← single Go module
|
||||||
├── agent/ ← agent loop, history, hooks, commands
|
├── agent/ ← agent loop, history, hooks, commands
|
||||||
|
|
@ -372,9 +291,8 @@ ollie/ ← single Go module
|
||||||
├── log/ ← structured logging
|
├── log/ ← structured logging
|
||||||
├── paths/ ← XDG path resolution
|
├── paths/ ← XDG path resolution
|
||||||
├── mount/ ← 9P FUSE mount client
|
├── mount/ ← 9P FUSE mount client
|
||||||
├── cmd/ ← binaries (olliesrv, ollie-9p, ollie-httpgw, ollie-remote)
|
├── cmd/ ← binaries (olliesrv, ollie-9p, ollie-remote)
|
||||||
├── kde/ ← KDE Plasma (submodule)
|
├── kde/ ← KDE Plasma (submodule)
|
||||||
├── webui/ ← browser frontend (submodule)
|
|
||||||
├── tools/ ← 37 tool scripts (Python)
|
├── tools/ ← 37 tool scripts (Python)
|
||||||
├── scripts/{s,u,x}/ ← filesystem-hosted scripts
|
├── scripts/{s,u,x}/ ← filesystem-hosted scripts
|
||||||
├── agents/ ← agent configs (JSON)
|
├── agents/ ← agent configs (JSON)
|
||||||
|
|
@ -383,12 +301,9 @@ ollie/ ← single Go module
|
||||||
├── sandbox/ ← landrun sandbox profiles (YAML)
|
├── sandbox/ ← landrun sandbox profiles (YAML)
|
||||||
└── doc/ ← documentation
|
└── doc/ ← documentation
|
||||||
```
|
```
|
||||||
|
|
||||||
## Principles That Emerged
|
## Principles That Emerged
|
||||||
|
|
||||||
1. **Filesystem-as-API** — everything is read/write on synthetic files. No
|
1. **Filesystem-as-API** — everything is read/write on synthetic files. No
|
||||||
custom protocols needed.
|
custom protocols needed.
|
||||||
2. **Submodule independence** — KDE and webui have their own repos, tests, build.
|
|
||||||
Monorepo coordinates versions.
|
Monorepo coordinates versions.
|
||||||
3. **Scripts over servers** — MCP servers removed early. Simple scripts won.
|
3. **Scripts over servers** — MCP servers removed early. Simple scripts won.
|
||||||
4. **Progressive disclosure** — lazy tool/skill loading keeps system prompts
|
4. **Progressive disclosure** — lazy tool/skill loading keeps system prompts
|
||||||
|
|
@ -399,9 +314,7 @@ ollie/ ← single Go module
|
||||||
Same core, different surfaces.
|
Same core, different surfaces.
|
||||||
7. **Security by default** — sandboxed execution with explicit elevation,
|
7. **Security by default** — sandboxed execution with explicit elevation,
|
||||||
per-session identity.
|
per-session identity.
|
||||||
|
|
||||||
## Dead Ends and Reversals
|
## Dead Ends and Reversals
|
||||||
|
|
||||||
| What | Why it was removed |
|
| What | Why it was removed |
|
||||||
|------|-------------------|
|
|------|-------------------|
|
||||||
| MCP servers | Too heavyweight; scripts are simpler and faster |
|
| MCP servers | Too heavyweight; scripts are simpler and faster |
|
||||||
|
|
|
||||||
153
doc/USAGE.md
153
doc/USAGE.md
|
|
@ -495,159 +495,6 @@ OLLIE_TOOLS_PATH=~/mnt/toolserver/t
|
||||||
OLLIE_TRANSCRIPT_PATH=~/mnt/archive/tr
|
OLLIE_TRANSCRIPT_PATH=~/mnt/archive/tr
|
||||||
```
|
```
|
||||||
|
|
||||||
## HTTP gateway
|
|
||||||
|
|
||||||
`ollie-httpgw` is an HTTP gateway that translates REST-style HTTP calls into 9P
|
|
||||||
filesystem operations. It lets any HTTP client — including the web UI, `curl`, or
|
|
||||||
custom scripts — interact with `olliesrv` without a 9P client library.
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
```sh
|
|
||||||
just httpgw
|
|
||||||
```
|
|
||||||
|
|
||||||
Produces `~/bin/ollie-httpgw`.
|
|
||||||
|
|
||||||
### Start and stop
|
|
||||||
|
|
||||||
Like `olliesrv`, the gateway supports daemon management:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ollie-httpgw start # start in background (daemonizes)
|
|
||||||
ollie-httpgw fgstart # start in foreground
|
|
||||||
ollie-httpgw stop # stop the daemon
|
|
||||||
ollie-httpgw status # check if running
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run (legacy)
|
|
||||||
|
|
||||||
For backwards compatibility, running without a subcommand starts in foreground:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ollie-httpgw # connect to local olliesrv via namespace; listen on :8080
|
|
||||||
ollie-httpgw -listen :9090 # different port
|
|
||||||
ollie-httpgw -net tcp -addr host:9564 # connect to remote olliesrv over TCP
|
|
||||||
```
|
|
||||||
|
|
||||||
**Flags**
|
|
||||||
|
|
||||||
| Flag | Default | Description |
|
|
||||||
|---|---|---|
|
|
||||||
| `-listen` | `:8080` | HTTP listen address |
|
|
||||||
| `-service` | `ollie` | 9P service name in the local namespace |
|
|
||||||
| `-net` | `` | Network type (`tcp`, `unix`); empty = use namespace |
|
|
||||||
| `-addr` | `` | 9P server address; required when `-net` is set |
|
|
||||||
|
|
||||||
The gateway must be able to reach `olliesrv`. For a local server started with
|
|
||||||
`olliesrv`, the defaults work as-is. For a remote server started with
|
|
||||||
`olliesrv -tcp :9564`, use `-net tcp -addr remotehost:9564`.
|
|
||||||
|
|
||||||
### HTTP → 9P mapping
|
|
||||||
|
|
||||||
Each HTTP method maps to a 9P operation:
|
|
||||||
|
|
||||||
| Method | 9P operation | Example |
|
|
||||||
|---|---|---|
|
|
||||||
| `GET` | read file / list dir | `GET /s/idx` |
|
|
||||||
| `POST` | write to file | `POST /session/new`, `POST /session/{id}/prompt` |
|
|
||||||
| `DELETE` | remove | `DELETE /session/{id}` |
|
|
||||||
| `PATCH` | rename (JSON `{"name":"..."}`) | `PATCH /session/{id}` |
|
|
||||||
| `HEAD` | stat (metadata in `X-9p-*` headers) | `HEAD /session/{id}` |
|
|
||||||
|
|
||||||
The full API is described in `httpgw/openapi.json` and served live at
|
|
||||||
`GET /openapi.json`.
|
|
||||||
|
|
||||||
### Key endpoints
|
|
||||||
|
|
||||||
```sh
|
|
||||||
GET /s/idx # list sessions (JSON array)
|
|
||||||
GET /session/{id}/cfg # session config (key-value JSON)
|
|
||||||
GET /session/{id}/chat # conversation transcript (plain text)
|
|
||||||
POST /session/new # create session (JSON: {cwd, backend, model, agent})
|
|
||||||
POST /session/{id}/prompt # submit prompt (JSON: {prompt: "..."})
|
|
||||||
POST /session/{id}/ctl # send control command (JSON: {ctl: "stop"})
|
|
||||||
DEL /session/{id} # kill and remove session
|
|
||||||
GET /backends # list available backends
|
|
||||||
GET /a/ # agent configs
|
|
||||||
GET /session/{id}/t/ # tool scripts
|
|
||||||
GET /m/ # memory files
|
|
||||||
GET /sk/ # skills
|
|
||||||
```
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Examples with curl:
|
|
||||||
curl http://localhost:8080/s/idx
|
|
||||||
curl -X POST http://localhost:8080/session/new \
|
|
||||||
-d '{"cwd":"/home/user/project","backend":"anthropic"}'
|
|
||||||
curl -X POST http://localhost:8080/s/mysession/prompt \
|
|
||||||
-d '{"prompt":"summarize the recent commits"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Web UI
|
|
||||||
|
|
||||||
The web UI is a Preact single-page application that talks to `ollie-httpgw`. It
|
|
||||||
provides session management, a chat interface, and a basic 9P filesystem browser
|
|
||||||
in the browser.
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
```sh
|
|
||||||
just webui
|
|
||||||
```
|
|
||||||
|
|
||||||
Requires Node.js and npm. Output lands in `webui/dist/`.
|
|
||||||
|
|
||||||
### Serve
|
|
||||||
|
|
||||||
The `dist/` folder is a static site. Serve it with any HTTP server and proxy the
|
|
||||||
API paths to `ollie-httpgw`:
|
|
||||||
|
|
||||||
**nginx example:**
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
server {
|
|
||||||
listen 3000;
|
|
||||||
root /path/to/ollie/webui/dist;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
location / { try_files $uri /index.html; }
|
|
||||||
|
|
||||||
location ~ ^/(s|a|m|t|sk|backends|openapi\.json)(/|$) {
|
|
||||||
proxy_pass http://localhost:8080;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Python one-liner (local testing only):**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd webui/dist && python3 -m http.server 3000
|
|
||||||
# API calls will fail — use the Vite dev server instead for local testing
|
|
||||||
```
|
|
||||||
|
|
||||||
### Development
|
|
||||||
|
|
||||||
Vite's dev server proxies API paths to httpgw automatically:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Terminal 1: gateway
|
|
||||||
ollie-httpgw -listen :8080
|
|
||||||
|
|
||||||
# Terminal 2: dev server (hot-reload, proxied API)
|
|
||||||
cd webui && npm run dev
|
|
||||||
# Open http://localhost:5173
|
|
||||||
```
|
|
||||||
|
|
||||||
The proxy configuration is in `webui/vite.config.ts` and targets `localhost:8080`
|
|
||||||
by default.
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Session list in a sidebar with state, model, backend, and cwd
|
|
||||||
- Create, rename, and kill sessions
|
|
||||||
- Send prompts; streaming via 2-second poll
|
|
||||||
- Stop a running agent
|
|
||||||
- Markdown rendering of assistant responses
|
- Markdown rendering of assistant responses
|
||||||
- 9P filesystem browser (navigate `s/`, `a/`, `m/`, `sk/`)
|
- 9P filesystem browser (navigate `s/`, `a/`, `m/`, `sk/`)
|
||||||
- Two built-in themes: `midnight` (dark) and `acme` (Plan 9-inspired light); choice
|
- Two built-in themes: `midnight` (dark) and `acme` (Plan 9-inspired light); choice
|
||||||
|
|
|
||||||
31
justfile
31
justfile
|
|
@ -1,7 +1,7 @@
|
||||||
# Ollie monorepo build system
|
# Ollie monorepo build system
|
||||||
#
|
#
|
||||||
# Two variants:
|
# Two variants:
|
||||||
# just Default (KF6): core, 9p, acme, kde, httpgw, webui, emacs
|
# just Default (KF6): core, 9p, acme, kde, emacs
|
||||||
# just kf5 KF5/Qt5 variant (Plasma 5.27)
|
# just kf5 KF5/Qt5 variant (Plasma 5.27)
|
||||||
#
|
#
|
||||||
# Build and install are separate phases. No sudo.
|
# Build and install are separate phases. No sudo.
|
||||||
|
|
@ -20,9 +20,9 @@ kf5: build-kf5 install-kf5
|
||||||
|
|
||||||
# === Build targets ===
|
# === Build targets ===
|
||||||
|
|
||||||
build: core ninep acme httpgw webui kde
|
build: core ninep acme kde
|
||||||
|
|
||||||
build-kf5: core ninep acme httpgw webui kde-kf5
|
build-kf5: core ninep acme kde-kf5
|
||||||
|
|
||||||
core:
|
core:
|
||||||
@echo === Building ollie-core ===
|
@echo === Building ollie-core ===
|
||||||
|
|
@ -40,14 +40,6 @@ acme:
|
||||||
install -m755 cmd/Ollie/scripts/Oi {{bin}}/Oi
|
install -m755 cmd/Ollie/scripts/Oi {{bin}}/Oi
|
||||||
install -m755 cmd/Ollie/scripts/Theo {{bin}}/Theo
|
install -m755 cmd/Ollie/scripts/Theo {{bin}}/Theo
|
||||||
|
|
||||||
httpgw:
|
|
||||||
@echo === Building ollie-httpgw ===
|
|
||||||
go build -o {{bin}}/ollie-httpgw ./cmd/ollie-httpgw/
|
|
||||||
|
|
||||||
webui:
|
|
||||||
@echo === Building ollie-webui ===
|
|
||||||
just -f webui/justfile webui
|
|
||||||
|
|
||||||
ollie-remote:
|
ollie-remote:
|
||||||
rm -rf cmd/ollie-remote/tools && cp -a contrib/tools cmd/ollie-remote/tools
|
rm -rf cmd/ollie-remote/tools && cp -a contrib/tools cmd/ollie-remote/tools
|
||||||
@echo === Building ollie-remote ===
|
@echo === Building ollie-remote ===
|
||||||
|
|
@ -70,7 +62,7 @@ install: test install-data install-scripts install-contrib install-kde install-e
|
||||||
|
|
||||||
install-kf5: test install-data install-scripts install-contrib install-kde-kf5 install-el
|
install-kf5: test install-data install-scripts install-contrib install-kde-kf5 install-el
|
||||||
install -m644 contrib/prompts/session-9p.md {{cfg}}/prompts/session.md
|
install -m644 contrib/prompts/session-9p.md {{cfg}}/prompts/session.md
|
||||||
@echo === Install complete \(KF5\) ===
|
@echo === Install complete (KF5) ===
|
||||||
@echo 'Restart Plasma to pick up KDE plugins.'
|
@echo 'Restart Plasma to pick up KDE plugins.'
|
||||||
|
|
||||||
install-kde:
|
install-kde:
|
||||||
|
|
@ -193,8 +185,6 @@ install-scripts:
|
||||||
install -m755 contrib/scripts/u/optimize {{cfg}}/scripts/u/optimize
|
install -m755 contrib/scripts/u/optimize {{cfg}}/scripts/u/optimize
|
||||||
install -m755 contrib/scripts/u/complete {{cfg}}/scripts/u/complete
|
install -m755 contrib/scripts/u/complete {{cfg}}/scripts/u/complete
|
||||||
install -m755 contrib/scripts/u/cascade {{cfg}}/scripts/u/cascade
|
install -m755 contrib/scripts/u/cascade {{cfg}}/scripts/u/cascade
|
||||||
|
|
||||||
|
|
||||||
install -m755 contrib/scripts/x/bd {{cfg}}/scripts/x/bd
|
install -m755 contrib/scripts/x/bd {{cfg}}/scripts/x/bd
|
||||||
install -m755 contrib/scripts/x/prime {{cfg}}/scripts/x/prime
|
install -m755 contrib/scripts/x/prime {{cfg}}/scripts/x/prime
|
||||||
install -m755 contrib/scripts/x/freeloader {{cfg}}/scripts/x/freeloader
|
install -m755 contrib/scripts/x/freeloader {{cfg}}/scripts/x/freeloader
|
||||||
|
|
@ -219,7 +209,6 @@ install-contrib:
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
@echo === Uninstalling ollie ===
|
@echo === Uninstalling ollie ===
|
||||||
rm -f {{bin}}/ollie-httpgw
|
|
||||||
rm -f {{bin}}/Ollie {{bin}}/Kmpl {{bin}}/Oi {{bin}}/Theo
|
rm -f {{bin}}/Ollie {{bin}}/Kmpl {{bin}}/Oi {{bin}}/Theo
|
||||||
rm -f {{bin}}/ollie-remount {{bin}}/ollie-watchdog
|
rm -f {{bin}}/ollie-remount {{bin}}/ollie-watchdog
|
||||||
rm -rf {{cfg}}
|
rm -rf {{cfg}}
|
||||||
|
|
@ -235,8 +224,7 @@ uninstall:
|
||||||
@echo === Uninstall complete ===
|
@echo === Uninstall complete ===
|
||||||
|
|
||||||
uninstall-kf5:
|
uninstall-kf5:
|
||||||
@echo === Uninstalling ollie \(KF5\) ===
|
@echo === Uninstalling ollie (KF5) ===
|
||||||
rm -f {{bin}}/ollie-httpgw
|
|
||||||
rm -f {{bin}}/Ollie {{bin}}/Kmpl {{bin}}/Oi {{bin}}/Theo
|
rm -f {{bin}}/Ollie {{bin}}/Kmpl {{bin}}/Oi {{bin}}/Theo
|
||||||
rm -f {{bin}}/ollie-remount {{bin}}/ollie-watchdog
|
rm -f {{bin}}/ollie-remount {{bin}}/ollie-watchdog
|
||||||
rm -rf {{cfg}}
|
rm -rf {{cfg}}
|
||||||
|
|
@ -249,7 +237,7 @@ uninstall-kf5:
|
||||||
rm -rf ~/.config/emacs/ellie
|
rm -rf ~/.config/emacs/ellie
|
||||||
# Plasma env
|
# Plasma env
|
||||||
rm -f ~/.config/plasma-workspace/env/99-ollie.sh
|
rm -f ~/.config/plasma-workspace/env/99-ollie.sh
|
||||||
@echo === Uninstall complete \(KF5\) ===
|
@echo === Uninstall complete (KF5) ===
|
||||||
|
|
||||||
# === Test targets ===
|
# === Test targets ===
|
||||||
|
|
||||||
|
|
@ -271,7 +259,6 @@ test-remote:
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
go clean ./agent/... ./backend/... 2>/dev/null || true
|
go clean ./agent/... ./backend/... 2>/dev/null || true
|
||||||
rm -f {{bin}}/ollie-httpgw
|
|
||||||
rm -f {{bin}}/Ollie {{bin}}/Kmpl {{bin}}/Oi {{bin}}/Theo
|
rm -f {{bin}}/Ollie {{bin}}/Kmpl {{bin}}/Oi {{bin}}/Theo
|
||||||
rm -f {{bin}}/ollie-remote
|
rm -f {{bin}}/ollie-remote
|
||||||
just -f kde/justfile clean
|
just -f kde/justfile clean
|
||||||
|
|
@ -282,8 +269,8 @@ help:
|
||||||
@echo 'Ollie monorepo build targets:'
|
@echo 'Ollie monorepo build targets:'
|
||||||
@echo ' just Build + install (default, KF6)'
|
@echo ' just Build + install (default, KF6)'
|
||||||
@echo ' just kf5 Build + install (KF5/Qt5, Plasma 5.27)'
|
@echo ' just kf5 Build + install (KF5/Qt5, Plasma 5.27)'
|
||||||
@echo ' just build Build only (KF6): core, 9p, acme, kde, httpgw, webui'
|
@echo ' just build Build only (KF6): core, 9p, acme, kde'
|
||||||
@echo ' just build-kf5 Build only (KF5): core, 9p, acme, kde-kf5, httpgw, webui'
|
@echo ' just build-kf5 Build only (KF5): core, 9p, acme, kde-kf5'
|
||||||
@echo ' just install Install (KF6)'
|
@echo ' just install Install (KF6)'
|
||||||
@echo ' just install-kf5 Install (KF5)'
|
@echo ' just install-kf5 Install (KF5)'
|
||||||
@echo ' just test Run all tests (core + 9p)'
|
@echo ' just test Run all tests (core + 9p)'
|
||||||
|
|
@ -293,4 +280,4 @@ help:
|
||||||
@echo ' just uninstall-kf5 Remove all installed files (KF5)'
|
@echo ' just uninstall-kf5 Remove all installed files (KF5)'
|
||||||
@echo ' just clean Clean build artifacts'
|
@echo ' just clean Clean build artifacts'
|
||||||
@echo ''
|
@echo ''
|
||||||
@echo 'Individual: just core | ninep | acme | httpgw | webui | kde | kde-kf5 | ollie-remote'
|
@echo 'Individual: just core | ninep | acme | kde | kde-kf5 | ollie-remote'
|
||||||
|
|
|
||||||
1
webui
1
webui
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit c760012f731704ff7a1a6caad7f5b24e6a76161b
|
|
||||||
Loading…
Reference in New Issue