feat: refresh autoLoad tools on agent profile switch
When the 'agent <profile>' 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 <agentID>' 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
This commit is contained in:
parent
e0ac54c0fb
commit
8960fb73fd
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 <agentID> <tool>' or 'unload <agentID> <tool>'"),
|
||||
virtfs.Doc("Control: load <agentID> <tool>, unload <agentID> <tool>, clear <agentID>"),
|
||||
virtfs.Write(func(data []byte) error {
|
||||
return srv.Fs.HandleCtl(strings.TrimSpace(string(data)))
|
||||
}),
|
||||
|
|
|
|||
Loading…
Reference in New Issue