From a1e78bca80f0f2bca7c01ae3c56e96f90ca16722 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Thu, 13 Aug 2026 21:44:46 +0200 Subject: [PATCH] remove tool_load: use ctl directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tool_load was a built-in intercept in the agent loop — the only 'tool' that didn't run in toolsrv. Removed entirely: - Intercept in loop.go (25 lines) - Script + .meta in data/tools/ - autoLoad references in agent configs Loading tools is now exclusively via ctl (which already existed): echo 'tool_load X' | ollie-9p write .../ctl System prompt updated to show the ctl pattern. --- cmd/olliesrv/internal/agent/loop.go | 25 ------------------- .../internal/prompts/system_prompt.md | 10 ++++++-- data/agents/default.json | 1 - data/agents/theo.json | 1 - data/tools/tool_load | 20 --------------- data/tools/tool_load.meta | 18 ------------- 6 files changed, 8 insertions(+), 67 deletions(-) delete mode 100644 data/tools/tool_load delete mode 100644 data/tools/tool_load.meta diff --git a/cmd/olliesrv/internal/agent/loop.go b/cmd/olliesrv/internal/agent/loop.go index f128daa..db57535 100644 --- a/cmd/olliesrv/internal/agent/loop.go +++ b/cmd/olliesrv/internal/agent/loop.go @@ -580,31 +580,6 @@ func (ag *Agent) execOne(ctx context.Context, call backend.ToolCall) (toolResult return toolResult{ToolCallID: call.ID, Name: call.Name, Content: result, IsError: isErr}, false } - // Built-in: tool_load - must be handled specially because tools run inside - // toolsrv and can't load other tools into themselves. - if call.Name == "tool_load" { - var args struct { - Name string `json:"name"` - } - if err := json.Unmarshal(call.Arguments, &args); err != nil { - result = "error: " + err.Error() - isErr = true - } else if args.Name == "" { - result = "error: name is required" - isErr = true - } else if toolServer == nil { - result = "error: no tool server" - isErr = true - } else if err := toolServer.LoadTool(args.Name); err != nil { - result = "error: " + err.Error() - isErr = true - } else { - result = "loaded: " + args.Name - } - ag.emit(Event{Role: "tool", Name: call.Name, Content: result, OutputFormat: toolOutputFormat(rt, call.Name)}) - return toolResult{ToolCallID: call.ID, Name: call.Name, Content: result, IsError: isErr}, false - } - if rt.Exec != nil { out, blocks, err := rt.Exec(ctx, call.Name, call.Arguments) if err != nil { diff --git a/cmd/olliesrv/internal/prompts/system_prompt.md b/cmd/olliesrv/internal/prompts/system_prompt.md index 23e0a31..f6e3344 100644 --- a/cmd/olliesrv/internal/prompts/system_prompt.md +++ b/cmd/olliesrv/internal/prompts/system_prompt.md @@ -26,7 +26,13 @@ Proactively load what you need — don't wait to be told. ## Tools -Tools are loaded at startup. Additional tools can be loaded at runtime via the `tool_load` tool. Once loaded, a tool is a first-class function — call it directly by name. +Tools are loaded at startup. Additional tools can be loaded at runtime via ctl: + +```bash +echo "tool_load file_read" | ollie-9p write session/$OLLIE_SESSION_ID/agent/$OLLIE_UNAME/ctl +``` + +Once loaded, a tool is a first-class function — call it directly by name. **Important**: Use native tool calls, not shell. For example: - To read a file: call `file_read` directly, NOT `cat` or `shell` with any command @@ -116,7 +122,7 @@ Your world model is a 9P filesystem. Your session ID is `${OLLIE_SESSION_ID}`. U | `chat.raw` | read | Full conversation with block markers | | `statewait` | read | Blocks until state changes; returns new value | | `cfg` | r/w | Agent config (key=value: backend, model, cwd, temperature, etc.) | -| `ctl` | rdwr | Control: stop, compact, clear, inject, agent, model, tools, tool_load, cwd, name | +| `ctl` | rdwr | Control: stop, compact, clear, inject, agent, model, tools, tool_load, tool_unload, cwd, name | | `stats` | read | Usage, cost, context size | | `tools` | r/w | Write: load tool by name. Read: list loaded tools. | | `proc/` | dir | Detached background processes | diff --git a/data/agents/default.json b/data/agents/default.json index d54525c..8a5b6d3 100644 --- a/data/agents/default.json +++ b/data/agents/default.json @@ -11,7 +11,6 @@ "file_glob", "file_grep", "file_write", - "tool_load" ], "maxSteps": 25, "temperature": 0.5, diff --git a/data/agents/theo.json b/data/agents/theo.json index 1d2259a..9c7404a 100644 --- a/data/agents/theo.json +++ b/data/agents/theo.json @@ -11,7 +11,6 @@ "file_glob", "file_grep", "file_write", - "tool_load" ], "maxSteps": 30, "temperature": 0.3, diff --git a/data/tools/tool_load b/data/tools/tool_load deleted file mode 100644 index 5ad5c1a..0000000 --- a/data/tools/tool_load +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# args_json: {"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Tool name to load into the current session/agent"}}} -# ollie:prompt -# ## tool_load -# -# Load a tool into the current session/agent. -# -# **Args**: `name` (required) — the tool name to load. -# -# ``` -# tool_load(name="file_read") -# ``` -# ollie:end - -set -euo pipefail - -# This tool just outputs the name - the actual loading is handled by -# the agent framework when it receives the tool result. -name=$(grep -oP '"name"\s*:\s*"\K[^"]+') -echo "STATUS=ok TOOL_LOAD=$name" diff --git a/data/tools/tool_load.meta b/data/tools/tool_load.meta deleted file mode 100644 index 2866da9..0000000 --- a/data/tools/tool_load.meta +++ /dev/null @@ -1,18 +0,0 @@ -{ - "description": "Load a tool into the current session/agent via ollie-9p.", - "prompt": "## tool_load\n\nLoad a tool into the current session/agent by writing its name to\n`session/{sid}/agent/{aid}/tools` via ollie-9p.\n\n**Args**: `name` (required) — the tool name to load.\n\n```\ntool_load(name=\"file_read\")\n```", - "args": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "description": "Tool name to load into the current session/agent" - } - } - }, - "tier": "hot", - "scope": "global" -} \ No newline at end of file