agent: eliminate Dispatcher, use tools.Server directly

Runtime.Dispatcher → Runtime.ExecServer (tools.Server). The Dispatcher
abstraction routed to exactly one server ('execute') — unnecessary
indirection. BuildRuntime now takes tools.Server directly.

Agent.newDispatcher → Agent.newToolServer. AgentCfg/Config updated.
All GetServer('execute') calls replaced with direct ExecServer access.
exec closure simplified: direct CallTool, no tool-name lookup.
This commit is contained in:
Levi Neely 2026-07-29 22:50:01 +02:00
parent 0524f38db0
commit d6bc89d3e2
6 changed files with 35 additions and 59 deletions

View File

@ -29,7 +29,7 @@ type Agent struct {
agentsDir string
baseLayers []string // system prompt layers for /agent reloads
promptEnvExtra []string // PRIME_* vars for prompt resolution
newDispatcher func() tools.Dispatcher
newToolServer func() tools.Server
newBackend func(string) (backend.Backend, error)
currentAction atomic.Pointer[actionHandle]
warnedContext bool
@ -258,10 +258,10 @@ func (ag *Agent) ToolCallCount() int64 {
// SetSessionEnv injects session env vars into the execute server.
func (ag *Agent) SetSessionEnv(sessionID string) {
if ag.runtime.Dispatcher == nil {
if ag.runtime.ExecServer == nil {
return
}
if srv, ok := ag.runtime.Dispatcher.GetServer("execute"); ok {
if srv := ag.runtime.ExecServer; srv != nil {
if es, ok := srv.(tools.EnvSetter); ok {
es.SetEnv("OLLIE_SESSION_ID", sessionID)
if ag.id != "" {
@ -273,10 +273,10 @@ func (ag *Agent) SetSessionEnv(sessionID string) {
// SetEnv stores an environment variable on the execute server.
func (ag *Agent) SetEnv(key, value string) {
if ag.runtime.Dispatcher == nil {
if ag.runtime.ExecServer == nil {
return
}
if srv, ok := ag.runtime.Dispatcher.GetServer("execute"); ok {
if srv := ag.runtime.ExecServer; srv != nil {
if es, ok := srv.(tools.EnvSetter); ok {
es.SetEnv(key, value)
}
@ -285,10 +285,10 @@ func (ag *Agent) SetEnv(key, value string) {
// Close releases agent resources (dispatcher, execute server).
func (ag *Agent) Close() {
if ag.runtime.Dispatcher == nil {
if ag.runtime.ExecServer == nil {
return
}
if srv, ok := ag.runtime.Dispatcher.GetServer("execute"); ok {
if srv := ag.runtime.ExecServer; srv != nil {
if c, ok := srv.(interface{ Close() }); ok {
c.Close()
}
@ -302,8 +302,8 @@ func (ag *Agent) SetCWD(dir string) {
if oldCwd != "" && dir != "" && oldCwd != dir {
ag.runtime.Preamble = strings.ReplaceAll(ag.runtime.Preamble, oldCwd, dir)
}
if ag.runtime != nil && ag.runtime.Dispatcher != nil {
if srv, ok := ag.runtime.Dispatcher.GetServer("execute"); ok {
if ag.runtime.ExecServer != nil {
if srv := ag.runtime.ExecServer; srv != nil {
if ws, ok := srv.(tools.CWDSetter); ok {
ws.SetCWD(dir)
}
@ -472,13 +472,9 @@ func (ag *Agent) React(responseID, emoji string) error {
return nil
}
// execServer returns the execute server interface, or nil if unavailable.
func (ag *Agent) execServer() interface{} {
if ag.runtime.Dispatcher == nil {
return nil
}
srv, _ := ag.runtime.Dispatcher.GetServer("execute")
return srv
// execServer returns the execute server, or nil if unavailable.
func (ag *Agent) execServer() tools.Server {
return ag.runtime.ExecServer
}
// Queue pushes a prompt onto the agent's FIFO.

View File

@ -17,7 +17,7 @@ import (
// env provides additional environment variables injected into prompt resolution
// subprocesses (e.g. OLLIE_SESSION_ID=xxx).
// The caller is responsible for registering all servers on d before calling this.
func BuildRuntime(cfg *AgentConfig, d tools.Dispatcher, cwd string, env []string, baseLayers ...string) *Runtime {
func BuildRuntime(cfg *AgentConfig, srv tools.Server, cwd string, env []string, baseLayers ...string) *Runtime {
var messages []string
var allToolInfos []tools.ToolInfo
@ -25,7 +25,7 @@ func BuildRuntime(cfg *AgentConfig, d tools.Dispatcher, cwd string, env []string
if cfg == nil || cfg.ToolsEnabled() {
var listErr error
allToolInfos, listErr = d.ListTools()
allToolInfos, listErr = srv.ListTools()
if listErr != nil {
messages = append(messages, fmt.Sprintf("list tools: %v", listErr))
}
@ -69,30 +69,14 @@ func BuildRuntime(cfg *AgentConfig, d tools.Dispatcher, cwd string, env []string
}
maxSteps = cfg.MaxSteps
if len(cfg.AllowTools) > 0 {
if srv, ok := d.GetServer("execute"); ok {
if rs, ok := srv.(tools.ToolRestrictionSetter); ok {
rs.SetAllowTools(cfg.AllowTools)
}
if rs, ok := srv.(tools.ToolRestrictionSetter); ok {
rs.SetAllowTools(cfg.AllowTools)
}
}
}
exec := func(ctx context.Context, name string, args json.RawMessage) (string, []backend.ContentBlock, error) {
infos, listErr := d.ListTools()
if listErr != nil {
return "", nil, listErr
}
server := ""
for _, t := range infos {
if t.Name == name {
server = t.Server
break
}
}
if server == "" {
return "", nil, fmt.Errorf("unknown tool: %s", name)
}
raw, err := d.Dispatch(ctx, server, name, args)
raw, err := srv.CallTool(ctx, name, args)
if err != nil {
return "", nil, err
}
@ -104,24 +88,20 @@ func BuildRuntime(cfg *AgentConfig, d tools.Dispatcher, cwd string, env []string
}
var classify toolClassifier
if srv, ok := d.GetServer("execute"); ok {
if pc, ok := srv.(tools.ParallelClassifier); ok {
classify = pc.IsParallelRead
}
if pc, ok := srv.(tools.ParallelClassifier); ok {
classify = pc.IsParallelRead
}
var tierFn func(string, json.RawMessage) ResultTier
if srv, ok := d.GetServer("execute"); ok {
if tc, ok := srv.(tools.TierClassifier); ok {
tierFn = func(name string, args json.RawMessage) ResultTier {
switch tc.ResultTierArgs(name, args) {
case "cold":
return TierCold
case "warm":
return TierWarm
default:
return TierHot
}
if tc, ok := srv.(tools.TierClassifier); ok {
tierFn = func(name string, args json.RawMessage) ResultTier {
switch tc.ResultTierArgs(name, args) {
case "cold":
return TierCold
case "warm":
return TierWarm
default:
return TierHot
}
}
}
@ -160,7 +140,7 @@ func BuildRuntime(cfg *AgentConfig, d tools.Dispatcher, cwd string, env []string
}
return &Runtime{
Dispatcher: d,
ExecServer: srv,
Tools: allTools,
Exec: exec,
ClassifyTool: classify,

View File

@ -180,7 +180,7 @@ func (ag *Agent) cmdAgent(_ context.Context, args []string) {
ag.emit(infoEvent(fmt.Sprintf("error: agent %q: %v", name, err)))
return
}
disp := ag.newDispatcher()
disp := ag.newToolServer()
env := []string{"OLLIE_SESSION_ID=" + ag.sessionID, "OLLIE_UNAME=" + ag.id}
env = append(env, ag.promptEnvExtra...)
rt := BuildRuntime(cfg, disp, ag.cwd, env, ag.baseLayers...)

View File

@ -19,7 +19,7 @@ type AgentCfg struct {
CWD string // working directory for tool execution
BaseLayers []string
PromptEnvExtra []string
NewDispatcher func() tools.Dispatcher
NewToolServer func() tools.Server
NewBackend func(string) (backend.Backend, error)
Bus *pubsub.Bus
Log *olog.Logger
@ -42,7 +42,7 @@ func NewAgent(cfg AgentCfg) *Agent {
cwd: cfg.CWD,
baseLayers: cfg.BaseLayers,
promptEnvExtra: cfg.PromptEnvExtra,
newDispatcher: cfg.NewDispatcher,
newToolServer: cfg.NewToolServer,
newBackend: cfg.NewBackend,
bus: cfg.Bus,
log: cfg.Log,

View File

@ -13,7 +13,7 @@ import (
// agents replaces it atomically.
type Runtime struct {
Backend backend.Backend
Dispatcher tools.Dispatcher
ExecServer tools.Server // the execute server (tool runtime, env, cwd)
Hooks Hooks
Preamble string // compiled system prompt
Tools []backend.Tool

View File

@ -32,7 +32,7 @@ type Config struct {
CWD string
History *agent.History
Runtime *agent.Runtime
NewDispatcher func() tools.Dispatcher
NewToolServer func() tools.Server
NewBackend func(string) (backend.Backend, error)
Log *olog.Logger
MaxSteps int
@ -150,7 +150,7 @@ func New(cfg Config) *Session {
CWD: paths.ExpandHome(cfg.CWD),
BaseLayers: cfg.BaseLayers,
PromptEnvExtra: cfg.PromptEnvExtra,
NewDispatcher: cfg.NewDispatcher,
NewToolServer: cfg.NewToolServer,
NewBackend: cfg.NewBackend,
Bus: bus,
Log: log,