From 5837d36f39816d412a235b0ca9ee89774b4b6745 Mon Sep 17 00:00:00 2001 From: Leandro Barbosa Date: Wed, 18 Feb 2026 15:46:56 -0300 Subject: [PATCH] feat: wire tool policy pipeline into agent registration and handoff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply per-agent tool policy (allow/deny) at startup in registerSharedTools so denied tools are removed before the LLM sees them. Apply depth-based policy in ExecuteHandoff: at max depth, leaf agents lose spawn/handoff/ list_agents to prevent further chaining. Clone is lightweight — shares tool instances, only copies the map. --- pkg/agent/loop.go | 21 ++++++++++++++++++++- pkg/multiagent/handoff.go | 11 ++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 9315ccf34..ca763b97c 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -203,11 +203,30 @@ func registerSharedTools(cfg *config.Config, msgBus *bus.MessageBus, registry *A agent.Tools.Register(multiagent.NewListAgentsTool(resolver)) } - // Update context builder with the complete tools registry + // Apply per-agent tool policy (static, startup-time filtering). + // This removes denied tools from the registry before the LLM ever sees them. + if agentCfg := findAgentConfig(cfg, agentID); agentCfg != nil && agentCfg.ToolPolicy != nil { + tools.ApplyPolicy(agent.Tools, tools.ToolPolicy{ + Allow: agentCfg.ToolPolicy.Allow, + Deny: agentCfg.ToolPolicy.Deny, + }) + } + + // Update context builder with the (possibly filtered) tools registry agent.ContextBuilder.SetToolsRegistry(agent.Tools) } } +// findAgentConfig returns the AgentConfig for a given agent ID, or nil if not found. +func findAgentConfig(cfg *config.Config, agentID string) *config.AgentConfig { + for i := range cfg.Agents.List { + if routing.NormalizeAgentID(cfg.Agents.List[i].ID) == agentID { + return &cfg.Agents.List[i] + } + } + return nil +} + func (al *AgentLoop) Run(ctx context.Context) error { al.running.Store(true) diff --git a/pkg/multiagent/handoff.go b/pkg/multiagent/handoff.go index fe8063f1f..47859f6c9 100644 --- a/pkg/multiagent/handoff.go +++ b/pkg/multiagent/handoff.go @@ -156,10 +156,19 @@ func ExecuteHandoff(ctx context.Context, resolver AgentResolver, board *Blackboa maxIter = 10 } + // Apply depth-based tool policy: clone target tools and remove depth-denied tools. + // At max depth, leaf agents lose spawn/handoff/list_agents to prevent further chaining. + targetTools := target.Tools + denyList := tools.DepthDenyList(req.Depth+1, maxDepth) + if len(denyList) > 0 && targetTools != nil { + targetTools = target.Tools.Clone() + tools.ApplyPolicy(targetTools, tools.ToolPolicy{Deny: denyList}) + } + loopResult, err := tools.RunToolLoop(ctx, tools.ToolLoopConfig{ Provider: target.Provider, Model: target.Model, - Tools: target.Tools, + Tools: targetTools, MaxIterations: maxIter, LLMOptions: map[string]any{ "max_tokens": 4096,