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.
This commit is contained in:
曾文锋0668000834 2026-03-18 10:51:41 +08:00
parent 2312553286
commit b25f17d7c1
2 changed files with 81 additions and 0 deletions

View file

@ -222,6 +222,8 @@ func registerSharedTools(
if cfg.Tools.IsToolEnabled("subagent") { if cfg.Tools.IsToolEnabled("subagent") {
subagentManager := tools.NewSubagentManager(provider, agent.Model, agent.Workspace) subagentManager := tools.NewSubagentManager(provider, agent.Model, agent.Workspace)
subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature) 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) spawnTool := tools.NewSpawnTool(subagentManager)
currentAgentID := agentID currentAgentID := agentID
spawnTool.SetAllowlistChecker(func(targetAgentID string) bool { spawnTool.SetAllowlistChecker(func(targetAgentID string) bool {

View file

@ -325,3 +325,82 @@ func TestSubagentTool_ForUserTruncation(t *testing.T) {
t.Error("ForLLM should contain reference to original task") 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()))
}
}