@@ -254,6 +254,9 @@ pub(crate) struct SubagentExecutionRequest {
254254 pub ( crate ) model_binding_policy : SessionModelBindingPolicy ,
255255 pub ( crate ) workspace_path : Option < String > ,
256256 pub ( crate ) model_id : Option < String > ,
257+ /// Explicitly select the current parent session's model instead of a
258+ /// configured subagent default.
259+ pub ( crate ) inherit_parent_model : bool ,
257260 pub ( crate ) subagent_parent_info : SubagentParentInfo ,
258261 pub ( crate ) context : HashMap < String , String > ,
259262 /// Execution policy for the child subagent session being launched.
@@ -6416,11 +6419,18 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
64166419 async fn resolve_fresh_subagent_model_id (
64176420 & self ,
64186421 explicit_model_id : Option < & str > ,
6422+ inherit_parent_model : bool ,
64196423 agent_type : & str ,
64206424 workspace_path : & str ,
64216425 parent_session_id : & str ,
64226426 ) -> BitFunResult < String > {
64236427 let defaults = Self :: agent_model_defaults ( ) . await ;
6428+ if inherit_parent_model {
6429+ return normalize_model_selection (
6430+ & self . parent_model_selection ( parent_session_id, & defaults) ?,
6431+ )
6432+ . await ;
6433+ }
64246434 let registry = get_agent_registry ( ) ;
64256435 let configured_selection = registry
64266436 . get_explicit_subagent_model_selection ( agent_type, Some ( Path :: new ( workspace_path) ) )
@@ -6459,6 +6469,13 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
64596469 . map ( str:: trim)
64606470 . filter ( |model_id| !model_id. is_empty ( ) )
64616471 . map ( str:: to_string) ;
6472+ let inherit_parent_model = request. inherit_parent_model ;
6473+ if inherit_parent_model && model_id. is_some ( ) {
6474+ return Err ( BitFunError :: Validation (
6475+ "A subagent model request cannot specify both a model ID and parent inheritance"
6476+ . to_string ( ) ,
6477+ ) ) ;
6478+ }
64626479 let created_by = Some ( format ! (
64636480 "session-{}" ,
64646481 request. subagent_parent_info. session_id
@@ -6492,8 +6509,20 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
64926509 background_allow_review_follow_up,
64936510 )
64946511 . await ?;
6495- if let Some ( model_id) = model_id. as_deref ( ) {
6496- let model_id = normalize_model_selection ( model_id) . await ?;
6512+ let requested_model_id = if inherit_parent_model {
6513+ let defaults = Self :: agent_model_defaults ( ) . await ;
6514+ Some (
6515+ normalize_model_selection (
6516+ & self . parent_model_selection ( & parent_session_id, & defaults) ?,
6517+ )
6518+ . await ?,
6519+ )
6520+ } else if let Some ( model_id) = model_id. as_deref ( ) {
6521+ Some ( normalize_model_selection ( model_id) . await ?)
6522+ } else {
6523+ None
6524+ } ;
6525+ if let Some ( model_id) = requested_model_id {
64976526 let session_id = session. session_id . clone ( ) ;
64986527 self . session_manager
64996528 . update_session_model_id ( & session_id, & model_id)
@@ -6560,7 +6589,7 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
65606589 request. model_binding_policy,
65616590 SessionModelBindingPolicy :: ApprovedImmutable
65626591 ) {
6563- if model_id. is_some ( ) {
6592+ if model_id. is_some ( ) || inherit_parent_model {
65646593 return Err ( BitFunError :: Validation (
65656594 "An approved immutable subagent model cannot be overridden" . to_string ( ) ,
65666595 ) ) ;
@@ -6577,6 +6606,7 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
65776606 } else {
65786607 self . resolve_fresh_subagent_model_id (
65796608 model_id. as_deref ( ) ,
6609+ inherit_parent_model,
65806610 & agent_type,
65816611 & workspace_path,
65826612 & request. subagent_parent_info . session_id ,
@@ -6649,8 +6679,9 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
66496679 . await ?;
66506680 }
66516681 let defaults = Self :: agent_model_defaults ( ) . await ;
6652- let parent_model_id = if model_id. is_none ( )
6653- && matches ! ( & defaults. subagents. fork, SubagentModelSelection :: Inherit )
6682+ let parent_model_id = if inherit_parent_model
6683+ || ( model_id. is_none ( )
6684+ && matches ! ( & defaults. subagents. fork, SubagentModelSelection :: Inherit ) )
66546685 {
66556686 Some (
66566687 trimmed_model_id ( snapshot. session_model_id . as_deref ( ) )
@@ -6665,11 +6696,19 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
66656696 } else {
66666697 None
66676698 } ;
6668- let model_selection = resolve_subagent_model_selection (
6669- model_id. as_deref ( ) ,
6670- & defaults. subagents . fork ,
6671- parent_model_id. as_deref ( ) ,
6672- ) ?;
6699+ let model_selection = if inherit_parent_model {
6700+ parent_model_id. ok_or_else ( || {
6701+ BitFunError :: Validation (
6702+ "Fork parent session has no model selection" . to_string ( ) ,
6703+ )
6704+ } ) ?
6705+ } else {
6706+ resolve_subagent_model_selection (
6707+ model_id. as_deref ( ) ,
6708+ & defaults. subagents . fork ,
6709+ parent_model_id. as_deref ( ) ,
6710+ ) ?
6711+ } ;
66736712 let resolved_model_id = normalize_model_selection ( & model_selection) . await ?;
66746713 let mut session_config = snapshot. build_child_session_config ( None ) ;
66756714 session_config. model_id = Some ( resolved_model_id) ;
@@ -9237,6 +9276,7 @@ mod tests {
92379276 model_binding_policy : SessionModelBindingPolicy :: Mutable ,
92389277 workspace_path : Some ( workspace_path. to_string_lossy ( ) . into_owned ( ) ) ,
92399278 model_id : None ,
9279+ inherit_parent_model : false ,
92409280 subagent_parent_info : SubagentParentInfo {
92419281 session_id : "parent-session" . to_string ( ) ,
92429282 dialog_turn_id : "parent-turn" . to_string ( ) ,
@@ -9289,6 +9329,7 @@ mod tests {
92899329 model_binding_policy : SessionModelBindingPolicy :: Mutable ,
92909330 workspace_path : None ,
92919331 model_id : None ,
9332+ inherit_parent_model : false ,
92929333 subagent_parent_info : SubagentParentInfo {
92939334 session_id : "parent-session" . to_string ( ) ,
92949335 dialog_turn_id : "parent-turn" . to_string ( ) ,
@@ -9383,6 +9424,7 @@ mod tests {
93839424 model_binding_policy : SessionModelBindingPolicy :: Mutable ,
93849425 workspace_path : None ,
93859426 model_id : Some ( "fast" . to_string ( ) ) ,
9427+ inherit_parent_model : false ,
93869428 subagent_parent_info : SubagentParentInfo {
93879429 session_id : parent_session. session_id . clone ( ) ,
93889430 dialog_turn_id : "parent-turn" . to_string ( ) ,
@@ -9457,6 +9499,7 @@ mod tests {
94579499 model_binding_policy : SessionModelBindingPolicy :: Mutable ,
94589500 workspace_path : None ,
94599501 model_id : None ,
9502+ inherit_parent_model : false ,
94609503 subagent_parent_info : SubagentParentInfo {
94619504 session_id : parent_session. session_id . clone ( ) ,
94629505 dialog_turn_id : "parent-turn" . to_string ( ) ,
@@ -9570,6 +9613,7 @@ mod tests {
95709613 model_binding_policy : SessionModelBindingPolicy :: Mutable ,
95719614 workspace_path : None ,
95729615 model_id : None ,
9616+ inherit_parent_model : false ,
95739617 subagent_parent_info : SubagentParentInfo {
95749618 session_id : parent_session. session_id . clone ( ) ,
95759619 dialog_turn_id : "parent-turn" . to_string ( ) ,
@@ -10309,7 +10353,51 @@ mod tests {
1030910353 }
1031010354
1031110355 #[ tokio:: test]
10312- async fn reused_subagent_send_input_updates_requested_model ( ) {
10356+ async fn fresh_subagent_request_can_explicitly_inherit_parent_model ( ) {
10357+ let ( coordinator, session_manager) = test_coordinator ( ) ;
10358+ let workspace_path = std:: env:: temp_dir ( ) . join ( format ! (
10359+ "bitfun-fresh-subagent-inherit-test-{}" ,
10360+ uuid:: Uuid :: new_v4( )
10361+ ) ) ;
10362+ std:: fs:: create_dir_all ( & workspace_path) . expect ( "workspace dir should exist" ) ;
10363+ struct TempWorkspaceGuard ( std:: path:: PathBuf ) ;
10364+ impl Drop for TempWorkspaceGuard {
10365+ fn drop ( & mut self ) {
10366+ let _ = std:: fs:: remove_dir_all ( & self . 0 ) ;
10367+ }
10368+ }
10369+ let _workspace_guard = TempWorkspaceGuard ( workspace_path. clone ( ) ) ;
10370+ let parent_session = session_manager
10371+ . create_session (
10372+ "Parent" . to_string ( ) ,
10373+ "agentic" . to_string ( ) ,
10374+ SessionConfig {
10375+ model_id : Some ( "primary" . to_string ( ) ) ,
10376+ workspace_path : Some ( workspace_path. to_string_lossy ( ) . into_owned ( ) ) ,
10377+ ..Default :: default ( )
10378+ } ,
10379+ )
10380+ . await
10381+ . expect ( "parent session should be created" ) ;
10382+
10383+ let model_id = coordinator
10384+ . resolve_fresh_subagent_model_id (
10385+ None ,
10386+ true ,
10387+ "Explore" ,
10388+ workspace_path
10389+ . to_str ( )
10390+ . expect ( "workspace path should be UTF-8" ) ,
10391+ & parent_session. session_id ,
10392+ )
10393+ . await
10394+ . expect ( "fresh subagent request should inherit the parent model" ) ;
10395+
10396+ assert_eq ! ( model_id, "primary" ) ;
10397+ }
10398+
10399+ #[ tokio:: test]
10400+ async fn reused_subagent_send_input_updates_requested_and_inherited_model ( ) {
1031310401 let ( coordinator, session_manager) = test_coordinator ( ) ;
1031410402 let workspace_path = std:: env:: temp_dir ( ) . join ( format ! (
1031510403 "bitfun-reused-subagent-model-test-{}" ,
@@ -10324,6 +10412,19 @@ mod tests {
1032410412 }
1032510413 let _workspace_guard = TempWorkspaceGuard ( workspace_path. clone ( ) ) ;
1032610414
10415+ let parent_session = session_manager
10416+ . create_session (
10417+ "Parent" . to_string ( ) ,
10418+ "agentic" . to_string ( ) ,
10419+ SessionConfig {
10420+ model_id : Some ( "primary" . to_string ( ) ) ,
10421+ workspace_path : Some ( workspace_path. to_string_lossy ( ) . into_owned ( ) ) ,
10422+ ..Default :: default ( )
10423+ } ,
10424+ )
10425+ . await
10426+ . expect ( "parent session should be created" ) ;
10427+
1032710428 let subagent_session = coordinator
1032810429 . create_hidden_agent_session (
1032910430 None ,
@@ -10334,7 +10435,7 @@ mod tests {
1033410435 workspace_path : Some ( workspace_path. to_string_lossy ( ) . into_owned ( ) ) ,
1033510436 ..Default :: default ( )
1033610437 } ,
10337- Some ( "session-parent-session" . to_string ( ) ) ,
10438+ Some ( format ! ( "session-{}" , parent_session . session_id ) ) ,
1033810439 SessionKind :: Subagent ,
1033910440 )
1034010441 . await
@@ -10350,8 +10451,9 @@ mod tests {
1035010451 model_binding_policy : SessionModelBindingPolicy :: Mutable ,
1035110452 workspace_path : None ,
1035210453 model_id : Some ( "fast" . to_string ( ) ) ,
10454+ inherit_parent_model : false ,
1035310455 subagent_parent_info : SubagentParentInfo {
10354- session_id : "parent-session" . to_string ( ) ,
10456+ session_id : parent_session . session_id . clone ( ) ,
1035510457 dialog_turn_id : "parent-turn" . to_string ( ) ,
1035610458 tool_call_id : "task-tool" . to_string ( ) ,
1035710459 } ,
@@ -10375,6 +10477,43 @@ mod tests {
1037510477 . as_deref( ) ,
1037610478 Some ( "fast" )
1037710479 ) ;
10480+
10481+ let inherit_request = SubagentExecutionRequest {
10482+ task_description : "Continue with the parent model" . to_string ( ) ,
10483+ context_mode : SubagentContextMode :: Fresh ,
10484+ target_session_id : Some ( subagent_session. session_id . clone ( ) ) ,
10485+ subagent_type : None ,
10486+ logical_subagent_type : None ,
10487+ continuation_policy : SessionContinuationPolicy :: Reusable ,
10488+ model_binding_policy : SessionModelBindingPolicy :: Mutable ,
10489+ workspace_path : None ,
10490+ model_id : None ,
10491+ inherit_parent_model : true ,
10492+ subagent_parent_info : SubagentParentInfo {
10493+ session_id : parent_session. session_id . clone ( ) ,
10494+ dialog_turn_id : "parent-turn" . to_string ( ) ,
10495+ tool_call_id : "task-tool" . to_string ( ) ,
10496+ } ,
10497+ context : HashMap :: new ( ) ,
10498+ delegation_policy : DelegationPolicy :: top_level ( ) . spawn_child ( ) ,
10499+ external_generation_lease : None ,
10500+ } ;
10501+
10502+ let prepared = coordinator
10503+ . prepare_subagent_execution_request ( inherit_request)
10504+ . await
10505+ . expect ( "send_input request should inherit the parent model" ) ;
10506+
10507+ assert_eq ! ( prepared. session_config. model_id. as_deref( ) , Some ( "primary" ) ) ;
10508+ assert_eq ! (
10509+ session_manager
10510+ . get_session( & subagent_session. session_id)
10511+ . expect( "subagent session should remain available" )
10512+ . config
10513+ . model_id
10514+ . as_deref( ) ,
10515+ Some ( "primary" )
10516+ ) ;
1037810517 }
1037910518
1038010519 #[ tokio:: test]
@@ -10396,7 +10535,7 @@ mod tests {
1039610535 "Parent" . to_string ( ) ,
1039710536 "agentic" . to_string ( ) ,
1039810537 SessionConfig {
10399- model_id : Some ( "parent-model " . to_string ( ) ) ,
10538+ model_id : Some ( "primary " . to_string ( ) ) ,
1040010539 workspace_path : Some ( workspace_path. to_string_lossy ( ) . into_owned ( ) ) ,
1040110540 ..Default :: default ( )
1040210541 } ,
@@ -10420,6 +10559,7 @@ mod tests {
1042010559 model_binding_policy : SessionModelBindingPolicy :: Mutable ,
1042110560 workspace_path : None ,
1042210561 model_id : Some ( "fast" . to_string ( ) ) ,
10562+ inherit_parent_model : false ,
1042310563 subagent_parent_info : SubagentParentInfo {
1042410564 session_id : parent_session. session_id . clone ( ) ,
1042510565 dialog_turn_id : "parent-turn" . to_string ( ) ,
@@ -10449,6 +10589,34 @@ mod tests {
1044910589 . as_deref( ) ,
1045010590 Some ( "fast" )
1045110591 ) ;
10592+
10593+ let inherit_request = SubagentExecutionRequest {
10594+ task_description : "Fork with the parent model" . to_string ( ) ,
10595+ context_mode : SubagentContextMode :: Fork ,
10596+ target_session_id : None ,
10597+ subagent_type : None ,
10598+ logical_subagent_type : None ,
10599+ continuation_policy : SessionContinuationPolicy :: Reusable ,
10600+ model_binding_policy : SessionModelBindingPolicy :: Mutable ,
10601+ workspace_path : None ,
10602+ model_id : None ,
10603+ inherit_parent_model : true ,
10604+ subagent_parent_info : SubagentParentInfo {
10605+ session_id : parent_session. session_id . clone ( ) ,
10606+ dialog_turn_id : "parent-turn" . to_string ( ) ,
10607+ tool_call_id : "task-tool" . to_string ( ) ,
10608+ } ,
10609+ context : HashMap :: new ( ) ,
10610+ delegation_policy : DelegationPolicy :: top_level ( ) . spawn_child ( ) ,
10611+ external_generation_lease : None ,
10612+ } ;
10613+
10614+ let prepared = coordinator
10615+ . prepare_subagent_execution_request ( inherit_request)
10616+ . await
10617+ . expect ( "fork request should inherit the parent model" ) ;
10618+
10619+ assert_eq ! ( prepared. session_config. model_id. as_deref( ) , Some ( "primary" ) ) ;
1045210620 }
1045310621
1045410622 #[ tokio:: test]
0 commit comments