From 6bd77276ee3a35005b1ff1f83eec5dd5e03a618b Mon Sep 17 00:00:00 2001 From: ollie Date: Tue, 28 Jul 2026 22:51:47 +0200 Subject: [PATCH] remove call_tool and pipe implementations --- pkg/agent/core.go | 2 +- pkg/agent/loop.go | 4 +- pkg/tools/execute/code.go | 121 +----------------------------------- pkg/tools/execute/server.go | 109 -------------------------------- pkg/tools/execute/tier.go | 60 +----------------- pkg/tools/tools.go | 3 +- 6 files changed, 7 insertions(+), 292 deletions(-) diff --git a/pkg/agent/core.go b/pkg/agent/core.go index 96dc342..75a5714 100644 --- a/pkg/agent/core.go +++ b/pkg/agent/core.go @@ -54,7 +54,7 @@ func BuildRuntime(cfg *config.Config, d tools.Dispatcher, cwd string, env []stri serverOf[t.Name] = t.Server } // Only built-in executors (with InputSchema) become backend tools. - // Named tool scripts are invoked via call_tool and only appear in the preamble. + // Named tool scripts are promoted via the tool registry and appear in the preamble. allTools = toolInfosToBackend(allToolInfos) // Append named tool scripts for preamble listing only. diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index db6815b..531aba6 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -247,7 +247,7 @@ func run(ctx context.Context, cfg agentConfig, state state) error { // Parse text-based tool calls: models that don't support the function // calling API emit tool invocations as plain text (e.g. - // "call_tool: calls=[...]"). Parse these into proper ToolCall structs. + // "file_read: args=[...]" (text-based tool call syntax)). Parse these into proper ToolCall structs. if len(toolCalls) == 0 && len(cfg.Tools) > 0 { if parsed := parseTextToolCalls(content.String(), cfg.Tools); len(parsed) > 0 { toolCalls = parsed @@ -717,7 +717,7 @@ func parseTextToolCalls(text string, tools []backend.Tool) []backend.ToolCall { } var calls []backend.ToolCall - for _, name := range []string{"execute_code", "call_tool", "pipe"} { + for _, name := range []string{"execute_code"} { if !toolNames[name] { continue } diff --git a/pkg/tools/execute/code.go b/pkg/tools/execute/code.go index e458cff..92550ec 100644 --- a/pkg/tools/execute/code.go +++ b/pkg/tools/execute/code.go @@ -117,10 +117,7 @@ func (e *Server) Dispatch(ctx context.Context, name string, args json.RawMessage switch name { case "execute_code": return dispatchExecuteCode(ctx, e, args) - case "call_tool": - return dispatchCallTool(ctx, e, args) - case "pipe": - return dispatchPipeCall(ctx, e, args) + default: return "", fmt.Errorf("unknown execute tool: %s", name) } @@ -206,50 +203,7 @@ func dispatchExecuteCode(ctx context.Context, e *Server, args json.RawMessage) ( return e.dispatchSteps(ctx, steps, timeout, sandboxName) } -// dispatchCallTool handles the call_tool tool: named tool scripts only. -// Inline code fields are rejected. Fan-out behaviour is preserved. -func dispatchCallTool(ctx context.Context, e *Server, args json.RawMessage) (string, error) { - calls, timeout, sandboxName, err := execCallToolArgs(args) - if err != nil { - return "", fmt.Errorf("call_tool: bad args: %w. Expected format: {\"calls\": [{\"tool\": \"tool_name\", \"args\": [\"arg1\", ...]}]}", err) - } - if len(calls) == 0 { - return "", fmt.Errorf("call_tool: calls is required. Format: {\"calls\": [{\"tool\": \"tool_name\", \"args\": [\"arg1\", ...]}]}") - } - // Validate: every top-level call must name a tool (or be a parallel group of tools). - for i, c := range calls { - if len(c.Parallel) > 0 { - for j, p := range c.Parallel { - if p.Tool == "" { - return "", fmt.Errorf("call_tool: calls[%d].parallel[%d]: tool name is required", i, j) - } - if len(e.allowTools) > 0 && !e.allowTools[p.Tool] { - return "", fmt.Errorf("call_tool: tool %q not permitted in this agent context", p.Tool) - } - } - continue - } - if c.Tool == "" { - return "", fmt.Errorf("call_tool: calls[%d]: tool name is required", i) - } - if len(e.allowTools) > 0 && !e.allowTools[c.Tool] { - return "", fmt.Errorf("call_tool: tool %q not permitted in this agent context", c.Tool) - } - } - return e.dispatchSteps(ctx, calls, timeout, sandboxName) -} -// dispatchPipeCall handles the pipe tool: sequential pipeline of code and/or tool stages. -func dispatchPipeCall(ctx context.Context, e *Server, args json.RawMessage) (string, error) { - stages, timeout, sandboxName, err := execPipeArgs(args) - if err != nil { - return "", fmt.Errorf("pipe: bad args: %w", err) - } - if len(stages) == 0 { - return "", fmt.Errorf("pipe: stages is required. Format: {\"stages\": [{\"code\": \"...\"} or {\"tool\": \"name\", \"args\": [...]}]}") - } - return e.dispatchPipe(ctx, stages, timeout, sandboxName) -} // dispatchSteps runs steps in parallel when safe (annotation-based), serially otherwise. func (e *Server) dispatchSteps(ctx context.Context, steps []CodeStep, timeout int, sandboxName string) (string, error) { @@ -308,38 +262,7 @@ func (e *Server) dispatchSteps(ctx context.Context, steps []CodeStep, timeout in return output, nil } -// dispatchPipe runs stages as an explicit pipeline: sequential, stdout of each -// stage feeds stdin of the next. -func (e *Server) dispatchPipe(ctx context.Context, pipe []CodeStep, timeout int, sandboxName string) (string, error) { - if e.Strict { - if err := enforceStrict(pipe); err != nil { - return "", err - } - } - if len(e.allowTools) > 0 { - for i, s := range pipe { - if s.Tool != "" && !e.allowTools[s.Tool] { - return "", fmt.Errorf("pipe: stage %d: tool %q not permitted in this agent context", i, s.Tool) - } - for j, p := range s.Parallel { - if p.Tool != "" && !e.allowTools[p.Tool] { - return "", fmt.Errorf("pipe: stage %d.parallel[%d]: tool %q not permitted in this agent context", i, j, p.Tool) - } - } - } - } - - var input string - for i, stage := range pipe { - out, err := e.runStage(ctx, i, stage, timeout, sandboxName, input) - if err != nil { - return out, fmt.Errorf("pipe stage %d: %w", i, err) - } - input = out - } - return input, nil -} // enforceStrict rejects any step that uses inline code rather than a named tool. func enforceStrict(stages []CodeStep) error { @@ -379,46 +302,4 @@ func execCodeOnlyArgs(args json.RawMessage) (steps []CodeStep, timeout int, sand return } -// execCallToolArgs parses args for the call_tool tool (calls array). -func execCallToolArgs(args json.RawMessage) (calls []CodeStep, timeout int, sandboxName string, err error) { - var a struct { - Calls []CodeStep `json:"calls"` - Timeout int `json:"timeout"` - Sandbox string `json:"sandbox"` - } - if err = json.Unmarshal(args, &a); err != nil { - return - } - calls = a.Calls - timeout = a.Timeout - if timeout <= 0 { - timeout = 30 - } - sandboxName = a.Sandbox - if sandboxName == "" { - sandboxName = "default" - } - return -} -// execPipeArgs parses args for the pipe tool (stages array). -func execPipeArgs(args json.RawMessage) (stages []CodeStep, timeout int, sandboxName string, err error) { - var a struct { - Stages []CodeStep `json:"stages"` - Timeout int `json:"timeout"` - Sandbox string `json:"sandbox"` - } - if err = json.Unmarshal(args, &a); err != nil { - return - } - stages = a.Stages - timeout = a.Timeout - if timeout <= 0 { - timeout = 30 - } - sandboxName = a.Sandbox - if sandboxName == "" { - sandboxName = "default" - } - return -} diff --git a/pkg/tools/execute/server.go b/pkg/tools/execute/server.go index 0189d02..29460e6 100644 --- a/pkg/tools/execute/server.go +++ b/pkg/tools/execute/server.go @@ -189,116 +189,7 @@ Examples: } }`), }, - { - Name: "call_tool", - Description: `Run one or more named scripts from the tools directory. -Tool names and their arguments are discoverable via: - grep -iA2 'keyword' $OLLIE_TOOLS_PATH/idx - -call_tool fans out in parallel when consecutive calls carry an ollie:parallel read -annotation in their script header; write-annotated or unannotated tools run serially. -Outputs are concatenated in submission order. -No stdout chaining between calls — use pipe for that. - -Each call is one of: -- {tool, args} — named script with arguments -- {tool, args, elevated: true} — run outside sandbox (bash tools only) -- {parallel: [{tool, args}...]} — explicit concurrent fan-out - -Examples: -- Single call: calls=[{tool: "file_read", args: ["README.md"]}] -- Parallel reads: calls=[{tool: "file_read", args: ["a.txt"]}, {tool: "file_read", args: ["b.txt"]}]`, - InputSchema: json.RawMessage(`{ - "type": "object", - "required": ["calls"], - "properties": { - "calls": { - "type": "array", - "description": "Tool calls. Fanned out in parallel when safe (ollie:parallel annotation), serially otherwise.", - "items": { - "type": "object", - "properties": { - "tool": {"type": "string", "description": "Named tool script from the tools directory."}, - "args": {"type": "array", "items": {"type": "string"}, "description": "Arguments for the tool script."}, - "elevated": {"type": "boolean", "description": "Run outside the sandbox via the elevation backend."}, - "parallel": { - "type": "array", - "description": "Explicit fan-out: calls run concurrently, outputs concatenated in submission order.", - "items": { - "type": "object", - "properties": { - "tool": {"type": "string"}, - "args": {"type": "array", "items": {"type": "string"}}, - "elevated": {"type": "boolean"} - } - } - } - } - } - }, - "timeout": {"type": "integer", "description": "Timeout in seconds per call (default: 30). Use 0 for no timeout."}, - "sandbox": {"type": "string", "description": "Sandbox name (default: default)."} - } - }`), - }, - { - Name: "pipe", - Description: `Compose execute_code and call_tool steps into a sequential pipeline. - -Stages run in order; each stage's stdout becomes the next stage's stdin. -Use this when you need to chain heterogeneous code and tool steps together. - -Each stage is one of: -- {code, language} — inline code (default: bash) -- {tool, args} — named script from the tools directory -- {elevated: true, code/tool} — run outside sandbox -- {parallel: [{code/tool}...]} — fan-out within a stage; outputs concatenated, fed to next stage - -Supported inline languages: bash, python3, perl, lua, awk, sed, jq, ed, expect, bc. -timeout applies to each stage independently (default: 30s). A failed stage aborts. - -Examples: -- Code → code: stages=[{code: "grep error app.log"}, {code: "wc -l"}] -- Tool → code: stages=[{tool: "fetch.sh", args: ["--last=1h"]}, {code: "jq .result"}] -- Fan-out → code: stages=[{parallel: [{code: "cat a.txt"}, {code: "cat b.txt"}]}, {code: "sort"}]`, - InputSchema: json.RawMessage(`{ - "type": "object", - "required": ["stages"], - "properties": { - "stages": { - "type": "array", - "description": "Pipeline stages. Each stage's stdout feeds the next stage's stdin.", - "items": { - "type": "object", - "properties": { - "code": {"type": "string", "description": "Inline code to execute."}, - "language": {"type": "string", "description": "Language interpreter (default: bash). Ignored when tool or parallel is set."}, - "tool": {"type": "string", "description": "Named tool script from the tools directory."}, - "args": {"type": "array", "items": {"type": "string"}, "description": "Arguments for the tool script."}, - "elevated": {"type": "boolean", "description": "Run outside the sandbox via the elevation backend."}, - "parallel": { - "type": "array", - "description": "Fan-out within a stage: steps run concurrently, outputs concatenated, then fed to next stage.", - "items": { - "type": "object", - "properties": { - "code": {"type": "string"}, - "language": {"type": "string"}, - "tool": {"type": "string"}, - "args": {"type": "array", "items": {"type": "string"}}, - "elevated": {"type": "boolean"} - } - } - } - } - } - }, - "timeout": {"type": "integer", "description": "Timeout in seconds per stage (default: 30). Use 0 for no timeout."}, - "sandbox": {"type": "string", "description": "Sandbox name (default: default)."} - } - }`), - }, } if e.toolRegistry != nil && e.sessionID != "" { diff --git a/pkg/tools/execute/tier.go b/pkg/tools/execute/tier.go index b9f8e69..6cf49b5 100644 --- a/pkg/tools/execute/tier.go +++ b/pkg/tools/execute/tier.go @@ -37,14 +37,10 @@ func (e *Server) ResultTier(name string) string { } // ResultTierArgs classifies the tier using both the outer tool name and its -// arguments. For call_tool and pipe, it inspects inner tool names and returns -// the coldest tier found. Tier ordering: cold < warm < hot. +// arguments. For execute_code, it returns warm. For promoted tools, it +// delegates to ResultTier. func (e *Server) ResultTierArgs(name string, args json.RawMessage) string { switch name { - case "call_tool": - return e.tierFromCallToolArgs(args) - case "pipe": - return e.tierFromPipeArgs(args) case "execute_code": return "warm" default: @@ -52,58 +48,6 @@ func (e *Server) ResultTierArgs(name string, args json.RawMessage) string { } } -func (e *Server) tierFromCallToolArgs(args json.RawMessage) string { - var parsed struct { - Calls []struct { - Tool string `json:"tool"` - Parallel []struct { - Tool string `json:"tool"` - } `json:"parallel"` - } `json:"calls"` - } - if json.Unmarshal(args, &parsed) != nil || len(parsed.Calls) == 0 { - return "hot" - } - coldest := "hot" - for _, c := range parsed.Calls { - if c.Tool != "" { - coldest = colderTier(coldest, e.ResultTier(c.Tool)) - } - for _, p := range c.Parallel { - if p.Tool != "" { - coldest = colderTier(coldest, e.ResultTier(p.Tool)) - } - } - } - return coldest -} - -func (e *Server) tierFromPipeArgs(args json.RawMessage) string { - var parsed struct { - Stages []struct { - Tool string `json:"tool"` - Parallel []struct { - Tool string `json:"tool"` - } `json:"parallel"` - } `json:"stages"` - } - if json.Unmarshal(args, &parsed) != nil || len(parsed.Stages) == 0 { - return "hot" - } - coldest := "hot" - for _, s := range parsed.Stages { - if s.Tool != "" { - coldest = colderTier(coldest, e.ResultTier(s.Tool)) - } - for _, p := range s.Parallel { - if p.Tool != "" { - coldest = colderTier(coldest, e.ResultTier(p.Tool)) - } - } - } - return coldest -} - // colderTier returns the colder of two tiers. cold < warm < hot. func colderTier(a, b string) string { order := map[string]int{"cold": 0, "warm": 1, "hot": 2} diff --git a/pkg/tools/tools.go b/pkg/tools/tools.go index 79dbe6f..816203b 100644 --- a/pkg/tools/tools.go +++ b/pkg/tools/tools.go @@ -129,7 +129,6 @@ type ParallelClassifier interface { type TierClassifier interface { ResultTier(name string) string // ResultTierArgs classifies the tier using both the outer tool name and its - // arguments. For multiplexed tools (call_tool, pipe), this inspects the - // inner tool names. Returns the coldest tier among all inner tools. + // arguments. ResultTierArgs(name string, args json.RawMessage) string }