Skip to content

fix(runtime): resolve transfer_task caller from pinned session agent - #3893

Open
yangtuooc wants to merge 1 commit into
docker:mainfrom
yangtuooc:fix/background-agent-transfer-task
Open

fix(runtime): resolve transfer_task caller from pinned session agent#3893
yangtuooc wants to merge 1 commit into
docker:mainfrom
yangtuooc:fix/background-agent-transfer-task

Conversation

@yangtuooc

Copy link
Copy Markdown

What

Fixes transfer_task failing inside a run_background_agent sub-session. See issue #3886.

Problem

A coordinator (root) fans out work via run_background_agent to a composite agent (pipeline) that has its own sub_agents (e.g. director). When pipeline calls transfer_task(agent="director"), it fails with:

Agent root cannot transfer task to director: target agent not in sub-agents list. Available agent IDs are: pipeline

The error names root as the caller even though the call originates from pipeline, whose sub-agents do include director.

Root cause

handleTaskTransfer resolves the calling agent via r.CurrentAgent(), which returns the runtime's shared current-agent field. Background sub-sessions are created with PinAgent: true + WithAgentName(cfg.AgentName) (RunAgentrunCollecting), which pins the session to pipeline but deliberately does not mutate currentAgent (it stays root). So validation runs against root.SubAgents() = [pipeline] and rejects director.

agentRouter.ResolveSession already handles pinned sessions ("when sess pins a specific agent (e.g. background agent tasks), that agent is returned directly"), but handleTaskTransfer wasn't using it.

Fix

Resolve the caller via resolveSessionAgent(sess) instead of CurrentAgent(). This returns the pinned agent when the session pins one, and falls back to CurrentAgent() otherwise — so ordinary transfer_task behavior is unchanged.

Tests

Adds TestHandleTaskTransfer_UsesPinnedSessionAgent: a session pinned to pipeline (as run_background_agent does) transfers to director while the runtime's current-agent is root.

  • Without fix: fails with the exact error above
  • With fix: passes

go test ./pkg/runtime/ passes.

Closes #3886

transfer_task inside a background sub-session failed because handleTaskTransfer
resolved the calling agent via CurrentAgent(), which returns the shared
current-agent (the root coordinator) rather than the agent pinned to the
sub-session. Background sub-sessions pin their agent (PinAgent) without
mutating currentAgent, so a pipeline dispatched via run_background_agent
could not delegate to its own sub-agents.

Resolve the caller via resolveSessionAgent(sess), which returns the pinned
agent when the session pins one and falls back to CurrentAgent otherwise.
Adds a regression test reproducing the background delegation failure.

Closes docker#3886
@yangtuooc
yangtuooc requested a review from a team as a code owner August 3, 2026 12:13
@aheritier aheritier added kind/fix PR fixes a bug (maps to fix:). Use on PRs only. area/runtime Runtime engine, agent loop execution, tool dispatch, loop detection labels Aug 3, 2026
@aheritier
aheritier requested a review from docker-agent August 4, 2026 07:11
@aheritier

Copy link
Copy Markdown
Collaborator

/review

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

👋 Heads up: The /review command is deprecated. Please re-request a review from docker-agent in the PR sidebar instead.

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

The fix is correct: replacing r.CurrentAgent() with r.resolveSessionAgent(sess) properly resolves the calling agent from the session's pinned agent in background sub-sessions, fixing the transfer_task regression described in #3886. The test correctly demonstrates the regression scenario. One low-severity note on a new edge case fallback is posted inline.

}

a := r.CurrentAgent()
a := r.resolveSessionAgent(sess)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] Silent fallback to current agent when session AgentName is unrecognized

ResolveSession falls back to r.Current() when sess.AgentName is non-empty but the named agent is not found in the team (e.g., if an agent was removed or renamed after the session was created). In that edge case, handleTaskTransfer will validate the transfer against the runtime's current agent instead of the session's intended pinned agent — which could either permit a transfer it should reject, or reject one it should permit.

This is a new code path introduced by this PR: before the change, CurrentAgent() was always used (sessions were never pinned), so the sess.AgentName != "" fallback didn't exist. In practice the risk is low because a session referencing a non-existent agent is already an erroneous state, and Current() is a reasonable fallback. Consider logging a warning when the named agent is not found (inside ResolveSession or here) so it's observable when this fallback fires.

Confidence Score
🟡 moderate 75/100

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

@Sayt-0 Sayt-0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation fix is correct, but one in-scope issue remains before this can be approved.

handleTaskTransfer now resolves the caller from the pinned session. However, runForwarding immediately resolves callerAgent again from r.currentAgentName(), and SwitchCurrentAgent: true mutates the runtime's shared current-agent state.

For root -> background pipeline -> director, this means:

  • validation correctly uses pipeline;
  • AgentSwitching, hooks, and SubSessionCompleted are still attributed to root;
  • the background goroutine temporarily changes the shared current agent to director, which can affect the foreground session or another concurrent background task.

A focused test against this commit confirmed that AgentSwitching.FromAgent is root instead of pipeline.

Could the session-resolved caller be carried into runForwarding, and could transfers from pinned sessions avoid mutating the shared currentAgent state, with tests covering:

  1. switching and completion attribution to pipeline;
  2. the shared current agent remaining root;
  3. isolation during concurrent background delegation?

The cycle detection and delegation depth guard discussed in #3886 can be tracked as a separate follow-up and does not need to expand this PR.

@aheritier aheritier added the status/needs-signed-commits Some commits in the PR are signed with a valid SSH/GPG key label Aug 4, 2026
@aheritier

Copy link
Copy Markdown
Collaborator

👋 Some commits in this PR are not signed and verified by GitHub. Please sign your commits with a GPG or SSH key registered in your GitHub account, then force-push.

Commits that are not verified: f00a794

See GitHub's guide on signing commits for setup instructions. I've added status/needs-signed-commits; it will be removed automatically once every commit in this PR carries a valid GitHub-verified signature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/runtime Runtime engine, agent loop execution, tool dispatch, loop detection kind/fix PR fixes a bug (maps to fix:). Use on PRs only. status/needs-signed-commits Some commits in the PR are signed with a valid SSH/GPG key

Projects

None yet

4 participants