From b6694d409ae4e8133b05a565a19e34eadc839049 Mon Sep 17 00:00:00 2001 From: Ollie Agent Date: Sat, 8 Aug 2026 14:33:23 +0200 Subject: [PATCH] agent/toolsrv: replace classifier system with ToolMeta map The entire classifier indirection (interfaces, type assertions, RPC methods for classification, closure wrappers in BuildRuntime) is replaced by a simple map[string]ToolInfo on Runtime. The .meta file is read once into ToolInfo. The loop reads fields directly: rt.ToolMeta[name].ReadOnly (was: rt.CanParallelize(name)) rt.ToolMeta[name].Tier (was: rt.MemoryTier(name, args)) rt.ToolMeta[name].OutputFormat (was: rt.OutputFormat(name)) rt.ToolMeta[name].ResetsCounter (was: rt.ResetsCounter(name)) Removed: ParallelClassifier, MemoryTierClassifier, OutputFormatClassifier interfaces, toolClassifier type, and all closure-wrapping in BuildRuntime. The RPC methods on Server/Conn stay (used by ollie-remote) but the agent no longer routes through them. --- agent/build_runtime.go | 53 ++++-------------------------------------- agent/loop.go | 34 +++++++++++++++++---------- agent/runtime.go | 21 +++++++---------- agent/turn.go | 9 ++++--- toolsrv/tools.go | 22 ------------------ 5 files changed, 39 insertions(+), 100 deletions(-) diff --git a/agent/build_runtime.go b/agent/build_runtime.go index b5daca1..a8f5572 100644 --- a/agent/build_runtime.go +++ b/agent/build_runtime.go @@ -67,51 +67,11 @@ func BuildRuntime(cfg *AgentConfig, srv toolsrv.Runner, cwd string, env []string return text, blocks, nil } - var classify toolClassifier - if pc, ok := srv.(toolsrv.ParallelClassifier); ok { - classify = pc.CanParallelize - } - - // Capture output formats from the same tool listing used to build the - // backend tool definitions. Remote runners expose ToolInfo through - // ListTools, but do not necessarily implement OutputFormatClassifier. - outputFormats := make(map[string]string, len(allToolInfos)) + // Build ToolMeta lookup from the tool listing — single source of truth + // for all per-tool metadata (tier, readOnly, outputFormat, resetsCounter). + toolMeta := make(map[string]toolsrv.ToolInfo, len(allToolInfos)) for _, ti := range allToolInfos { - outputFormats[ti.Name] = ti.OutputFormat - } - outputFormat := func(name string) string { - return outputFormats[name] - } - if oc, ok := srv.(toolsrv.OutputFormatClassifier); ok { - outputFormat = oc.OutputFormat - } - - var tierFn func(string, json.RawMessage) ResultTier - if tc, ok := srv.(toolsrv.MemoryTierClassifier); ok { - tierFn = func(name string, args json.RawMessage) ResultTier { - switch tc.MemoryTierArgs(name, args) { - case "cold": - return TierCold - case "warm": - return TierWarm - default: - return TierHot - } - } - } - - // Build resetsCounter lookup from tool infos. - resetsCounterMap := make(map[string]bool, len(allToolInfos)) - for _, ti := range allToolInfos { - if ti.ResetsCounter { - resetsCounterMap[ti.Name] = true - } - } - var resetsCounterFn func(string) bool - if len(resetsCounterMap) > 0 { - resetsCounterFn = func(name string) bool { - return resetsCounterMap[name] - } + toolMeta[ti.Name] = ti } var compactionModel string @@ -124,11 +84,8 @@ func BuildRuntime(cfg *AgentConfig, srv toolsrv.Runner, cwd string, env []string return &Runtime{ ToolServer: srv, Tools: allTools, + ToolMeta: toolMeta, Exec: exec, - CanParallelize: classify, - OutputFormat: outputFormat, - MemoryTier: tierFn, - ResetsCounter: resetsCounterFn, Preamble: preamble, GenParams: genParams, MaxSteps: maxSteps, diff --git a/agent/loop.go b/agent/loop.go index fddac71..eaa1dde 100644 --- a/agent/loop.go +++ b/agent/loop.go @@ -99,10 +99,22 @@ func fileStat(path string) (int64, int64) { } func toolOutputFormat(rt *Runtime, name string) string { - if rt.OutputFormat == nil { - return "" + if ti, ok := rt.ToolMeta[name]; ok { + return ti.OutputFormat } - return rt.OutputFormat(name) + return "" +} + +func toolMemoryTier(rt *Runtime, name string) ResultTier { + if ti, ok := rt.ToolMeta[name]; ok { + switch ti.Tier { + case "cold": + return TierCold + case "warm": + return TierWarm + } + } + return TierHot } // TurnCtx holds per-turn closures and state that vary between turns within @@ -317,7 +329,7 @@ func run(rt *Runtime, ctx TurnCtx, state state) error { return cr, true } emit(ctx, Event{Role: "call", Name: call.Name, Content: string(call.Arguments)}) - readSafe := rt.CanParallelize != nil && rt.CanParallelize(call.Name) + readSafe := rt.ToolMeta[call.Name].ReadOnly if readSafe { key := call.Name + "\x00" + string(call.Arguments) if v, ok := resultCache.Load(key); ok { @@ -420,14 +432,14 @@ func run(rt *Runtime, ctx TurnCtx, state state) error { emit(ctx, Event{Role: "tool", Name: call.Name, OutputFormat: toolOutputFormat(rt, call.Name)}) } tier := TierHot - if !isErr && rt.MemoryTier != nil { - tier = rt.MemoryTier(call.Name, call.Arguments) + if !isErr { + tier = toolMemoryTier(rt, call.Name) } return toolResult{ToolCallID: call.ID, Name: call.Name, Content: result, ContentBlocks: resultBlocks, IsError: isErr, Tier: tier}, false } isParallelSafe := func(name string) bool { - return name != "" && rt.CanParallelize != nil && rt.CanParallelize(name) + return name != "" && rt.ToolMeta[name].ReadOnly } for i := 0; i < len(toolCalls) && !interrupted; { @@ -644,9 +656,9 @@ func run(rt *Runtime, ctx TurnCtx, state state) error { // This allows the agent to continue working without hitting the soft // step-budget guardrail, as long as it's making active progress (writing // files, renaming symbols, etc.) rather than looping on research. - if rt.ResetsCounter != nil { + if rt.ToolMeta != nil { for _, r := range results { - if !r.IsError && rt.ResetsCounter(r.Name) { + if !r.IsError && rt.ToolMeta[r.Name].ResetsCounter { step = 0 break } @@ -1079,6 +1091,4 @@ func retryCountdown(ctx TurnCtx, wait time.Duration) error { // defaultToolResultMaxBytes caps tool result content sent back to the model. const defaultToolResultMaxBytes = 131072 -// toolClassifier reports whether a named tool is safe to run concurrently -// with other read-class toolsrv. nil means treat all tools as serial. -type toolClassifier func(name string) bool + diff --git a/agent/runtime.go b/agent/runtime.go index bbaa896..11d1dea 100644 --- a/agent/runtime.go +++ b/agent/runtime.go @@ -1,8 +1,6 @@ package agent import ( - "encoding/json" - "ollie/backend" "ollie/toolsrv" ) @@ -12,17 +10,14 @@ import ( // agent. The agent struct stores a pointer to the active Runtime; switching // agents replaces it atomically. type Runtime struct { - Backend backend.Backend - ToolServer toolsrv.Runner // the execute server (tool runtime, env, cwd) - Preamble string // compiled system prompt - Tools []backend.Tool - Exec toolExecutor - CanParallelize toolClassifier - OutputFormat func(string) string - MemoryTier func(string, json.RawMessage) ResultTier - ResetsCounter func(string) bool // true if tool resets step counter - GenParams backend.GenerationParams - MaxSteps int + Backend backend.Backend + ToolServer toolsrv.Runner // the execute server (tool runtime, env, cwd) + Preamble string // compiled system prompt + Tools []backend.Tool + ToolMeta map[string]toolsrv.ToolInfo // keyed by tool name — source of truth for all per-tool metadata + Exec toolExecutor + GenParams backend.GenerationParams + MaxSteps int // toolRegistryRevision tracks the last-seen tool registry revision. When the // registry revision advances (after tool_load/unload), the next turn // will detect the mismatch and refetch. diff --git a/agent/turn.go b/agent/turn.go index c51aaef..029681a 100644 --- a/agent/turn.go +++ b/agent/turn.go @@ -10,6 +10,7 @@ import ( "strings" "ollie/backend" + "ollie/toolsrv" ) // Submit processes one line of user input: it starts an agent turn that streams @@ -111,13 +112,11 @@ func (ag *Agent) executeTurn(ctx context.Context, input string) string { return "" } ag.runtime.Tools = toolInfosToBackend(ti) - outputFormats := make(map[string]string, len(ti)) + meta := make(map[string]toolsrv.ToolInfo, len(ti)) for _, info := range ti { - outputFormats[info.Name] = info.OutputFormat - } - ag.runtime.OutputFormat = func(name string) string { - return outputFormats[name] + meta[info.Name] = info } + ag.runtime.ToolMeta = meta } turnCtx := TurnCtx{ diff --git a/toolsrv/tools.go b/toolsrv/tools.go index ca23e59..080d354 100644 --- a/toolsrv/tools.go +++ b/toolsrv/tools.go @@ -36,25 +36,3 @@ type Runner interface { ListTools() ([]ToolInfo, error) CallTool(ctx context.Context, tool string, args json.RawMessage) (json.RawMessage, error) } - -// ParallelClassifier is implemented by tool servers that can report whether a -// named tool is safe to run concurrently with other read-class tools. -// Returns false for unknown tools (conservative default). -type ParallelClassifier interface { - CanParallelize(name string) bool -} - -// MemoryTierClassifier is implemented by tool servers that can report the retention -// tier of a tool's results: "hot" (verbatim), "warm" (summarized on compaction), -// or "cold" (immediately summarized). Returns "hot" for unknown tools. -type MemoryTierClassifier interface { - MemoryTier(name string) string - // MemoryTierArgs classifies the tier using both the outer tool name and its - // arguments. - MemoryTierArgs(name string, args json.RawMessage) string -} - -// OutputFormatClassifier reports the source-fence language for tool output. -type OutputFormatClassifier interface { - OutputFormat(name string) string -}