@@ -1364,3 +1364,75 @@ func TestToolRejectionWithoutReason(t *testing.T) {
13641364 require .Equal (t , "The user rejected the tool call." , toolResponse .Response )
13651365 require .NotContains (t , toolResponse .Response , "Reason:" )
13661366}
1367+
1368+ func TestTransferTaskRejectsNonSubAgent (t * testing.T ) {
1369+ // root has librarian as sub-agent but NOT planner.
1370+ // planner exists in the team. transfer_task to planner should be rejected.
1371+ prov := & mockProvider {id : "test/mock-model" , stream : & mockStream {}}
1372+
1373+ librarian := agent .New ("librarian" , "Library agent" , agent .WithModel (prov ))
1374+ root := agent .New ("root" , "Root agent" , agent .WithModel (prov ))
1375+ planner := agent .New ("planner" , "Planner agent" , agent .WithModel (prov ))
1376+
1377+ agent .WithSubAgents (librarian )(root )
1378+
1379+ tm := team .New (team .WithAgents (root , planner , librarian ))
1380+
1381+ rt , err := NewLocalRuntime (tm , WithSessionCompaction (false ), WithModelStore (mockModelStore {}))
1382+ require .NoError (t , err )
1383+
1384+ sess := session .New (session .WithUserMessage ("Test" ))
1385+ evts := make (chan Event , 128 )
1386+
1387+ toolCall := tools.ToolCall {
1388+ ID : "call_1" ,
1389+ Type : "function" ,
1390+ Function : tools.FunctionCall {
1391+ Name : "transfer_task" ,
1392+ Arguments : `{"agent":"planner","task":"do something","expected_output":""}` ,
1393+ },
1394+ }
1395+
1396+ result , err := rt .handleTaskTransfer (t .Context (), sess , toolCall , evts )
1397+ require .NoError (t , err )
1398+ require .NotNil (t , result )
1399+ assert .True (t , result .IsError , "transfer to non-sub-agent should return an error result" )
1400+ assert .Contains (t , result .Output , "cannot transfer task to planner" )
1401+ assert .Contains (t , result .Output , "librarian" )
1402+ assert .Equal (t , "root" , rt .currentAgent , "current agent should remain root" )
1403+ }
1404+
1405+ func TestTransferTaskAllowsSubAgent (t * testing.T ) {
1406+ // Verify that transfer_task to a valid sub-agent is NOT rejected by the validation.
1407+ // We can't fully run the child session without a real model, so we just confirm
1408+ // it gets past validation (it will fail later due to mock stream being empty,
1409+ // which is fine — we only care that it's not blocked by the sub-agent check).
1410+ prov := & mockProvider {id : "test/mock-model" , stream : newStreamBuilder ().AddContent ("done" ).AddStopWithUsage (10 , 5 ).Build ()}
1411+
1412+ librarian := agent .New ("librarian" , "Library agent" , agent .WithModel (prov ))
1413+ root := agent .New ("root" , "Root agent" , agent .WithModel (prov ))
1414+
1415+ agent .WithSubAgents (librarian )(root )
1416+
1417+ tm := team .New (team .WithAgents (root , librarian ))
1418+
1419+ rt , err := NewLocalRuntime (tm , WithSessionCompaction (false ), WithModelStore (mockModelStore {}))
1420+ require .NoError (t , err )
1421+
1422+ sess := session .New (session .WithUserMessage ("Test" ), session .WithToolsApproved (true ))
1423+ evts := make (chan Event , 128 )
1424+
1425+ toolCall := tools.ToolCall {
1426+ ID : "call_1" ,
1427+ Type : "function" ,
1428+ Function : tools.FunctionCall {
1429+ Name : "transfer_task" ,
1430+ Arguments : `{"agent":"librarian","task":"find a book","expected_output":"book title"}` ,
1431+ },
1432+ }
1433+
1434+ result , err := rt .handleTaskTransfer (t .Context (), sess , toolCall , evts )
1435+ require .NoError (t , err )
1436+ require .NotNil (t , result )
1437+ assert .False (t , result .IsError , "transfer to valid sub-agent should succeed" )
1438+ }
0 commit comments