From 659f89fcbff6aaee5c51ce65c09e653a76520da9 Mon Sep 17 00:00:00 2001 From: Ollie Agent Date: Tue, 11 Aug 2026 11:49:19 +0200 Subject: [PATCH] toolsrv: per-agent tool registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tools are now scoped per-agent within a session. The registry uses OLLIE_UNAME (agent identity) to partition loaded tools. Each agent sees only its own tools — load/unload/list/lookup all key on the agent ID read from the toolsrv environment. --- cmd/toolsrv/internal/fs/proc.go | 23 +++++--- cmd/toolsrv/internal/registry/registry.go | 68 +++++++++++++++-------- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/cmd/toolsrv/internal/fs/proc.go b/cmd/toolsrv/internal/fs/proc.go index 8d617dd..de158e7 100644 --- a/cmd/toolsrv/internal/fs/proc.go +++ b/cmd/toolsrv/internal/fs/proc.go @@ -117,29 +117,36 @@ func (st *State) GetEnv(key string) string { // --- Tool Registry --- -// ListTools returns loaded tools. +// agentID returns the current agent identity from the environment. +func (st *State) agentID() string { + return st.env["OLLIE_UNAME"] +} + +// ListTools returns loaded tools for the current agent. func (st *State) ListTools() []toolsrv.ToolInfo { st.mu.RLock() reg := st.registry + aid := st.agentID() st.mu.RUnlock() if reg == nil { return nil } - return reg.Loaded() + return reg.Loaded(aid) } -// LoadTool loads a tool by name into the registry. +// LoadTool loads a tool by name for the current agent. func (st *State) LoadTool(name string) error { st.mu.RLock() reg := st.registry + aid := st.agentID() st.mu.RUnlock() if reg == nil { return fmt.Errorf("no tool registry configured") } - if err := reg.Load(name); err != nil { + if err := reg.Load(aid, name); err != nil { return err } if st.OnToolsChanged != nil { @@ -148,17 +155,18 @@ func (st *State) LoadTool(name string) error { return nil } -// UnloadTool removes a tool from the registry. +// UnloadTool removes a tool from the current agent's registry. func (st *State) UnloadTool(name string) error { st.mu.RLock() reg := st.registry + aid := st.agentID() st.mu.RUnlock() if reg == nil { return fmt.Errorf("no tool registry configured") } - if err := reg.Unload(name); err != nil { + if err := reg.Unload(aid, name); err != nil { return err } if st.OnToolsChanged != nil { @@ -200,6 +208,7 @@ func (st *State) NewProc(ctx context.Context, payload string, background bool) ( // Look up tool st.mu.RLock() reg := st.registry + aid := st.agentID() cwd := st.cwd yolo := st.yolo envCopy := make(map[string]string) @@ -212,7 +221,7 @@ func (st *State) NewProc(ctx context.Context, payload string, background bool) ( return "", 0, fmt.Errorf("tool registry not configured") } - info, ok := reg.Lookup(toolName) + info, ok := reg.Lookup(aid, toolName) if !ok { return "", 0, fmt.Errorf("tool not found: %s", toolName) } diff --git a/cmd/toolsrv/internal/registry/registry.go b/cmd/toolsrv/internal/registry/registry.go index 45a20d5..88ef156 100644 --- a/cmd/toolsrv/internal/registry/registry.go +++ b/cmd/toolsrv/internal/registry/registry.go @@ -1,5 +1,5 @@ // Package registry provides the tool registry for toolsrv. -// One toolsrv per session, so no session-scoping needed. +// Tools are scoped per-agent within a session (one toolsrv per session). package registry import ( @@ -10,17 +10,18 @@ import ( "ollie/toolsrv" ) -// Registry tracks loaded tools. +// Registry tracks loaded tools per agent. type Registry struct { - mu sync.RWMutex - tools map[string]toolsrv.ToolInfo - revision uint64 + mu sync.RWMutex + agents map[string]map[string]toolsrv.ToolInfo + revisions map[string]uint64 } // New creates a new tool registry. func New() (*Registry, error) { return &Registry{ - tools: make(map[string]toolsrv.ToolInfo), + agents: make(map[string]map[string]toolsrv.ToolInfo), + revisions: make(map[string]uint64), }, nil } @@ -34,8 +35,8 @@ func (r *Registry) discover() map[string]toolsrv.ToolInfo { return tools } -// Load loads a tool into the registry. -func (r *Registry) Load(name string) error { +// Load loads a tool for the given agent. +func (r *Registry) Load(agentID, name string) error { tools := r.discover() tool, exists := tools[name] if !exists { @@ -45,50 +46,69 @@ func (r *Registry) Load(name string) error { r.mu.Lock() defer r.mu.Unlock() - r.tools[name] = tool - r.revision++ + if r.agents[agentID] == nil { + r.agents[agentID] = make(map[string]toolsrv.ToolInfo) + } + + r.agents[agentID][name] = tool + r.revisions[agentID]++ return nil } -// Unload removes a tool from the registry. -func (r *Registry) Unload(name string) error { +// Unload removes a tool from an agent's registry. +func (r *Registry) Unload(agentID, name string) error { r.mu.Lock() defer r.mu.Unlock() - if _, exists := r.tools[name]; !exists { + agentTools, ok := r.agents[agentID] + if !ok { return nil } - delete(r.tools, name) - r.revision++ + if _, exists := agentTools[name]; !exists { + return nil + } + + delete(agentTools, name) + r.revisions[agentID]++ return nil } -// Loaded returns all loaded tools. -func (r *Registry) Loaded() []toolsrv.ToolInfo { +// Loaded returns all loaded tools for an agent. +func (r *Registry) Loaded(agentID string) []toolsrv.ToolInfo { r.mu.RLock() defer r.mu.RUnlock() + agentTools, ok := r.agents[agentID] + if !ok { + return nil + } + var loaded []toolsrv.ToolInfo - for _, info := range r.tools { + for _, info := range agentTools { loaded = append(loaded, info) } sort.Slice(loaded, func(i, j int) bool { return loaded[i].Name < loaded[j].Name }) return loaded } -// Lookup returns a tool by name. -func (r *Registry) Lookup(name string) (toolsrv.ToolInfo, bool) { +// Lookup returns a tool by name for an agent. +func (r *Registry) Lookup(agentID, name string) (toolsrv.ToolInfo, bool) { r.mu.RLock() defer r.mu.RUnlock() - tool, exists := r.tools[name] + agentTools, ok := r.agents[agentID] + if !ok { + return toolsrv.ToolInfo{}, false + } + + tool, exists := agentTools[name] return tool, exists } -// Revision returns the registry revision counter. -func (r *Registry) Revision() uint64 { +// Revision returns the revision counter for an agent's tools. +func (r *Registry) Revision(agentID string) uint64 { r.mu.RLock() defer r.mu.RUnlock() - return r.revision + return r.revisions[agentID] }