Skip to content

Design boundary? transfer_task inside a run_background_agent sub-agent fails with 'target agent not in sub-agents list' because handler uses CurrentAgent() instead of the pinned session agent #3886

Description

@yangtuooc

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.

Metadata

Metadata

Assignees

Labels

area/agentFor work that has to do with the general agent loop/agentic features of the apparea/runtimeRuntime engine, agent loop execution, tool dispatch, loop detection

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions