From 60fafd53819a17c371c5aaed48827c7bc395892f Mon Sep 17 00:00:00 2001 From: Ollie Agent Date: Mon, 3 Aug 2026 00:55:22 +0200 Subject: [PATCH] =?UTF-8?q?remove=20tools/builtin/=20=E2=80=94=20zero=20bu?= =?UTF-8?q?ilt-in=20tools,=20all=20tools=20are=20external=20scripts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- AGENTS.md | 1 - README.md | 73 ++++++++++++-------- cmd/ollie-remote/main.go | 5 +- doc/ARCHITECTURE.md | 21 +++--- doc/EVOLUTION.md | 136 +++++++++++++++++++++++++++++++++++++- justfile | 4 +- tools/builtin/builtins.go | 12 ---- toolsrv/server.go | 57 ++-------------- toolsrv/tools.go | 3 - 9 files changed, 199 insertions(+), 113 deletions(-) delete mode 100644 tools/builtin/builtins.go diff --git a/AGENTS.md b/AGENTS.md index 2cc0818..01fdde7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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) diff --git a/README.md b/README.md index 5e094fd..2da62e1 100644 --- a/README.md +++ b/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
ellie.el] - KDE[KDE
plasmoid / GUI / Kate
KRunner / tray] + KDE[KDE
GUI / Kate / KRunner] end subgraph "olliesrv" P9[9P Filesystem
session/ namespace] - DBUS[D-Bus Adapter
org.ollie.SessionManager] SM[Session Manager] AG[agent.Agent
loop · compaction
prompt templates] ROUTE[Router
complete · generate · route] - TOOLS[toolsrv.Server
dynamic registry · dispatch] - SB[Landlock Sandbox] + TOOLS[toolsrv.Server
dynamic dispatch · sandbox] end subgraph "LLM Backends" @@ -109,25 +105,20 @@ graph TB end subgraph Execution - LOCAL[Tool Scripts
file_* · lsp_* · memory_*
gui_* · subagent_*] - REMOTE[ollie-remote
via SSH] - RTOOL[Remote Tools] + LOCAL[Tool Scripts
loaded via 9P write
· shell, reasoning_think
· file_*, lsp_*, memory_*
· gui_*, subagent_*] + REMOTE[ollie-remote
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/` | diff --git a/cmd/ollie-remote/main.go b/cmd/ollie-remote/main.go index 42cbe82..4a58575 100644 --- a/cmd/ollie-remote/main.go +++ b/cmd/ollie-remote/main.go @@ -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)) } diff --git a/doc/ARCHITECTURE.md b/doc/ARCHITECTURE.md index 19734de..7d106e4 100644 --- a/doc/ARCHITECTURE.md +++ b/doc/ARCHITECTURE.md @@ -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 diff --git a/doc/EVOLUTION.md b/doc/EVOLUTION.md index 8217690..6102136 100644 --- a/doc/EVOLUTION.md +++ b/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 --listen +``` + +### 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. diff --git a/justfile b/justfile index fe251f8..c7c7538 100644 --- a/justfile +++ b/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 === diff --git a/tools/builtin/builtins.go b/tools/builtin/builtins.go deleted file mode 100644 index a1dcfba..0000000 --- a/tools/builtin/builtins.go +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/toolsrv/server.go b/toolsrv/server.go index d0a7074..72f1ecd 100644 --- a/toolsrv/server.go +++ b/toolsrv/server.go @@ -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. diff --git a/toolsrv/tools.go b/toolsrv/tools.go index 46a3e0f..8a05c24 100644 --- a/toolsrv/tools.go +++ b/toolsrv/tools.go @@ -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)