toolsrv: per-agent tool registry

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.
This commit is contained in:
Ollie Agent 2026-08-11 11:49:19 +02:00
parent 7dfd769c25
commit 659f89fcbf
2 changed files with 60 additions and 31 deletions

View File

@ -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)
}

View File

@ -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]
}