From 8960fb73fd80f9ffcd399e4c6b59e1f05592f4df Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Fri, 21 Aug 2026 10:57:59 +0200 Subject: [PATCH] feat: refresh autoLoad tools on agent profile switch When the 'agent ' ctl command switches profiles, the tool registry now clears old tools and loads the new profile's autoLoad list. Previously, switching profiles left the old tools loaded. Changes: - registry: add ClearAgent(agentID) to remove all tools for an agent - server/proc: add ClearAgent wrapper and 'clear ' ctl command - toolclient: add ClearTools() method to ToolsrvConn - agent: SwitchProfile now returns *AgentConfig for tool reload - fs/spec: agent ctl handler clears and reloads tools after switch --- cmd/olliesrv/internal/agent/agent.go | 17 ++++++++-------- cmd/olliesrv/internal/fs/spec.go | 8 +++++++- cmd/olliesrv/internal/toolclient/toolsrv.go | 11 +++++++++++ cmd/toolsrv/internal/registry/registry.go | 8 ++++++++ cmd/toolsrv/internal/server/proc.go | 22 +++++++++++++++++++++ cmd/toolsrv/internal/server/server.go | 2 +- 6 files changed, 58 insertions(+), 10 deletions(-) diff --git a/cmd/olliesrv/internal/agent/agent.go b/cmd/olliesrv/internal/agent/agent.go index 30bb26d..dbe6ca5 100644 --- a/cmd/olliesrv/internal/agent/agent.go +++ b/cmd/olliesrv/internal/agent/agent.go @@ -489,21 +489,22 @@ func (ag *Agent) Clear() error { } // SwitchProfile loads a new agent profile by name, replacing the runtime -// and clearing history. Returns an error if the agent is running or the -// profile cannot be loaded. -func (ag *Agent) SwitchProfile(name string) error { +// and clearing history. Returns the loaded config on success for the caller +// to reload tools. Returns an error if the agent is running or the profile +// cannot be loaded. +func (ag *Agent) SwitchProfile(name string) (*AgentConfig, error) { if ag.IsRunning() { - return fmt.Errorf("cannot switch agent while agent is running") + return nil, fmt.Errorf("cannot switch agent while agent is running") } cfgPath := AgentConfigPath(ag.agentsDir, name) f, err := os.Open(cfgPath) if err != nil { - return fmt.Errorf("agent %q: %w", name, err) + return nil, fmt.Errorf("agent %q: %w", name, err) } cfg, err := Load(f) f.Close() if err != nil { - return fmt.Errorf("agent %q: %w", name, err) + return nil, fmt.Errorf("agent %q: %w", name, err) } disp := ag.newToolServer() if disp != nil && ag.id != "" { @@ -514,7 +515,7 @@ func (ag *Agent) SwitchProfile(name string) error { if cfg.Backend != "" { newBe, err := ag.newBackend(cfg.Backend) if err != nil { - return fmt.Errorf("backend %q: %w", cfg.Backend, err) + return nil, fmt.Errorf("backend %q: %w", cfg.Backend, err) } if cfg.Model != "" { newBe.SetModel(cfg.Model) @@ -532,7 +533,7 @@ func (ag *Agent) SwitchProfile(name string) error { ag.save() ag.flush() ag.notifyChange() - return nil + return cfg, nil } // Event is a typed output event emitted during an agent turn or in response diff --git a/cmd/olliesrv/internal/fs/spec.go b/cmd/olliesrv/internal/fs/spec.go index c7babc6..e23052b 100644 --- a/cmd/olliesrv/internal/fs/spec.go +++ b/cmd/olliesrv/internal/fs/spec.go @@ -1002,9 +1002,15 @@ func buildAgentChildren(a *agent.Agent, s *session.Session) []virtfs.FsNodeDecl if len(args) == 0 { return []byte(a.Profile() + "\n"), nil } - if err := a.SwitchProfile(args[0]); err != nil { + cfg, err := a.SwitchProfile(args[0]) + if err != nil { return nil, err } + // Reload tools for the new profile + if ts := a.ToolServer(); ts != nil { + ts.ClearTools() + session.LoadAutoLoadTools(cfg, ts, s.ID, a.ID(), nil) + } return []byte(args[0] + "\n"), nil }, "model": func(args []string) ([]byte, error) { diff --git a/cmd/olliesrv/internal/toolclient/toolsrv.go b/cmd/olliesrv/internal/toolclient/toolsrv.go index 04af629..0742034 100644 --- a/cmd/olliesrv/internal/toolclient/toolsrv.go +++ b/cmd/olliesrv/internal/toolclient/toolsrv.go @@ -275,6 +275,17 @@ func (c *ToolsrvConn) UnloadTool(name string) error { return err } +// ClearTools removes all tools for this agent. +func (c *ToolsrvConn) ClearTools() error { + fid, err := c.fsys.Open("ctl", plan9.OWRITE) + if err != nil { + return err + } + defer fid.Close() + _, err = fid.Write([]byte("clear " + c.agentID + "\n")) + return err +} + // Ping checks if the connection is alive. func (c *ToolsrvConn) Ping() error { _, err := c.fsys.Stat("info") diff --git a/cmd/toolsrv/internal/registry/registry.go b/cmd/toolsrv/internal/registry/registry.go index d7c45bf..7f48b29 100644 --- a/cmd/toolsrv/internal/registry/registry.go +++ b/cmd/toolsrv/internal/registry/registry.go @@ -80,6 +80,14 @@ func (r *Registry) Unload(agentID, name string) error { return nil } +// ClearAgent removes all tools for an agent. +func (r *Registry) ClearAgent(agentID string) { + r.mu.Lock() + defer r.mu.Unlock() + delete(r.agents, agentID) + delete(r.revisions, agentID) +} + // Loaded returns all loaded tools for an agent. func (r *Registry) Loaded(agentID string) []protocol.ToolInfo { r.mu.RLock() diff --git a/cmd/toolsrv/internal/server/proc.go b/cmd/toolsrv/internal/server/proc.go index 041b91b..9e6ad1b 100644 --- a/cmd/toolsrv/internal/server/proc.go +++ b/cmd/toolsrv/internal/server/proc.go @@ -218,6 +218,22 @@ func (st *State) UnloadTool(agentID, name string) error { return nil } +// ClearAgent removes all tools for an agent's registry. +func (st *State) ClearAgent(agentID string) { + st.mu.RLock() + reg := st.registry + st.mu.RUnlock() + + if reg == nil { + return + } + + reg.ClearAgent(agentID) + if st.OnToolsChanged != nil { + st.OnToolsChanged() + } +} + // --- Process Management --- // allocID allocates a new process ID. @@ -646,6 +662,12 @@ func (st *State) HandleCtl(input string) error { return fmt.Errorf("unload requires agent ID and tool name") } return st.UnloadTool(parts[1], parts[2]) + case "clear": + if len(parts) < 2 { + return fmt.Errorf("clear requires agent ID") + } + st.ClearAgent(parts[1]) + return nil case "env": if len(parts) < 2 { return fmt.Errorf("env requires KEY=VALUE") diff --git a/cmd/toolsrv/internal/server/server.go b/cmd/toolsrv/internal/server/server.go index 6e329d2..4125421 100644 --- a/cmd/toolsrv/internal/server/server.go +++ b/cmd/toolsrv/internal/server/server.go @@ -117,7 +117,7 @@ func randomToken() (string, error) { func Spec(srv *Server) virtfs.FsNodeDecl { return virtfs.DirNode("/", virtfs.FileNode("ctl", 0222, - virtfs.Doc("Control: write 'load ' or 'unload '"), + virtfs.Doc("Control: load , unload , clear "), virtfs.Write(func(data []byte) error { return srv.Fs.HandleCtl(strings.TrimSpace(string(data))) }),