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.
This commit is contained in:
Ollie Agent 2026-08-08 14:33:23 +02:00
parent 38b35f1ba5
commit b6694d409a
5 changed files with 39 additions and 100 deletions

View File

@ -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,

View File

@ -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

View File

@ -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.

View File

@ -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{

View File

@ -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
}