From 46c0e936fac81102bc1f6bb05499944257d7f3de Mon Sep 17 00:00:00 2001 From: Anton Bogdanovich <27antonb@gmail.com> Date: Wed, 6 May 2026 15:14:55 -0700 Subject: [PATCH] fix(agents): route spawn to target agent --- pkg/agent/subturn.go | 42 ++++++++++++++++++++++++------------------ pkg/tools/subagent.go | 1 + 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/pkg/agent/subturn.go b/pkg/agent/subturn.go index 86617d02f..6df05f67f 100644 --- a/pkg/agent/subturn.go +++ b/pkg/agent/subturn.go @@ -103,6 +103,7 @@ type subTurnRuntimeConfig struct { // // Result also available in parent's pendingResults channel // // Parent turn will poll and process it in a later iteration type SubTurnConfig struct { + AgentID string Model string Tools []tools.Tool SystemPrompt string @@ -224,6 +225,7 @@ func (s *AgentLoopSpawner) SpawnSubTurn( // Convert tools.SubTurnConfig to agent.SubTurnConfig agentCfg := SubTurnConfig{ + AgentID: cfg.AgentID, Model: cfg.Model, Tools: cfg.Tools, SystemPrompt: cfg.SystemPrompt, @@ -318,9 +320,14 @@ func spawnSubTurn( return nil, ErrDepthLimitExceeded } - // 2. Config validation: Model is required unless TargetAgentID is set - // (the target agent provides its own model). - if cfg.Model == "" && cfg.TargetAgentID == "" { + // 2. Config validation: Model is required unless a target agent is set + // (the target agent provides its own model). Accept both TargetAgentID + // and AgentID for backward compatibility with older spawn callers. + targetAgentID := cfg.TargetAgentID + if targetAgentID == "" { + targetAgentID = cfg.AgentID + } + if cfg.Model == "" && targetAgentID == "" { return nil, ErrInvalidSubTurnConfig } @@ -338,23 +345,22 @@ func spawnSubTurn( childID := al.generateSubTurnID() - // Resolve the agent instance for the child turn. - // When TargetAgentID is set, look up that agent from the registry so the - // child runs with the target's workspace, model, tools, and system prompt. - // Otherwise fall back to the parent's agent (existing behavior). - var baseAgent *AgentInstance - if cfg.TargetAgentID != "" { - var ok bool - baseAgent, ok = al.registry.GetAgent(cfg.TargetAgentID) - if !ok { - return nil, fmt.Errorf("target agent %q not found in registry", cfg.TargetAgentID) - } - } else { - baseAgent = parentTS.agent - if baseAgent == nil { - baseAgent = al.registry.GetDefaultAgent() + // Get the requested agent instance, falling back to the parent agent and + // then to the default agent. The requested agent path lets spawn(agent_id) + // use that agent's model, tools, workspace, skills, and injected files. + // Wrap it in a shallow copy that uses an ephemeral (in-memory only) session store + // so that child turns never pollute or persist to the parent's session history. + baseAgent := parentTS.agent + if targetAgentID != "" { + if targetAgent, ok := al.registry.GetAgent(targetAgentID); ok { + baseAgent = targetAgent + } else { + return nil, fmt.Errorf("target agent %q not found", targetAgentID) } } + if baseAgent == nil { + baseAgent = al.registry.GetDefaultAgent() + } if baseAgent == nil { return nil, errors.New("parent turnState has no agent instance") } diff --git a/pkg/tools/subagent.go b/pkg/tools/subagent.go index feeabe536..bcad48528 100644 --- a/pkg/tools/subagent.go +++ b/pkg/tools/subagent.go @@ -18,6 +18,7 @@ type SubTurnSpawner interface { // SubTurnConfig holds configuration for spawning a sub-turn. type SubTurnConfig struct { + AgentID string Model string Tools []Tool SystemPrompt string