feat: wire tool policy pipeline into agent registration and handoff

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.
This commit is contained in:
Leandro Barbosa 2026-02-18 15:46:56 -03:00
parent 0b251e7ee8
commit 5837d36f39
2 changed files with 30 additions and 2 deletions

View file

@ -203,11 +203,30 @@ func registerSharedTools(cfg *config.Config, msgBus *bus.MessageBus, registry *A
agent.Tools.Register(multiagent.NewListAgentsTool(resolver)) 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) 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 { func (al *AgentLoop) Run(ctx context.Context) error {
al.running.Store(true) al.running.Store(true)

View file

@ -156,10 +156,19 @@ func ExecuteHandoff(ctx context.Context, resolver AgentResolver, board *Blackboa
maxIter = 10 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{ loopResult, err := tools.RunToolLoop(ctx, tools.ToolLoopConfig{
Provider: target.Provider, Provider: target.Provider,
Model: target.Model, Model: target.Model,
Tools: target.Tools, Tools: targetTools,
MaxIterations: maxIter, MaxIterations: maxIter,
LLMOptions: map[string]any{ LLMOptions: map[string]any{
"max_tokens": 4096, "max_tokens": 4096,