diff --git a/README.md b/README.md index 599e0ca..68f6218 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ Ollie starts from a simple, slightly silly question: *What might an AI agent loo - **Parallel fan-out** — `s/bfg -parallel 4` spawns N agents on the same prompt and collects results - **Multi-agent workflows in plain shell** — create named sessions, pass prompts between them via files, coordinate with `statewait`; no framework or SDK - **Subagent delegation** — agents can fork ephemeral subagents that run independently and return results -- **Model cascading for 90% cost reduction** — `cascade` delegates mechanical work to cheaper models, keeping expensive reasoning for high-level planning -- **Orchestrator/worker system** — specialized agents for task decomposition (`orchestrator`) and mechanical execution (`worker`) +- **Tier-based routing** — `route` dispatches subtasks to worker or expert models based on complexity; the orchestrator picks the tier, the system picks the backend/model +- **Orchestrator/worker system** — specialized agents for task decomposition (`orchestrator`) and scoped execution (`worker`/`expert`); workers read code and reason about implementation within their assigned scope - **Any model, any backend** — Ollama (local), OpenAI, Anthropic, OpenRouter, GitHub Copilot, Kiro; switch per-session with one write - **Sandboxed execution** — every tool call runs inside a Landlock sandbox with configurable filesystem access -- **Extensible via plain scripts** — drop executables into a directory and the agent picks them up; built-in tools cover file I/O, LSP (go-to-definition, references, rename, diagnostics), persistent memory, web search, browser screenshots, and task tracking +- **Extensible via plain scripts** — drop executables into a directory and the agent picks them up; built-in tools cover file I/O, LSP (go-to-definition, references, rename, diagnostics), persistent memory, web search, and browser screenshots - **Domain skills** — teach the agent your project's conventions with a markdown file; loaded on demand - **AI code completion** — `u/complete` reads a prefix from stdin and prints the completion to stdout; plug it into any editor that can shell out - **Multiple frontends** — terminal (`s/sh`), acme (`ollie-acme` + `Kmpl`), Emacs (`ellie`), and a browser-based web UI; all talk to the same 9P server @@ -21,7 +21,7 @@ Ollie starts from a simple, slightly silly question: *What might an AI agent loo - **Store federation** — point tools or transcripts at a remote 9P mount to share across machines - **Prompt optimization** — `u/optimize` generates N candidate prompts in parallel, then judges them to return the best one - **Automatic context compaction** — long conversations are summarized transparently when approaching the model's context limit -- **Plan‑first discipline** — agents write checklists before acting, reducing wasted steps and improving reliability +- **Per-agent 9P principals** — each agent session mounts the filesystem as its own user; 9P permission enforcement prevents self-prompting loops and enforces access boundaries ## Getting Started diff --git a/doc/ARCHITECTURE.md b/doc/ARCHITECTURE.md index 3cf7c24..da18ecd 100644 --- a/doc/ARCHITECTURE.md +++ b/doc/ARCHITECTURE.md @@ -60,7 +60,7 @@ Agent configs are JSON files in `~/.config/ollie/agents/`. The key fields are: **`trustedTools`** — array of tool names allowed to run outside the sandbox. -**`mcpServers`** — MCP server configurations for external tool providers: +**`mcpServers`** (optional) — MCP server configurations for external tool providers: ```json { @@ -138,7 +138,7 @@ User-facing scripts are organized by the 9P namespace they are hosted under and | Namespace | Purpose | Scripts | |---|---|---| | `s/` | Session management and one-shot queries | `sh`, `ls`, `kill`, `cleanup`, `b`, `bfg`, `bbg` | -| `u/` | Utility scripts; compositions of `s/` primitives | `optimize` | +| `u/` | Utility scripts; compositions of `s/` primitives | `complete`, `optimize`, `cascade`, `escalate` | | `x/` | System exec and plugins | `elevate`, `elevate-adapter-*`, `bd`, `prime`, `freeloader`, `task` | `s/b` is the core one-shot runner. `s/bfg` and `s/bbg` are thin wrappers: `s/bfg` blocks until the agent finishes and writes the result to stdout; `s/bbg` returns immediately and prints the session path. `u/` scripts have no special server backing — they are executable scripts that compose other namespaces via the filesystem. @@ -147,21 +147,32 @@ User-facing scripts are organized by the 9P namespace they are hosted under and All capabilities beyond `execute_code` itself are implemented as tool scripts in `OLLIE_TOOLS_PATH`, invoked via `{tool}` steps. The agent discovers available scripts via `s/{sid}/t/idx`. -Examples from the default install: `file_read`, `file_write`, `file_edit`, `file_glob`, `file_grep`, `memory_remember`, `memory_recall`, `reasoning_think`, `task_add`, `task_check`, `task_clear`, `denote_view`, `browser_screencap`, `subagent_spawn`. +Examples from the default install: `file_read`, `file_write`, `file_edit`, `file_glob`, `file_grep`, `memory_remember`, `memory_recall`, `reasoning_think`, `lsp_definition`, `lsp_references`, `lsp_rename`, `lsp_symbols`, `lsp_diagnostics`, `lsp_hover`, `lsp_completion`, `route`, `cascade`, `escalate`, `subagent_generate`, `subagent_spawn`, `denote_view`, `browser_screencap`. ## Sub-agents -Sub-agents are implemented as a tool script (`subagent_spawn`) on top of the `s/` namespace. The script writes a session spec to `s/new`, blocks on `s/{id}/statewait` per job in background subshells, joins on all, and concatenates results. The `s/` namespace handles agent lifecycle, cancellation, and result storage. +Sub-agents are implemented as tool scripts (`subagent_spawn`, `cascade`, `route`, `escalate`) on top of the `s/` namespace. Each script writes a session spec to `s/new`, waits for the session directory to appear, then submits a prompt. All are fire-and-forget: the parent continues immediately and receives results asynchronously when the child writes to `s/{parent_session_id}/prompt`. -For heterogeneous parallel delegation (N agents, N different prompts), a `{parallel:[...]}` stage composes with `subagent_spawn` — each parallel step is one shell invocation with a distinct prompt: +**Routing and tiers**: `route` is the orchestrator's primary dispatch tool. It accepts `--tier=worker|expert` and `--backend=`, resolving the model from `OLLIE_TIER_{WORKER|EXPERT}_{backend}` env vars. `cascade` and `escalate` are simpler variants with fixed tier semantics (cheap worker and strong expert respectively). + +**Callback protocol**: when a sub-agent finishes, it writes a structured result to the parent's prompt file: ``` -execute_code {parallel} stage - ├── subagent_spawn "prompt A" → s/{id-0}/chat - ├── subagent_spawn "prompt B" → s/{id-1}/chat - └── subagent_spawn "prompt C" → s/{id-2}/chat - ↓ - concatenated in submission order +[from={session_id}] +STATUS: done|error +SUMMARY: +``` + +The parent receives this as its next queued turn — no polling, no blocking, no active waiting. + +For heterogeneous parallel delegation (N agents, N different prompts), multiple `route`/`cascade` calls fan out concurrently (tool scripts are annotated `ollie:parallel read`): + +``` +call_tool: calls=[ + {tool: "route", args: ["implement X in /src/a.go"]}, + {tool: "route", args: ["implement Y in /src/b.go"]}, + {tool: "route", args: ["redesign Z", "--tier=expert"]} +] ``` The architecture boundary is clean: `execute_code` is the only built-in tool in `core`; everything else is a tool script; `s/` is a 9P namespace. No layer knows about the others. @@ -188,7 +199,15 @@ core := agent.NewAgentCore(agent.AgentCoreConfig{ ## Session Lifecycle -`NewAgentCore` creates `/tmp/ollie/{sessionID}` when a session starts. `Core.Close()` removes it recursively. olliesrv calls `Close()` from `killSession` (individual session removal) and `Shutdown` (server stop — iterates all sessions). `SetSessionID` renames the tmpdir atomically alongside the persisted session files. +### Per-Agent 9P Principals + +Each agent session gets its own FUSE mount at `/tmp/ollie-mount-{sessionID}`, created by `ollie-9p-mount -u {sessionID}`. The session ID is used as the 9P user principal, so the server can enforce per-agent file permissions. The agent's `$OLLIE` env var points to this private mount — not the user's global mount. + +This enables 9P permission enforcement: an agent cannot write to its own `prompt` file (preventing self-prompting loops), cannot read other agents' private state unless explicitly permitted, and cannot escalate its own privileges. + +### Tmpdir and Cleanup + +`NewAgentCore` creates `/tmp/ollie/{sessionID}` when a session starts. `Core.Close()` removes it recursively and tears down the FUSE mount. olliesrv calls `Close()` from `killSession` (individual session removal) and `Shutdown` (server stop — iterates all sessions). `SetSessionID` renames the tmpdir atomically alongside the persisted session files. `!` shell commands run in the session's current working directory (`Core.CWD()`), not the process working directory.