Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/runtime/agent_delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ func (r *LocalRuntime) handleTaskTransfer(ctx context.Context, sess *session.Ses
return nil, fmt.Errorf("invalid arguments: %w", err)
}

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

if errResult := validateAgentInList(a.Name(), params.Agent, "transfer task to", "sub-agents list", a.SubAgents()); errResult != nil {
return errResult, nil
}
Expand Down
50 changes: 50 additions & 0 deletions pkg/runtime/agent_delegation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -643,3 +643,53 @@ func TestTransferTask_PropagatesPermissions(t *testing.T) {
assert.Equal(t, []string{"safe_tool"}, parentClone.Allow,
"parent permissions must remain isolated from child mutations after transfer_task")
}

// TestHandleTaskTransfer_UsesPinnedSessionAgent verifies that transfer_task
// inside a background sub-session resolves the caller from the session's
// pinned agent, not the runtime's shared current-agent. Regression test for
// the bug where a pipeline agent dispatched via run_background_agent could
// not transfer_task to its own sub-agents because current-agent remained
// the root coordinator.
func TestHandleTaskTransfer_UsesPinnedSessionAgent(t *testing.T) {
t.Parallel()

childStream := newStreamBuilder().AddContent("done").AddStopWithUsage(10, 5).Build()
prov := &mockProvider{id: "test/mock-model", stream: childStream}

// root only knows pipeline; pipeline knows director.
director := agent.New("director", "Director agent", agent.WithModel(prov))
pipeline := agent.New("pipeline", "Pipeline agent", agent.WithModel(prov))
agent.WithSubAgents(director)(pipeline)
root := agent.New("root", "Root agent", agent.WithModel(prov))
agent.WithSubAgents(pipeline)(root)

tm := team.New(team.WithAgents(root, pipeline, director))
rt, err := NewLocalRuntime(t.Context(), tm,
WithSessionCompaction(false),
WithModelStore(mockModelStore{}),
)
require.NoError(t, err)

// background sub-session pinned to pipeline (as run_background_agent does).
sess := session.New(
session.WithUserMessage("Please proceed."),
session.WithAgentName("pipeline"),
session.WithToolsApproved(true),
)
evts := make(chan Event, 128)

toolCall := tools.ToolCall{
ID: "call_1",
Type: "function",
Function: tools.FunctionCall{
Name: "transfer_task",
Arguments: `{"agent":"director","task":"write a prompt","expected_output":"prompt text"}`,
},
}

result, err := rt.handleTaskTransfer(t.Context(), sess, toolCall, NewChannelSink(evts))
require.NoError(t, err)
require.NotNil(t, result)
assert.False(t, result.IsError,
"pipeline (pinned session) should be able to transfer to its sub-agent director; got: %s", result.Output)
}
Loading