fix(tools): propagate tool registry to subagents#1711
Conversation
SubagentManager was created with an empty ToolRegistry and SetTools() was never called, causing all subagent tool invocations to fail with "tool not found". This was a regression from the multi-agent refactor. Fix: clone the parent agent's tool registry into the subagent manager after creation but before spawn/spawn_status registration — giving subagents access to file, exec, web, and other tools while preventing recursive subagent spawning. - Add ToolRegistry.Clone() for independent shallow copies - Call subagentManager.SetTools(agent.Tools.Clone()) in registerSharedTools - Add tests for Clone isolation, empty clone, and hidden tool state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
yinwm
left a comment
There was a problem hiding this comment.
Code Review
This is a critical bugfix that resolves the issue where subagents were completely non-functional due to missing tool propagation. The changes are minimal and well-targeted.
✅ What's Good
-
Accurate root cause analysis - Correctly identified that
SubagentManagerwas created with an emptyToolRegistryandSetTools()was never called after the multi-agent refactor. -
Correct solution - Using
Clone()to create a snapshot rather than sharing the pointer effectively prevents recursive subagent spawning while giving subagents access to all necessary tools. -
Proper lock usage - Using
RLock()instead ofLock()since we're only reading. -
Performance optimization - Pre-allocating the map with
make(map[string]*ToolEntry, len(r.tools))avoids multiple allocations. -
Good test coverage - Tests verify bidirectional isolation, edge case (empty clone), and hidden tool state preservation.
⚠️ Items to Consider
1. version field not copied
clone := &ToolRegistry{
tools: make(map[string]*ToolEntry, len(r.tools)),
// version field defaults to 0
}The version field in ToolRegistry is used for cache invalidation. The clone starts with version=0, which should be fine (the clone is a new independent registry managing its own version). However, it might be worth adding a note in the comment explaining this behavior.
2. Test doesn't verify TTL value is correctly copied
The current test TestToolRegistry_Clone_PreservesHiddenToolState only checks TTL=0 case. Consider adding a test to verify TTL > 0 values are correctly preserved:
func TestToolRegistry_Clone_PreservesTTL(t *testing.T) {
r := NewToolRegistry()
tool := newMockTool("hidden_tool", "hidden")
r.RegisterHidden(tool)
// Manually set TTL > 0
if entry, ok := r.tools["hidden_tool"]; ok {
entry.TTL = 5
}
clone := r.Clone()
if entry, ok := clone.tools["hidden_tool"]; ok {
if entry.TTL != 5 {
t.Errorf("expected TTL=5, got %d", entry.TTL)
}
}
}💡 Minor Suggestion (Non-blocking)
The comment could mention that version is reset:
// Clone creates an independent copy of the registry containing the same tool
// entries (shallow copy of each ToolEntry). This is used to give subagents a
// snapshot of the parent agent's tools without sharing the same registry —
// tools registered on the parent after cloning (e.g. spawn, spawn_status)
// will NOT be visible to the clone, preventing recursive subagent spawning.
// The version counter is reset to 0 in the clone as it's a new independent registry.🎯 Verdict
| Aspect | Rating |
|---|---|
| Correctness | ✅ Fix logic is correct |
| Thread Safety | ✅ No concurrency issues |
| Maintainability | ✅ Code is clear |
| Test Coverage |
LGTM with minor nits — Ready to merge, but recommend adding a test case for TTL value preservation for completeness.
🤖 Generated with Claude Code
|
plz fix Linter and tests @paoloanzn |
- Fix cron_test.go:229 — replace non-existent SubscribeOutbound(ctx) with select on OutboundChan(), matching the MessageBus channel API - Add TestToolRegistry_Clone_PreservesTTLValue per reviewer feedback - Add version reset note to Clone() doc comment Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@yinwm Thanks for the review! I've pushed a follow-up commit addressing both the CI failures and your review feedback: Fixes:
All tests pass locally ( CI may need approval to re-run since this is a fork PR. |
|
@paoloanzn sorry , there's a conflicts, plz fix |
88b1ea4 to
8851313
Compare
|
thanks for this pr |
my pleasure to contribute. best agent system out there rn 😄 |
|
@paoloanzn Great debugging work here. Tracking down the empty ToolRegistry after the multi-agent refactor and using Clone() to snapshot the parent tools while preventing recursive spawning is a well thought out solution. The isolation tests are thorough too. We have a PicoClaw Dev Group on Discord for contributors to collaborate more directly. Want to join? Send an email to |
thank you for the invite, i've sent the email! |
Summary
SubagentManageris created with an emptyToolRegistry(NewToolRegistry()atsubagent.go:48) andSetTools()is never called after the multi-agent refactor inregisterSharedTools(). This causes every tool invocation from a subagent to return"tool not found", making subagents completely non-functional.registerSharedTools()was introduced, thesubagentManager.SetTools()call that populated the subagent's tools was dropped. TheSetTools()andRegisterTool()methods exist onSubagentManagerbut are never invoked.ToolRegistry.Clone()to create an independent snapshot of the parent agent's tool registry, then callsubagentManager.SetTools(agent.Tools.Clone())immediately after manager creation but beforespawn/spawn_statusregistration — giving subagents access to file, exec, web, and other tools while preventing recursive subagent spawning.Changes
pkg/tools/registry.goClone()method — creates independent shallow copy of a tool registrypkg/agent/loop.gosubagentManager.SetTools(agent.Tools.Clone())inregisterSharedTools()pkg/tools/registry_test.goWhy Clone instead of sharing the pointer?
spawnandspawn_statustools are registered onagent.Toolsafter the subagent manager is created (lines 248-251 ofloop.go). Sharing the registry pointer would give subagents access to spawn tools → recursive subagent spawning.Clone()captures a snapshot with only the tools registered at that point.Test plan
TestToolRegistry_Clone— verifies parent/clone isolation in both directionsTestToolRegistry_Clone_Empty— verifies cloning an empty registryTestToolRegistry_Clone_PreservesHiddenToolState— verifies hidden tool TTL behavior is preservedpkg/toolsandpkg/agentpackages build cleanly🤖 Generated with Claude Code