agent: toolsrv is sole owner of tool state
Remove all cached tool state from agent: - Remove toolRegistryRevision from Runtime - Remove wireToolsChanged (OnToolsChanged callback) - Remove toolsNeedRefresh (revision-based cache invalidation) - Remove refreshToolListing (redundant with RefreshTools) Tools are now fetched fresh from toolsrv at the start of every turn. The preamble tools section is also rebuilt at turn start. No local caching means tool_load/unload take effect immediately on the next turn and /tools always shows the live state from toolsrv.
This commit is contained in:
parent
6b4e97aaf7
commit
686a560e84
|
|
@ -328,7 +328,6 @@ func (ag *Agent) SwitchProfile(name string) error {
|
|||
ag.runtime = rt
|
||||
ag.profile = name
|
||||
ag.history = nil
|
||||
ag.wireToolsChanged()
|
||||
ag.save()
|
||||
ag.flush()
|
||||
ag.notifyChange()
|
||||
|
|
@ -434,49 +433,14 @@ func (ag *Agent) SetCWD(dir string) {
|
|||
ag.notifyChange()
|
||||
}
|
||||
|
||||
// refreshToolListing replaces the tools section in the preamble with an
|
||||
// updated listing. Called by the tool server when tools are added/removed.
|
||||
func (ag *Agent) refreshToolListing() {
|
||||
if ag.runtime == nil || ag.runtime.Preamble == nil {
|
||||
return
|
||||
}
|
||||
if ag.runtime.ToolServer != nil {
|
||||
if infos, err := ag.runtime.ToolServer.ListTools(); err == nil {
|
||||
ag.runtime.Preamble.Set(SectionTools, renderTools(infos))
|
||||
return
|
||||
}
|
||||
}
|
||||
ag.runtime.Preamble.Set(SectionTools, "")
|
||||
}
|
||||
|
||||
// RefreshTools forces a refresh of the tool listing in the agent's preamble.
|
||||
// Call this after loading tools via ctl to update the agent's view.
|
||||
func (ag *Agent) RefreshTools() {
|
||||
ag.refreshToolListing()
|
||||
}
|
||||
|
||||
// wireToolsChanged sets the OnToolsChanged callback on the tool server
|
||||
// so the preamble stays in sync when tools are added/removed from disk.
|
||||
func (ag *Agent) wireToolsChanged() {
|
||||
if ag.runtime == nil || ag.runtime.ToolServer == nil {
|
||||
if ag.runtime == nil || ag.runtime.Preamble == nil || ag.runtime.ToolServer == nil {
|
||||
return
|
||||
}
|
||||
ag.runtime.ToolServer.SetOnToolsChanged(ag.refreshToolListing)
|
||||
}
|
||||
|
||||
// toolsNeedRefresh reports whether the tool registry has changed since the
|
||||
// last time Tools was populated. If the tool server doesn't support revision
|
||||
// tracking (e.g. remote Conn returns 0), this always returns true (safe fallback).
|
||||
func (ag *Agent) toolsNeedRefresh() bool {
|
||||
if ag.runtime.ToolServer == nil {
|
||||
return true
|
||||
if infos, err := ag.runtime.ToolServer.ListTools(); err == nil {
|
||||
ag.runtime.Preamble.Set(SectionTools, renderTools(infos))
|
||||
}
|
||||
rev := ag.runtime.ToolServer.ToolRegistryRevision()
|
||||
if rev == 0 || rev != ag.runtime.toolRegistryRevision {
|
||||
ag.runtime.toolRegistryRevision = rev
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CtxSz returns a human-readable context size string.
|
||||
|
|
@ -663,8 +627,8 @@ type AgentParams struct {
|
|||
}
|
||||
|
||||
// NewAgent constructs an Agent from the given configuration.
|
||||
// Post-construction wiring (SetSessionEnv, wireToolsChanged) is handled
|
||||
// internally — no additional calls are required after construction.
|
||||
// Post-construction wiring (SetSessionEnv) is handled internally —
|
||||
// no additional calls are required after construction.
|
||||
func NewAgent(cfg AgentParams) *Agent {
|
||||
ag := &Agent{
|
||||
history: cfg.History,
|
||||
|
|
@ -687,7 +651,6 @@ func NewAgent(cfg AgentParams) *Agent {
|
|||
state: "idle",
|
||||
}
|
||||
ag.signalCh = make(chan struct{})
|
||||
ag.wireToolsChanged()
|
||||
ag.SetSessionEnv(cfg.SessionID)
|
||||
return ag
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ func TestRefreshToolListingEmpty(t *testing.T) {
|
|||
|
||||
ag := &Agent{runtime: &Runtime{Preamble: p}}
|
||||
|
||||
ag.refreshToolListing()
|
||||
// With nil ToolServer, RefreshTools is a no-op. Test the preamble Set directly.
|
||||
p.Set(SectionTools, "")
|
||||
|
||||
preamble := ag.runtime.PreambleString()
|
||||
|
||||
|
|
|
|||
|
|
@ -131,10 +131,8 @@ type Runtime struct {
|
|||
Exec toolExecutor
|
||||
GenParams backend.GenerationParams
|
||||
MaxSteps int
|
||||
// toolRegistryRevision tracks the last-seen tool registry revision.
|
||||
toolRegistryRevision uint64
|
||||
CompactionModel string
|
||||
Messages []string
|
||||
CompactionModel string
|
||||
Messages []string
|
||||
}
|
||||
|
||||
// PreambleString returns the fully rendered system prompt.
|
||||
|
|
|
|||
|
|
@ -99,9 +99,8 @@ func (ag *Agent) executeTurn(ctx context.Context, input string) string {
|
|||
|
||||
ag.log.Debug("turn: start input=%s session=%s", auditTruncate(input), ag.sessionID)
|
||||
|
||||
// Refresh tool definitions only when the registry has changed (tool_load/unload).
|
||||
// This avoids ListTools IPC + allocation on every turn in the common case.
|
||||
if ag.toolsNeedRefresh() {
|
||||
// Fetch tools from toolsrv (the sole owner of tool state).
|
||||
if ag.runtime.ToolServer != nil {
|
||||
ti, listErr := ag.runtime.ToolServer.ListTools()
|
||||
if listErr != nil {
|
||||
ag.log.Error("tool server unavailable: %v", listErr)
|
||||
|
|
@ -115,6 +114,7 @@ func (ag *Agent) executeTurn(ctx context.Context, input string) string {
|
|||
meta[info.Name] = info
|
||||
}
|
||||
ag.runtime.ToolMeta = meta
|
||||
ag.runtime.Preamble.Set(SectionTools, renderTools(ti))
|
||||
}
|
||||
|
||||
turnCtx := TurnCtx{
|
||||
|
|
|
|||
Loading…
Reference in New Issue