remove tools/builtin/ — zero built-in tools, all tools are external scripts
With the zero-tools transition complete, the tools/builtin/ package served no purpose — Builtins() returned nil. Removed: - tools/builtin/builtins.go and the directory - builtins field, WithBuiltins, Dispatch from toolsrv.Server - Handler type from toolsrv (only used by builtins) - OnPreDispatch/WithOnPreDispatch (dead code) - Strict field and WithStrict (dead code, never read) - CallTool simplified — no fallback to Dispatch - builtin import and WithBuiltins/WithStrict calls from ollie-remote - tools/builtin from justfile build/test targets Updated AGENTS.md, ARCHITECTURE.md, README.md, EVOLUTION.md
This commit is contained in:
parent
73eb4dea63
commit
60fafd5381
|
|
@ -15,7 +15,6 @@ ollie/ ← you are here
|
|||
│ └── builder.go BuildTree — walks spec, validates, wires handlers
|
||||
├── cmd/ (Go) Binaries (olliesrv, ollie-9p, ollie-remote)
|
||||
├── tools/ (Go) Tool implementations:
|
||||
│ ├── builtin/ Built-in handlers (shell, reasoning, tool/skill registry)
|
||||
│ └── lsp/ LSP bridge + cmd/ binaries (definition, hover, rename, ...)
|
||||
├── kde/ (C++/Qt6) KDE integration: plasmoid, GUI, Kate plugin, KRunner, tray
|
||||
├── contrib/elisp (Elisp) Emacs frontend (ellie.el)
|
||||
|
|
|
|||
73
README.md
73
README.md
|
|
@ -60,12 +60,10 @@ Single Go module with one Git submodule (kde).
|
|||
|-----------|----------|-------------|
|
||||
| `agent/` | Go | Agent loop, history, hooks, prompt resolution, commands |
|
||||
| `backend/` | Go | LLM providers (Anthropic, OpenAI, Ollama, Gemini, Copilot, CodeWhisperer) |
|
||||
| `toolsrv/` | Go | Tool server, registry, sandboxed execution, remote execution |
|
||||
| `tools/builtin/` | Go | Built-in tool handlers (shell, reasoning, tool/skill registry) |
|
||||
| `toolsrv/` | Go | Tool server, dynamic tool dispatch, sandboxed execution, remote execution |
|
||||
| `tools/lsp/` | Go | LSP bridge daemon + client library (gopls, clangd, intelephense) |
|
||||
| `session/` | Go | Session lifecycle, config, persistence |
|
||||
| `fs/` | Go | 9P filesystem tree for session namespace |
|
||||
| `dbus/` | Go | D-Bus adapter (`org.ollie.SessionManager`) |
|
||||
| `detach/` | Go | Background process management |
|
||||
| `elevate/` | Go | Elevation broker (privilege escalation) |
|
||||
| `mount/` | Go | FUSE-based 9P mount for network transparency |
|
||||
|
|
@ -87,17 +85,15 @@ graph TB
|
|||
subgraph Frontends
|
||||
ACME[Plan 9 acme]
|
||||
EMACS[Emacs<br>ellie.el]
|
||||
KDE[KDE<br>plasmoid / GUI / Kate<br>KRunner / tray]
|
||||
KDE[KDE<br>GUI / Kate / KRunner]
|
||||
end
|
||||
|
||||
subgraph "olliesrv"
|
||||
P9[9P Filesystem<br>session/ namespace]
|
||||
DBUS[D-Bus Adapter<br>org.ollie.SessionManager]
|
||||
SM[Session Manager]
|
||||
AG[agent.Agent<br>loop · compaction<br>prompt templates]
|
||||
ROUTE[Router<br>complete · generate · route]
|
||||
TOOLS[toolsrv.Server<br>dynamic registry · dispatch]
|
||||
SB[Landlock Sandbox]
|
||||
TOOLS[toolsrv.Server<br>dynamic dispatch · sandbox]
|
||||
end
|
||||
|
||||
subgraph "LLM Backends"
|
||||
|
|
@ -109,25 +105,20 @@ graph TB
|
|||
end
|
||||
|
||||
subgraph Execution
|
||||
LOCAL[Tool Scripts<br>file_* · lsp_* · memory_*<br>gui_* · subagent_*]
|
||||
REMOTE[ollie-remote<br>via SSH]
|
||||
RTOOL[Remote Tools]
|
||||
LOCAL[Tool Scripts<br>loaded via 9P write<br>· shell, reasoning_think<br>· file_*, lsp_*, memory_*<br>· gui_*, subagent_*]
|
||||
REMOTE[ollie-remote<br>via SSH · no embedded tools]
|
||||
end
|
||||
|
||||
ACME & EMACS -->|9P| P9
|
||||
KDE -->|D-Bus| DBUS
|
||||
|
||||
ACME & EMACS & KDE --> P9
|
||||
P9 --> SM
|
||||
DBUS --> SM
|
||||
SM --> AG
|
||||
AG --> ROUTE
|
||||
AG --> TOOLS
|
||||
TOOLS --> SB
|
||||
|
||||
AG -->|streaming| OLL & OAI & ANT & COP & KIRO
|
||||
|
||||
SB --> LOCAL
|
||||
SB -->|SSH| REMOTE --> RTOOL
|
||||
TOOLS --> LOCAL
|
||||
TOOLS -->|SSH| REMOTE
|
||||
```
|
||||
|
||||
`olliesrv` is a single binary. Which surface it exposes is a flag:
|
||||
|
|
@ -252,7 +243,40 @@ GUI frontends subscribe once and react — no timers, no file watches, no busy l
|
|||
- **System tray** — status indicator and quick actions
|
||||
- **Standalone GUI** — full-featured Qt6 chat application
|
||||
|
||||
## Tool registry: declarative tool registration
|
||||
## Tool registry: zero built-in tools
|
||||
|
||||
**No tools are compiled into the Go binary.** Every tool — including `shell` and `reasoning_think` — is an external executable loaded dynamically through the 9P filesystem. Tool definitions, schemas, and execution logic live entirely in the filesystem.
|
||||
|
||||
### Loading tools
|
||||
|
||||
Tools are loaded by writing their name to `session/{id}/agent/{aid}/tools`:
|
||||
|
||||
```bash
|
||||
# List currently loaded tools
|
||||
ollie-9p read session/{id}/agent/{aid}/tools
|
||||
|
||||
# Load a tool — becomes a native callable function
|
||||
echo file_glob | ollie-9p write session/{id}/agent/{aid}/tools
|
||||
|
||||
# Global catalog: all discoverable tools on disk
|
||||
ollie-9p read tools
|
||||
```
|
||||
|
||||
The `tool_load` tool is itself a bash script that does exactly this — it wraps the 9P write in a callable function so the LLM can load tools during a turn.
|
||||
|
||||
### Agent autoLoad configuration
|
||||
|
||||
Agent configs declare which tools to load at startup via `autoLoad`:
|
||||
|
||||
```json
|
||||
{
|
||||
"autoLoad": ["file_read", "file_edit", "file_glob", "file_grep", "file_write", "tool_load"]
|
||||
}
|
||||
```
|
||||
|
||||
Each agent profile (default, copilot, explorer, librarian, navigator, taskmanager, theo) has its own `autoLoad` list. The `allowTools` field provides a secondary security gate.
|
||||
|
||||
### Declarative registration
|
||||
|
||||
Tools are executables in `~/.config/ollie/tools/`. Each one has a `.meta` sidecar
|
||||
file declaring its JSON schema, documentation, concurrency class, tier, and
|
||||
|
|
@ -260,19 +284,9 @@ privilege requirements. The registry reads `.meta` files — it never inspects t
|
|||
executable itself.
|
||||
|
||||
This is **declarative tool registration**: write a `.meta` file, drop an
|
||||
executable anywhere on the system, and the tool is live. No server restart,
|
||||
executable in `$OLLIE_TOOLS_PATH`, and the tool is live. No server restart,
|
||||
no config file, no code change.
|
||||
|
||||
```bash
|
||||
# Discover all available tools
|
||||
cat session/{id}/tools
|
||||
|
||||
# Load one into your session — becomes a native callable function
|
||||
echo file_glob | 9p write session/{id}/tools
|
||||
|
||||
# The model sees it as a first-class tool on the next turn.
|
||||
```
|
||||
|
||||
The `.meta` file (`file_glob.meta`):
|
||||
|
||||
```json
|
||||
|
|
@ -350,6 +364,7 @@ full variant specification.
|
|||
| **One-shot LLM** | Write to `/generate`, read back the response |
|
||||
| **Task routing** | Write to `/route`, read back `backend=X model=Y` |
|
||||
| **Background jobs** | Detach via `shell({"elevated":true, "detach":true})` |
|
||||
| **Load tools** | Write tool name to `agent/{id}/tools` via 9P |
|
||||
| **Custom tools** | Drop an executable + `.meta` in `$OLLIE_TOOLS_PATH` |
|
||||
| **Privileged tools** | Declare `"sudo": true` in `.meta` — broker handles elevation + credentials |
|
||||
| **Custom agents** | Write a JSON config in `agents/` |
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import (
|
|||
"sync"
|
||||
"syscall"
|
||||
|
||||
"ollie/tools/builtin"
|
||||
"ollie/toolsrv"
|
||||
)
|
||||
|
||||
|
|
@ -51,7 +50,7 @@ func main() {
|
|||
binDir := filepath.Join(home, ".config", "ollie", "bin")
|
||||
os.Setenv("PATH", binDir+":"+os.Getenv("PATH"))
|
||||
|
||||
// Build the tool server with builtins and registries.
|
||||
// Build the tool server with tool registry.
|
||||
toolReg, _ := toolsrv.NewRegistry()
|
||||
agentID := fmt.Sprintf("agent-%d", os.Getpid())
|
||||
|
||||
|
|
@ -59,8 +58,6 @@ func main() {
|
|||
if *yolo {
|
||||
opts = append(opts, toolsrv.WithYolo())
|
||||
}
|
||||
opts = append(opts, toolsrv.WithStrict())
|
||||
opts = append(opts, toolsrv.WithBuiltins(builtin.Builtins()))
|
||||
if toolReg != nil {
|
||||
opts = append(opts, toolsrv.WithToolRegistry(toolReg, agentID))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
## Philosophy
|
||||
ollie's design philosophy is Plan 9 / Acme / Unix: a small substrate that integrates with the surrounding system rather than trying to do everything itself. 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 external tools, scripts, and frontends.
|
||||
|
||||
The key difference from the Emacs model: Emacs extensibility means building capabilities *on top of* its Lisp interpreter and text primitives. Ollie's extensibility means **not** building capabilities into the core at all — instead, the core provides integration surfaces (9P filesystem, shell execution) and lets the surrounding system handle everything else. This is the Acme approach: a lightweight framework that delegates to external programs via a shared filesystem namespace.
|
||||
The key difference from the Emacs model: Emacs extensibility means building capabilities *on top of* its Lisp interpreter and text primitives. Ollie's extensibility means **not** building capabilities into the core at all — instead, the core provides integration surfaces (9P filesystem, agent loop) and lets the surrounding system handle everything else. This is the Acme approach: a lightweight framework that delegates to external programs via a shared filesystem namespace.
|
||||
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.
|
||||
All clients communicate via 9P exclusively. Desktop notifications use D-Bus directly (org.freedesktop.Notifications) for elevation prompts only — this is the last remaining D-Bus dependency.
|
||||
Design principles:
|
||||
- **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.
|
||||
- **Zero built-in tools.** No tools are compiled into the Go binary. Every tool — including `shell` and `reasoning_think` — is an external script loaded dynamically via the 9P filesystem. The core is pure coordination: stream LLM → dispatch tool calls → loop.
|
||||
- **One integration path: 9P filesystem. Streaming via blocking reads. No polling.
|
||||
- **9P namespace declared via EDSL.** The entire filesystem is a single recursive `FsNodeDecl` tree in `fs/spec.go`, validated and built by `BuildTree()` in `fs/builder.go`. Adding a file means adding a `Leaf()` call — no manual stat/readdir/write wiring.
|
||||
- **No framework lock-in.** Frontends are decoupled via whichever interface they prefer; the core doesn't know or care.
|
||||
|
|
@ -77,7 +77,7 @@ flowchart TB
|
|||
subgraph Core["Agent Engine (per session)"]
|
||||
LOOP["Agent Loop\n(agent/loop.go)"]
|
||||
SESS["Session State\n(agent/history.go)"]
|
||||
TRV["toolsrv.Server\n· shell execution\n· tool registry\n· landlock sandbox"]
|
||||
TRV["toolsrv.Server\n· dynamic tool dispatch\n· landlock sandboxed execution\n· remote execution via SSH"]
|
||||
end
|
||||
subgraph Backends["LLM Backends"]
|
||||
OLLAMA["Ollama"]
|
||||
|
|
@ -96,8 +96,8 @@ flowchart TB
|
|||
LOOP <--> SESS
|
||||
LOOP --> TRV
|
||||
LOOP <--> Backends
|
||||
TRV --> Shell["sandboxed shell\n(landrun)"]
|
||||
TRV --> Tools["tool scripts\n(OLLIE_TOOLS_PATH)"]
|
||||
TRV --> Tools["tool scripts\n(OLLIE_TOOLS_PATH)\n· shell, reasoning_think\n· file_read, file_write\n· lsp_*, memory_*\n· gui_*, subagent_*"]
|
||||
TRV --> Remote["ollie-remote (SSH)\npure 9P RPC\nno embedded tools"]
|
||||
```
|
||||
## Core Library
|
||||
The core is a single Go module (`ollie`) with no binary. Binaries live in `cmd/`.
|
||||
|
|
@ -106,7 +106,7 @@ The core is a single Go module (`ollie`) with no binary. Binaries live in `cmd/`
|
|||
|---|---|
|
||||
| `agent/` | Agent struct, loop, history, compaction, hooks, commands, prompt resolution, state |
|
||||
| `backend/` | Backend interface + LLM providers (Anthropic, OpenAI, Ollama, Gemini, Copilot, CodeWhisperer) |
|
||||
| `toolsrv/` | Tool server: sandboxed shell execution, named tool dispatch, skill management, result tiering, remote execution |
|
||||
| `toolsrv/` | Tool server: dynamic tool dispatch, sandboxed execution, result tiering, remote execution (ollie-remote) |
|
||||
| `session/` | Session lifecycle, config, persistence |
|
||||
| `detach/` | Background process management (ring buffer, signal) |
|
||||
| `elevate/` | Elevation broker (privilege escalation daemon, policy) |
|
||||
|
|
@ -196,6 +196,7 @@ Agent configs declare prompts as a JSON array of shell commands. Each command is
|
|||
├── help help text
|
||||
├── models model list (cached)
|
||||
├── agents agent config list
|
||||
├── tools global tool catalog (all discoverable tools on disk)
|
||||
├── ctl root control (invalidate, kill)
|
||||
├── eventwait global event stream (blocking read)
|
||||
├── complete request-response: code completion
|
||||
|
|
@ -238,16 +239,16 @@ Agent configs declare prompts as a JSON array of shell commands. Each command is
|
|||
│ ├── systemprompt rendered system prompt
|
||||
│ ├── context rendered context window
|
||||
│ ├── tail exec helper for tailing chat
|
||||
│ ├── tools tool management (read all, write name to load)
|
||||
│ ├── toolsrv.loaded currently loaded tools
|
||||
│ ├── toolsrv.rev revision counter
|
||||
│ ├── tools tool management (read = list loaded, write name = load)
|
||||
│ └── proc/{pid} detached process output
|
||||
```
|
||||
### 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, tool management, and inter-agent communication as files:
|
||||
- Sessions are directories under `session/`
|
||||
- Session creation is a two-step process: `session/new` (name only) then `session/{name}/agent/new` (full config)
|
||||
- Sessions and agents have **immutable UUIDs** (`id` file) and **mutable human-readable names** (`name` file); writing to `name` renames the entry
|
||||
- **Tool loading is a file write** — writing a tool name to `agent/{id}/tools` loads it for the agent; reading it lists currently loaded tools
|
||||
- **Global tool catalog** at `/tools` lists all discoverable tools on disk
|
||||
- Prompts, state, chat history, and config are readable/writable files
|
||||
- Streaming via blocking reads: `chat`, `statewait`, `eventwait` block the 9P read until data arrives
|
||||
- Permission enforcement uses 9P user principals
|
||||
|
|
|
|||
136
doc/EVOLUTION.md
136
doc/EVOLUTION.md
|
|
@ -333,7 +333,7 @@ themselves (gopls, clangd, intelephense).
|
|||
### Repo reorganization
|
||||
- `contrib/{prompts,scripts,services,tools,skills,agents}` → `data/`
|
||||
- `contrib/elisp` stays (Emacs frontend contribution)
|
||||
- `tools/builtin/` — built-in tool handlers (moved from `tools/`)
|
||||
- `tools/builtin/` — built-in tool handlers (moved from `tools/`, removed in Phase 18)
|
||||
- `tools/lsp/` — Go LSP implementation (replaced `data/tools/_lib/lsp/`)
|
||||
|
||||
### Conditional variants for network transparency
|
||||
|
|
@ -683,3 +683,137 @@ ollie/ ← single Go module
|
|||
| Single-step session creation | Split into `session/new` + `session/{name}/agent/new` |
|
||||
| D-Bus-driven KDE frontends | Rewritten on pure 9P via plan9port `9p` binary |
|
||||
| Plasmoid/tray KDE components | Deleted — not useful enough to maintain |
|
||||
| Built-in tool handlers (shell, reasoning_think, tool_list, tool_load, tool_active) | Replaced by external scripts loaded via 9P `agent/{id}/tools` write |
|
||||
| Go-compiled tool registry in `tools/builtin/` | Zero built-in tools — all tools are external scripts with `.meta` sidecars |
|
||||
| Embedded tool scripts in `ollie-remote` | Remote uses `$OLLIE_TOOLS_PATH` from environment; tools deployed via tarball over SSH |
|
||||
| SSH binary transfer (gzip+base64+hash-verify) | Replaced by simple `tar | ssh` tarball of `~/.config/ollie/` |
|
||||
| `toolsrv.loaded` / `toolsrv.rev` 9P files | Removed — `tools` file handles both list and load |
|
||||
|
||||
## Phase 18: Zero Built-in Tools (Aug 2–3)
|
||||
|
||||
The most radical simplification yet: **zero tools compiled into the Go binary.** Every tool — including `shell` and `reasoning_think` — is now an external script loaded dynamically through the 9P filesystem.
|
||||
|
||||
### What changed
|
||||
|
||||
**Before**: `tools/builtin/builtins.go` returned four Go-compiled handlers (`shell`, `reasoning_think`, `tool_list`, `tool_load`, `tool_active`). These were hard-linked into every agent's tool definition, consuming LLM context slots even when unused. The tool registry was a two-tier system: built-in handlers (Go code, always available) + script tools (loaded on demand via `tool_load`).
|
||||
|
||||
**After**: `tools/builtin/` was removed entirely — `builtins.go` last returned `nil`. All tools are loaded through a single convergent path: writing the tool name to `session/{id}/agent/{aid}/tools`. The `shell` and `reasoning_think` tools are now external scripts (`data/tools/shell`, `data/tools/reasoning_think`) with `.meta` sidecars — identical to `file_read`, `file_grep`, or any other tool.
|
||||
|
||||
### Unified tool loading
|
||||
|
||||
Tool loading follows a single path shared by both 9P writes and autoLoad initialization:
|
||||
|
||||
```
|
||||
9P write to agent/{id}/tools
|
||||
│
|
||||
▼
|
||||
session.loadTool(name, agent)
|
||||
│
|
||||
├── Check disallow list
|
||||
└── RPC "tool_load" → ollie-remote process
|
||||
```
|
||||
|
||||
The `tool_load` tool itself is a bash script (`data/tools/tool_load`) that writes the tool name to the 9P filesystem — it's a meta-tool that uses the same interface as every other operation.
|
||||
|
||||
### Agent config: `autoLoad`
|
||||
|
||||
Agent configuration files now declare which tools to load at startup via an `autoLoad` array:
|
||||
|
||||
```json
|
||||
{
|
||||
"autoLoad": [
|
||||
"file_read",
|
||||
"file_edit",
|
||||
"file_glob",
|
||||
"file_grep",
|
||||
"file_write",
|
||||
"tool_load"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Each agent profile (default, copilot, explorer, librarian, navigator, taskmanager, theo) has its own `autoLoad` list tailored to its domain. The `allowTools` field provides a secondary security gate.
|
||||
|
||||
### Tool output format classification
|
||||
|
||||
Each tool's `.meta` can declare an `outputFormat` field specifying the source-fence language for its output:
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "...",
|
||||
"outputFormat": "markdown"
|
||||
}
|
||||
```
|
||||
|
||||
The agent loop propagates this to the chat log as the fence language (`[[[tool:file_read]]]\`\`\`markdown`), enabling syntax-highlighted output in the KDE GUI. Tools without `outputFormat` default to plaintext fences.
|
||||
|
||||
### Remote execution simplified
|
||||
|
||||
`ollie-remote` no longer embeds tool scripts or sandbox config:
|
||||
- Removed `//go:embed all:tools` — tools come from `$OLLIE_TOOLS_PATH` in the environment
|
||||
- Removed embedded sandbox `default.yaml` — sandbox config is provisioned alongside tools
|
||||
- Removed `--tools` flag — uses `~/.config/ollie/tools` by default
|
||||
|
||||
### SSH bootstrap simplified
|
||||
|
||||
The SSH bootstrap sequence was cut from a complex multi-stage protocol to a single tarball transfer:
|
||||
|
||||
| Before | After |
|
||||
|--------|-------|
|
||||
| SHA256 hash verification | No hashing |
|
||||
| gzip + base64 binary encoding | Raw `tar czf` tarball |
|
||||
| `OLLIE_LOADER_START` / `OLLIE_LOADER_READY` handshake | Single `OLLIE_LISTEN_READY` signal |
|
||||
| Separate `bootstrap.sh` template (88 lines) | Inline 10-line script |
|
||||
| Per-binary transfer (ollie-remote + landrun) | Single `tar` of `~/.config/ollie/` |
|
||||
|
||||
The simplified bootstrap:
|
||||
|
||||
```sh
|
||||
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/ollie"
|
||||
mkdir -p "$CACHE_DIR"
|
||||
tar xzf - -C "$CACHE_DIR" # receives tarball from local
|
||||
export OLLIE_TOOLS_PATH="$CACHE_DIR/tools"
|
||||
export PATH="$CACHE_DIR/bin:$PATH"
|
||||
exec "$CACHE_DIR/bin/ollie-remote" serve --cwd <dir> --listen <sock>
|
||||
```
|
||||
|
||||
### 9P namespace changes
|
||||
|
||||
- `agent/{id}/tools` — **read** lists all currently loaded tools (via the session's `toolsConn`), **write** loads a tool by name (via `session.loadTool`)
|
||||
- Removed `agent/{id}/toolsrv.loaded` — subsumed by `tools` read
|
||||
- Removed `agent/{id}/toolsrv.rev` — no longer needed
|
||||
- Added root-level `/tools` — lists all discoverable tools on disk (global catalog)
|
||||
- Removed `HandlerCtx.ToolReg` — the in-memory Go-level registry is no longer passed through 9P handlers
|
||||
|
||||
### Files removed
|
||||
|
||||
| File | Status |
|
||||
|------|--------|
|
||||
| `tools/builtin/` | Package removed entirely — all tool logic lives in external scripts |
|
||||
| `tools/builtin/shell.go` | Deleted (now `data/tools/shell` + `data/tools/shell.meta`) |
|
||||
| `tools/builtin/reasoning.go` | Deleted (now `data/tools/reasoning_think` + `data/tools/reasoning_think.meta`) |
|
||||
| `tools/builtin/tool.go` | Deleted (`tool_list`/`tool_load`/`tool_active` replaced by 9P filesystem mechanism) |
|
||||
| `toolsrv/bootstrap.sh` | Deleted (inline bootstrap replaces multi-stage protocol) |
|
||||
| `cmd/ollie-remote/tools/.gitkeep` | Deleted (no embedded tools) |
|
||||
|
||||
### Lines of code
|
||||
|
||||
- **−530 lines** from the Go core (removed built-in handlers, embedded tools, bootstrap script)
|
||||
- **+82 lines** for external tool scripts + `.meta` sidecars (`shell`, `reasoning_think`, `tool_load`)
|
||||
- **Net**: tools system is smaller, simpler, and more uniform
|
||||
|
||||
### Why it matters
|
||||
|
||||
The zero-tools transition is the culmination of the architectural trajectory that began with the tool registry in Phase 10: **every facility is a file; nothing is hard-coded.**
|
||||
|
||||
1. **Uniformity** — There is no distinction between "core" tools and "script" tools. `shell`, `reasoning_think`, `file_read`, `file_write` — all loaded the same way, all have `.meta` sidecars, all called via the same RPC mechanism.
|
||||
|
||||
2. **Minimal core** — The Go binary no longer contains any tool logic. The agent loop is pure coordination: stream LLM → parse tool calls → dispatch to remote process → loop. Tool definitions, schemas, and execution are entirely external.
|
||||
|
||||
3. **Agent-choosable tools** — Each agent profile declares exactly which tools it needs. No agent pays the context-window cost of `shell` and `reasoning_think` unless its config asks for them.
|
||||
|
||||
4. **9P as the sole control plane** — Tool loading is now a file write. The same mechanism (`ollie-9p write ...`) that submits prompts and reads state also manages tool loading. No special RPC, no separate tool-manager protocol.
|
||||
|
||||
5. **Simpler remote deployment** — No embedded files, no binary transfer protocol, no hash verification. Provisioning a remote host is `tar czf ~/.config/ollie | ssh host tar xzf -`.
|
||||
|
||||
The result: the Go core is a 9P file server and an agent loop, nothing more. All behavior — every tool, skill, prompt, and configuration — lives in data files on disk. This is the Plan 9 ideal: a small, fixed kernel; all policy and capability in the filesystem.
|
||||
|
|
|
|||
4
justfile
4
justfile
|
|
@ -26,7 +26,7 @@ build-kf5: core ninep ollie-remote acme kde-kf5
|
|||
|
||||
core:
|
||||
@echo === Building ollie-core ===
|
||||
go build ./agent/... ./backend/... ./detach/... ./elevate/... ./env/... ./fs/... ./log/... ./mount/... ./paths/... ./prompts/... ./sandbox/... ./session/... ./skills/... ./tools/builtin/... ./tools/lsp/... ./toolsrv/...
|
||||
go build ./agent/... ./backend/... ./detach/... ./elevate/... ./env/... ./fs/... ./log/... ./mount/... ./paths/... ./prompts/... ./sandbox/... ./session/... ./skills/... ./tools/lsp/... ./toolsrv/...
|
||||
|
||||
ninep:
|
||||
@echo === Building ollie-9p ===
|
||||
|
|
@ -221,7 +221,7 @@ test: test-core test-9p
|
|||
|
||||
test-core:
|
||||
@echo === Testing ollie-core ===
|
||||
go test ./agent/... ./backend/... ./detach/... ./elevate/... ./env/... ./log/... ./paths/... ./sandbox/... ./session/... ./skills/... ./tools/builtin/... ./toolsrv/... ./prompts/...
|
||||
go test ./agent/... ./backend/... ./detach/... ./elevate/... ./env/... ./log/... ./paths/... ./sandbox/... ./session/... ./skills/... ./toolsrv/... ./prompts/...
|
||||
|
||||
test-9p:
|
||||
@echo === Testing ollie-9p ===
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
// Package tools provides the built-in tool handlers for the toolsrv server.
|
||||
// Each file in this package is one tool. Builtins() is the single registry.
|
||||
package builtin
|
||||
|
||||
import "ollie/toolsrv"
|
||||
|
||||
// Builtins returns the default set of built-in tool handlers.
|
||||
// Returns nil — there are zero built-in tools. All tools are loaded
|
||||
// dynamically via the 9P agent/{id}/tools write mechanism.
|
||||
func Builtins() map[string]toolsrv.Handler {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -42,13 +42,9 @@ type Server struct {
|
|||
envMu sync.RWMutex
|
||||
envExtra map[string]string
|
||||
|
||||
// Hooks for lifecycle events (harnesses like 9P can inject mount logic here)
|
||||
OnPreDispatch func()
|
||||
OnClose func()
|
||||
OnEnvSet func(key, value string)
|
||||
|
||||
// Strict rejects inline {code} steps; only {tool} steps are allowed.
|
||||
Strict bool
|
||||
// Hooks for lifecycle events (harnesses like 9P can inject mount logic here)
|
||||
OnClose func()
|
||||
OnEnvSet func(key, value string)
|
||||
|
||||
// Yolo skips the landrun sandbox for all execution.
|
||||
Yolo bool
|
||||
|
|
@ -57,9 +53,6 @@ type Server struct {
|
|||
// Empty means all are allowed.
|
||||
allowTools map[string]bool
|
||||
|
||||
// builtins maps tool names to their handler functions.
|
||||
builtins map[string]Handler
|
||||
|
||||
toolRegistry *Registry
|
||||
sessionID string
|
||||
|
||||
|
|
@ -88,15 +81,9 @@ type Server struct {
|
|||
// Option configures a Server.
|
||||
type Option func(*Server)
|
||||
|
||||
// WithStrict rejects inline {code} steps; only {tool} steps are allowed.
|
||||
func WithStrict() Option { return func(s *Server) { s.Strict = true } }
|
||||
|
||||
// WithYolo skips the landrun sandbox.
|
||||
func WithYolo() Option { return func(s *Server) { s.Yolo = true } }
|
||||
|
||||
// WithOnPreDispatch registers a hook called before each Dispatch.
|
||||
func WithOnPreDispatch(fn func()) Option { return func(s *Server) { s.OnPreDispatch = fn } }
|
||||
|
||||
// WithOnClose registers a hook called during Close.
|
||||
func WithOnClose(fn func()) Option { return func(s *Server) { s.OnClose = fn } }
|
||||
|
||||
|
|
@ -130,11 +117,6 @@ func (e *Server) AllowTools() []string {
|
|||
return out
|
||||
}
|
||||
|
||||
// WithBuiltins registers the built-in tool handlers.
|
||||
func WithBuiltins(m map[string]Handler) Option {
|
||||
return func(s *Server) { s.builtins = m }
|
||||
}
|
||||
|
||||
// WithToolRegistry attaches a tool registry and session ID to the Server.
|
||||
func WithToolRegistry(r *Registry, sessionID string) Option {
|
||||
return func(s *Server) {
|
||||
|
|
@ -169,30 +151,15 @@ func (e *Server) ListTools() ([]ToolInfo, error) {
|
|||
return all, nil
|
||||
}
|
||||
|
||||
// CallTool implements Server.
|
||||
// CallTool implements Runner.
|
||||
// With zero built-in tools, all tools must be loaded in the registry first.
|
||||
func (e *Server) CallTool(ctx context.Context, tool string, args json.RawMessage) (json.RawMessage, error) {
|
||||
if e.toolRegistry != nil && e.sessionID != "" {
|
||||
if _, promoted := e.toolRegistry.Lookup(e.sessionID, tool); promoted {
|
||||
return e.callPromotedTool(ctx, tool, args)
|
||||
}
|
||||
}
|
||||
result, err := e.Dispatch(ctx, tool, args)
|
||||
if err != nil {
|
||||
return json.Marshal(map[string]any{
|
||||
"isError": true,
|
||||
"content": []map[string]string{{"type": "text", "text": err.Error()}},
|
||||
})
|
||||
}
|
||||
// If the tool output is already structured content-block JSON, pass it through.
|
||||
var structured map[string]json.RawMessage
|
||||
if json.Unmarshal([]byte(result), &structured) == nil {
|
||||
if _, ok := structured["content"]; ok {
|
||||
return []byte(result), nil
|
||||
}
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
"content": []map[string]string{{"type": "text", "text": result}},
|
||||
})
|
||||
return nil, fmt.Errorf("unknown tool: %s (not loaded)", tool)
|
||||
}
|
||||
|
||||
// New creates a new Server with the given working directory.
|
||||
|
|
@ -316,18 +283,6 @@ func (e *Server) Close() {
|
|||
}
|
||||
}
|
||||
|
||||
// Dispatch routes tool calls to the appropriate handler.
|
||||
// Dispatch routes tool calls to the appropriate built-in handler.
|
||||
func (e *Server) Dispatch(ctx context.Context, name string, args json.RawMessage) (string, error) {
|
||||
if e.OnPreDispatch != nil {
|
||||
e.OnPreDispatch()
|
||||
}
|
||||
if h, ok := e.builtins[name]; ok {
|
||||
return h(ctx, e, args)
|
||||
}
|
||||
return "", fmt.Errorf("unknown tool: %s", name)
|
||||
}
|
||||
|
||||
// Detach signals the currently running process to be detached from the agent.
|
||||
// The process continues running; its output is captured in a ring buffer.
|
||||
// Returns false if no process is currently running.
|
||||
|
|
|
|||
|
|
@ -53,6 +53,3 @@ type TierClassifier interface {
|
|||
type OutputFormatClassifier interface {
|
||||
OutputFormat(name string) string
|
||||
}
|
||||
|
||||
// Handler is the signature for built-in tool handlers.
|
||||
type Handler func(ctx context.Context, srv *Server, args json.RawMessage) (string, error)
|
||||
|
|
|
|||
Loading…
Reference in New Issue