From b25f17d7c1e20952b90050391d8f2b4673db79ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E6=96=87=E9=94=8B0668000834?= Date: Wed, 18 Mar 2026 10:51:41 +0800 Subject: [PATCH] fix(subagent): ensure spawned subagents receive tools Fix #1713: Subagents spawned via spawn tool have no tools The issue was that SubagentManager was initialized with an empty ToolRegistry instead of the agent's actual tools. This caused spawned subagents to have no tools available during execution. Changes: - In registerSharedTools(), call subagentManager.SetTools(agent.Tools) to pass the agent's tool registry to the subagent manager - Add tests to verify SetTools behavior and default initialization The fix ensures that when a subagent is spawned, it has access to the same tools as the parent agent, allowing proper tool execution in the subagent's RunToolLoop. --- pkg/agent/loop.go | 2 + pkg/tools/subagent_tool_test.go | 79 +++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 58f53bef8..047d004ab 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -222,6 +222,8 @@ func registerSharedTools( if cfg.Tools.IsToolEnabled("subagent") { subagentManager := tools.NewSubagentManager(provider, agent.Model, agent.Workspace) subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature) + // Fix #1713: Pass the agent's tool registry to subagent so spawned subagents have access to tools + subagentManager.SetTools(agent.Tools) spawnTool := tools.NewSpawnTool(subagentManager) currentAgentID := agentID spawnTool.SetAllowlistChecker(func(targetAgentID string) bool { diff --git a/pkg/tools/subagent_tool_test.go b/pkg/tools/subagent_tool_test.go index 4b6f130a5..c194c4e43 100644 --- a/pkg/tools/subagent_tool_test.go +++ b/pkg/tools/subagent_tool_test.go @@ -325,3 +325,82 @@ func TestSubagentTool_ForUserTruncation(t *testing.T) { t.Error("ForLLM should contain reference to original task") } } + +// MockTool is a test tool implementation for verifying subagent receives tools +type MockTool struct { + name string + description string +} + +func (m *MockTool) Name() string { return m.name } +func (m *MockTool) Description() string { return m.description } +func (m *MockTool) Parameters() map[string]any { + return map[string]any{ + "type": "object", + "properties": map[string]any{}, + } +} +func (m *MockTool) Execute(ctx context.Context, args map[string]any) *ToolResult { + return &ToolResult{ForLLM: "mock result"} +} + +// TestSubagentManager_SetTools verifies that SetTools properly passes tools to subagent +func TestSubagentManager_SetTools(t *testing.T) { + provider := &MockLLMProvider{} + manager := NewSubagentManager(provider, "test-model", "/tmp/test") + + // Create a tool registry with some tools + registry := NewToolRegistry() + registry.Register(&MockTool{name: "test_tool", description: "A test tool"}) + registry.Register(&MockTool{name: "another_tool", description: "Another test tool"}) + + // Set the tools on the manager + manager.SetTools(registry) + + // Verify that the tools were set by checking the internal tools field + // We can do this by checking if the manager has tools after SetTools + toolCount := len(registry.List()) + if toolCount != 2 { + t.Fatalf("Expected 2 tools in registry, got: %d", toolCount) + } + + // Verify the tools are the same instance + if manager.tools != registry { + t.Error("Manager's tools should be the same registry instance passed to SetTools") + } +} + +// TestSubagentManager_SetTools_NilRegistry verifies behavior when nil registry is passed +func TestSubagentManager_SetTools_NilRegistry(t *testing.T) { + provider := &MockLLMProvider{} + manager := NewSubagentManager(provider, "test-model", "/tmp/test") + + // Initially, manager has an empty registry from NewSubagentManager + initialTools := manager.tools + if initialTools == nil { + t.Fatal("NewSubagentManager should initialize tools to a non-nil registry") + } + + // Set nil tools - this should replace the registry with nil + manager.SetTools(nil) + + if manager.tools != nil { + t.Error("SetTools(nil) should set tools to nil") + } +} + +// TestSubagentManager_DefaultToolsNotNil verifies that NewSubagentManager initializes tools +func TestSubagentManager_DefaultToolsNotNil(t *testing.T) { + provider := &MockLLMProvider{} + manager := NewSubagentManager(provider, "test-model", "/tmp/test") + + // The tools field should be initialized to a non-nil registry + if manager.tools == nil { + t.Error("NewSubagentManager should initialize tools to a non-nil registry") + } + + // The default registry should be empty + if len(manager.tools.List()) != 0 { + t.Errorf("Default registry should be empty, got: %d tools", len(manager.tools.List())) + } +}