From 993d5a812ac71b367f2689f72628671f7b4fa106 Mon Sep 17 00:00:00 2001 From: Ollie Agent Date: Sun, 9 Aug 2026 02:52:06 +0200 Subject: [PATCH] fs: move tools to ctl commands, remove tools file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /tools — lists loaded tools /tool_load X — loads a tool by name System prompt updated to instruct the agent to use ctl for tool management instead of writing to a tools file. --- data/scripts/o | 2 +- fs/handlers.go | 48 +++++++++++++++++++++------------------- fs/spec.go | 6 ----- prompts/system_prompt.md | 9 ++++---- 4 files changed, 30 insertions(+), 35 deletions(-) diff --git a/data/scripts/o b/data/scripts/o index eaa815e..32e86f6 100755 --- a/data/scripts/o +++ b/data/scripts/o @@ -33,7 +33,7 @@ ROOT_ENTRIES="session bypass agents backends models help ctl eventwait complete SESSION_FILES="env paused connected ctl name id bypass" # Agent-level files (exist under session/{name}/agent/{name}/) -AGENT_FILES="prompt fifo chat chat.raw statewait log plan cfg ctl stats systemprompt tools name id proc" +AGENT_FILES="prompt fifo chat chat.raw statewait log plan cfg ctl stats systemprompt name id proc" is_root_path() { local path="$1" diff --git a/fs/handlers.go b/fs/handlers.go index 8e32dda..e3c813a 100644 --- a/fs/handlers.go +++ b/fs/handlers.go @@ -712,6 +712,31 @@ func requestAgentCtl(ctx HandlerCtx, data []byte) ([]byte, error) { return []byte(strings.Join(args, " ") + "\n"), nil case "models": return []byte(ctx.Session.CachedListModels() + "\n"), nil + case "tools": + if ctx.Session.ToolsConn() == nil { + return []byte("(no tool server)\n"), nil + } + loaded, err := ctx.Session.ToolsConn().ListTools() + if err != nil { + return nil, err + } + var sb strings.Builder + for _, ti := range loaded { + if ti.Description != "" { + fmt.Fprintf(&sb, "%-20s %s\n", ti.Name, ti.Description) + } else { + sb.WriteString(ti.Name + "\n") + } + } + return []byte(sb.String()), nil + case "tool_load": + if len(args) == 0 { + return nil, fmt.Errorf("tool_load requires a tool name") + } + if err := ctx.Session.LoadTool(args[0], ctx.Agent); err != nil { + return nil, err + } + return []byte(args[0] + "\n"), nil case "backend": if len(args) == 0 { if be := ctx.Agent.Backend(); be != nil { @@ -753,29 +778,6 @@ func readAgentSystemprompt(ctx HandlerCtx) ([]byte, error) { return []byte(ctx.Agent.SystemPrompt() + "\n"), nil } -func readAgentTools(ctx HandlerCtx) ([]byte, error) { - if ctx.Session.ToolsConn() == nil { - return []byte("(no tool server)\n"), nil - } - loaded, err := ctx.Session.ToolsConn().ListTools() - if err != nil { - return []byte(fmt.Sprintf("(error: %v)\n", err)), nil - } - var sb strings.Builder - for _, ti := range loaded { - if ti.Description != "" { - fmt.Fprintf(&sb, "%-20s %s\n", ti.Name, ti.Description) - } else { - sb.WriteString(ti.Name + "\n") - } - } - return []byte(sb.String()), nil -} - -func writeAgentTools(ctx HandlerCtx, data []byte) error { - return ctx.Session.LoadTool(string(data), ctx.Agent) -} - func readAgentName(ctx HandlerCtx) ([]byte, error) { return []byte(ctx.Agent.Name() + "\n"), nil } diff --git a/fs/spec.go b/fs/spec.go index 0be00d2..dafdae5 100644 --- a/fs/spec.go +++ b/fs/spec.go @@ -279,12 +279,6 @@ FileNode("generate", 0666, Read(readAgentSystemprompt), ), - FileNode("tools", 0666, - Doc("Tool management. Read: loaded tools. Write: tool name to load."), - Read(readAgentTools), - Write(writeAgentTools), - ), - FileNode("name", 0666, Doc("Agent display name. Read: current name. Write: rename agent."), Read(readAgentName), diff --git a/prompts/system_prompt.md b/prompts/system_prompt.md index 29251ed..d758bcc 100644 --- a/prompts/system_prompt.md +++ b/prompts/system_prompt.md @@ -57,11 +57,10 @@ You have autonomous access to tools and skills. Proactively load what you need ## Tools -Tools are loaded into your session at startup (configured in the agent's `autoLoad` list). Additional tools can be loaded dynamically via the 9P filesystem: +Tools are loaded into your session at startup (configured in the agent's `autoLoad` list). Additional tools can be loaded dynamically via the agent's ctl file: -- **List available tools** — write to `session/{sname}/agent/{aname}/tools` (empty string lists all) -- **Load a tool** — write the tool name to `session/{sname}/agent/{aname}/tools` -- **See loaded tools** — read from `session/{sname}/agent/{aname}/tools` +- **List loaded tools** — write `tools` to `ctl` +- **Load a tool** — write `tool_load ` to `ctl` Once loaded, a tool becomes a first-class function. Call it directly by name with JSON arguments matching its schema. @@ -182,7 +181,7 @@ Each agent has its own directory. The `{aname}` is the agent's numeric ID (stabl | `chat.raw` | read | Conversation (full markup with block markers) | | `statewait` | read | Blocks until state changes; returns new value | | `cfg` | r/w | Agent config: backend, model, cwd, params (key=value) | -| `ctl` | write | Control: stop, compact, clear, inject, agent, model, cwd | +| `ctl` | rdwr | Control: stop, compact, clear, inject, agent, model, models, tools, tool_load, cwd, name | | `stats` | read | Agent statistics (usage=, cost=, ctxsz=) | | `models` | read | Available models for current backend | | `systemprompt` | read | Fully rendered system prompt |