Summary
I'm trying to confirm whether this is the intended design boundary or a bug: can a sub-agent dispatched via run_background_agent call transfer_task to delegate to its own sub_agents?
Reproduction
A coordinator (root) fans out work with run_background_agent to a composite agent (pipeline), which in turn tries to transfer_task to its own sub-agents (director, producer, reviewer):
agents:
root:
description: Batch scheduler
sub_agents: [pipeline]
toolsets:
- type: background_agents
- type: think
pipeline:
description: Composite pipeline (director -> producer -> reviewer)
sub_agents: [director, producer, reviewer]
toolsets:
- type: think
director: ...
producer: ...
reviewer: ...
The pipeline agent, running inside the background sub-session, calls transfer_task(agent="director", ...) and fails with:
Agent root cannot transfer task to director: target agent not in sub-agents list. Available agent IDs are: pipeline
The error incorrectly names root as the caller (root's sub-agents are only [pipeline]), even though the call originates from pipeline whose sub-agents do include director.
Root cause analysis
pkg/runtime/agent_delegation.go, handleTaskTransfer resolves the calling agent via r.CurrentAgent():
a := r.CurrentAgent()
if errResult := validateAgentInList(a.Name(), params.Agent, "transfer task to", "sub-agents list", a.SubAgents()); ...
Background sub-sessions are created with PinAgent: true + WithAgentName(cfg.AgentName) (in RunAgent -> runCollecting), which pins the session to pipeline but deliberately does NOT mutate the runtime's shared currentAgent field (which remains root). So CurrentAgent() returns root, and the transfer validation fails.
Meanwhile agentRouter.ResolveSession (agent_router.go) explicitly handles this case:
// ResolveSession returns the agent for sess: when sess pins a specific
// agent (e.g. background agent tasks), that agent is returned directly
// instead of reading the shared current-agent field; otherwise Current is returned.
but handleTaskTransfer does not use it.
Question
Is delegating further from inside a background_agent a supported pattern? The docs for background_agents describe dispatching work to sub-agents that complete it themselves (all examples are leaf agents with toolsets, none re-delegate). If background agents are only meant to be leaf executors, then this is working as designed and my configuration is out of scope — please confirm and I'll restructure.
If background agents are meant to support nested delegation, then handleTaskTransfer should resolve the caller via the pinned session agent (resolveSessionAgent(sess)) rather than CurrentAgent().
Verification
I wrote a regression test (TestHandleTaskTransfer_UsesPinnedSessionAgent) that pins a session to pipeline (mimicking run_background_agent) with currentAgent = root, then calls transfer_task to director:
- Without fix: fails with exactly
Agent root cannot transfer task to director: target agent not in sub-agents list. Available agent IDs are: pipeline
- With fix (
a := r.resolveSessionAgent(sess)): passes
Environment: v1.111.0 (runtime), latest main (8003b63) confirmed the code is unchanged.
Summary
I'm trying to confirm whether this is the intended design boundary or a bug: can a sub-agent dispatched via
run_background_agentcalltransfer_taskto delegate to its ownsub_agents?Reproduction
A coordinator (
root) fans out work withrun_background_agentto a composite agent (pipeline), which in turn tries totransfer_taskto its own sub-agents (director,producer,reviewer):The
pipelineagent, running inside the background sub-session, callstransfer_task(agent="director", ...)and fails with:The error incorrectly names root as the caller (root's sub-agents are only
[pipeline]), even though the call originates frompipelinewhose sub-agents do includedirector.Root cause analysis
pkg/runtime/agent_delegation.go,handleTaskTransferresolves the calling agent viar.CurrentAgent():Background sub-sessions are created with
PinAgent: true+WithAgentName(cfg.AgentName)(inRunAgent->runCollecting), which pins the session topipelinebut deliberately does NOT mutate the runtime's sharedcurrentAgentfield (which remainsroot). SoCurrentAgent()returnsroot, and the transfer validation fails.Meanwhile
agentRouter.ResolveSession(agent_router.go) explicitly handles this case:but
handleTaskTransferdoes not use it.Question
Is delegating further from inside a
background_agenta supported pattern? The docs forbackground_agentsdescribe dispatching work to sub-agents that complete it themselves (all examples are leaf agents with toolsets, none re-delegate). If background agents are only meant to be leaf executors, then this is working as designed and my configuration is out of scope — please confirm and I'll restructure.If background agents are meant to support nested delegation, then
handleTaskTransfershould resolve the caller via the pinned session agent (resolveSessionAgent(sess)) rather thanCurrentAgent().Verification
I wrote a regression test (
TestHandleTaskTransfer_UsesPinnedSessionAgent) that pins a session topipeline(mimickingrun_background_agent) withcurrentAgent=root, then callstransfer_tasktodirector:Agent root cannot transfer task to director: target agent not in sub-agents list. Available agent IDs are: pipelinea := r.resolveSessionAgent(sess)): passesEnvironment: v1.111.0 (runtime), latest
main(8003b63) confirmed the code is unchanged.