Honor configured model defaults for spawned agents (#33631)

## What changed

- Apply `agents.default_subagent_model` and
  `agents.default_subagent_reasoning_effort` when a spawn request does not
  provide explicit values, including agent job workers.
- Allow full-history forks to use configured defaults or explicit model and
  reasoning-effort overrides while retaining the parent conversation context.
- Preserve the selected model and reasoning effort when an agent role does not
  override them.

## Testing

- Cover configured defaults, explicit override precedence, full-history forks,
  role application, and agent job workers.

GitOrigin-RevId: b1efa8c9abeee82355088fceef57560433144595
This commit is contained in:
jif
2026-07-16 16:08:43 +00:00
committed by copyberry
parent 0f44bca915
commit 21c37fb374
14 changed files with 299 additions and 155 deletions
+2 -2
View File
@@ -688,9 +688,9 @@ pub struct AgentsToml {
pub max_concurrent_threads_per_session: Option<usize>,
/// Maximum nesting depth for V1 agent threads. Ignored by V2.
pub max_depth: Option<i32>,
/// Reserved default model for spawned subagents. Currently ignored.
/// Default model for spawned subagents when the spawn call does not select one.
pub default_subagent_model: Option<String>,
/// Reserved default reasoning effort for spawned subagents. Currently ignored.
/// Default reasoning effort for spawned subagents when the spawn call does not select one.
pub default_subagent_reasoning_effort: Option<ReasoningEffort>,
/// Default maximum runtime in seconds for agent job workers.
#[schemars(range(min = 1))]
+2 -2
View File
@@ -37,7 +37,7 @@
},
"properties": {
"default_subagent_model": {
"description": "Reserved default model for spawned subagents. Currently ignored.",
"description": "Default model for spawned subagents when the spawn call does not select one.",
"type": "string"
},
"default_subagent_reasoning_effort": {
@@ -46,7 +46,7 @@
"$ref": "#/definitions/ReasoningEffort"
}
],
"description": "Reserved default reasoning effort for spawned subagents. Currently ignored."
"description": "Default reasoning effort for spawned subagents when the spawn call does not select one."
},
"enabled": {
"description": "Whether multi-agent tools are enabled. Defaults to true. An enabled `features.multi_agent_v2` setting takes precedence.",
+17 -4
View File
@@ -2,9 +2,9 @@
//!
//! Roles are selected at spawn time and are loaded with the same config machinery as
//! `config.toml`. This module resolves built-in and user-defined role files, inserts the role as a
//! high-precedence layer, and preserves the caller's current provider and service tier unless the
//! role layer sets them. It does not decide when to spawn a sub-agent or which role to use; the
//! multi-agent tool handler owns that orchestration.
//! high-precedence layer, and preserves the caller's current model, reasoning effort, provider,
//! and service tier unless the role layer sets them. It does not decide when to spawn a sub-agent
//! or which role to use; the multi-agent tool handler owns that orchestration.
use crate::config::AgentRoleConfig;
use crate::config::Config;
@@ -135,14 +135,18 @@ mod reload {
preserve_current_provider: bool,
preserve_current_service_tier: bool,
) -> anyhow::Result<Config> {
let preserve_current_model = role_layer_toml.get("model").is_none();
let preserve_current_reasoning_effort =
role_layer_toml.get("model_reasoning_effort").is_none();
let config_layer_stack = build_config_layer_stack(config, &role_layer_toml)?;
let merged_config = deserialize_effective_config(config, &config_layer_stack)?;
let next_config = Config::load_config_with_layer_stack(
let mut next_config = Config::load_config_with_layer_stack(
LOCAL_FS.as_ref(),
merged_config,
reload_overrides(
config,
preserve_current_model,
preserve_current_provider,
preserve_current_service_tier,
),
@@ -150,6 +154,11 @@ mod reload {
config_layer_stack,
)
.await?;
if preserve_current_reasoning_effort {
next_config
.model_reasoning_effort
.clone_from(&config.model_reasoning_effort);
}
Ok(next_config)
}
@@ -200,11 +209,15 @@ mod reload {
fn reload_overrides(
config: &Config,
preserve_current_model: bool,
preserve_current_provider: bool,
preserve_current_service_tier: bool,
) -> ConfigOverrides {
ConfigOverrides {
cwd: Some(config.cwd.to_path_buf()),
model: preserve_current_model
.then(|| config.model.clone())
.flatten(),
model_provider: preserve_current_provider.then(|| config.model_provider_id.clone()),
service_tier: preserve_current_service_tier.then(|| config.service_tier.clone()),
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
+9 -4
View File
@@ -183,8 +183,8 @@ async fn apply_role_preserves_unspecified_keys() {
config.main_execve_wrapper_exe = Some(PathBuf::from("/tmp/codex-execve-wrapper"));
let role_path = write_role_config(
&home,
"effort-only.toml",
"developer_instructions = \"Stay focused\"\nmodel_reasoning_effort = \"high\"",
"instructions-only.toml",
"developer_instructions = \"Stay focused\"",
)
.await;
config.agent_roles.insert(
@@ -196,12 +196,17 @@ async fn apply_role_preserves_unspecified_keys() {
},
);
config.model = Some("spawn-model".to_string());
config.model_reasoning_effort = Some(ReasoningEffort::Low);
apply_role_to_config(&mut config, Some("custom"))
.await
.expect("custom role should apply");
assert_eq!(config.model.as_deref(), Some("base-model"));
assert_eq!(config.model_reasoning_effort, Some(ReasoningEffort::High));
assert_eq!(
(config.model.as_deref(), config.model_reasoning_effort),
(Some("spawn-model"), Some(ReasoningEffort::Low)),
);
assert_eq!(
config.codex_linux_sandbox_exe,
Some(PathBuf::from("/tmp/codex-linux-sandbox"))
+2 -2
View File
@@ -863,10 +863,10 @@ pub struct Config {
/// User-configured maximum number of spawned agent threads per session.
pub agent_max_threads: Option<usize>,
/// Reserved default model for spawned subagents.
/// Default model for spawned subagents when the spawn call does not select one.
pub agent_default_subagent_model: Option<String>,
/// Reserved default reasoning effort for spawned subagents.
/// Default reasoning effort for spawned subagents when the spawn call does not select one.
pub agent_default_subagent_reasoning_effort: Option<ReasoningEffort>,
/// Maximum runtime in seconds for agent job workers before they are failed.
+10 -1
View File
@@ -5,6 +5,7 @@ use crate::function_tool::FunctionCallError;
use crate::session::session::Session;
use crate::session::turn_context::TurnContext;
use crate::tools::handlers::multi_agents::build_agent_spawn_config;
use crate::tools::handlers::multi_agents_common::apply_requested_spawn_agent_model_overrides;
use crate::tools::handlers::parse_arguments;
use codex_protocol::ThreadId;
use codex_protocol::error::CodexErr;
@@ -124,7 +125,15 @@ async fn build_runner_options(
}
let max_concurrency = normalize_concurrency(requested_concurrency, agent_max_threads);
let base_instructions = session.get_base_instructions().await;
let spawn_config = build_agent_spawn_config(&base_instructions, turn.as_ref())?;
let mut spawn_config = build_agent_spawn_config(&base_instructions, turn.as_ref())?;
apply_requested_spawn_agent_model_overrides(
session,
turn,
&mut spawn_config,
/*requested_model*/ None,
/*requested_reasoning_effort*/ None,
)
.await?;
Ok(JobRunnerOptions {
max_concurrency,
spawn_config,
@@ -92,20 +92,17 @@ async fn handle_spawn_agent(
config.service_tier = Some(service_tier.clone());
}
if args.fork_context {
reject_full_fork_spawn_overrides(
role_name,
args.model.as_deref(),
args.reasoning_effort.clone(),
)?;
} else {
apply_requested_spawn_agent_model_overrides(
&session,
turn.as_ref(),
&mut config,
args.model.as_deref(),
args.reasoning_effort.clone(),
)
.await?;
reject_full_fork_agent_type_override(role_name)?;
}
apply_requested_spawn_agent_model_overrides(
&session,
turn.as_ref(),
&mut config,
args.model.as_deref(),
args.reasoning_effort.clone(),
)
.await?;
if !args.fork_context {
apply_role_to_config(&mut config, role_name)
.await
.map_err(FunctionCallError::RespondToModel)?;
@@ -201,14 +201,12 @@ fn build_agent_shared_config(turn: &TurnContext) -> Result<Config, FunctionCallE
Ok(config)
}
pub(crate) fn reject_full_fork_spawn_overrides(
pub(crate) fn reject_full_fork_agent_type_override(
agent_type: Option<&str>,
model: Option<&str>,
reasoning_effort: Option<ReasoningEffort>,
) -> Result<(), FunctionCallError> {
if agent_type.is_some() || model.is_some() || reasoning_effort.is_some() {
if agent_type.is_some() {
return Err(FunctionCallError::RespondToModel(
"Full-history forked agents inherit the parent agent type, model, and reasoning effort; omit agent_type, model, and reasoning_effort, or spawn without a full-history fork.".to_string(),
"Full-history forked agents inherit the parent agent type; omit agent_type, or spawn without a full-history fork.".to_string(),
));
}
Ok(())
@@ -249,6 +247,9 @@ pub(crate) async fn apply_requested_spawn_agent_model_overrides(
requested_model: Option<&str>,
requested_reasoning_effort: Option<ReasoningEffort>,
) -> Result<(), FunctionCallError> {
let requested_model = requested_model.or(turn.config.agent_default_subagent_model.as_deref());
let requested_reasoning_effort = requested_reasoning_effort
.or_else(|| turn.config.agent_default_subagent_reasoning_effort.clone());
if requested_model.is_none() && requested_reasoning_effort.is_none() {
return Ok(());
}
@@ -339,42 +339,7 @@ async fn spawn_agent_fork_context_rejects_agent_type_override() {
assert_eq!(
err,
FunctionCallError::RespondToModel(
"Full-history forked agents inherit the parent agent type, model, and reasoning effort; omit agent_type, model, and reasoning_effort, or spawn without a full-history fork.".to_string(),
)
);
}
#[tokio::test]
async fn spawn_agent_fork_context_rejects_child_model_overrides() {
let (mut session, turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
session.thread_id = root.thread_id;
let err = SpawnAgentHandler::default()
.handle(invocation(
Arc::new(session),
Arc::new(turn),
"spawn_agent",
function_payload(json!({
"message": "inspect this repo",
"model": "gpt-5-child-override",
"reasoning_effort": "low",
"fork_context": true
})),
))
.await
.err()
.expect("forked spawn should reject child model overrides");
assert_eq!(
err,
FunctionCallError::RespondToModel(
"Full-history forked agents inherit the parent agent type, model, and reasoning effort; omit agent_type, model, and reasoning_effort, or spawn without a full-history fork.".to_string(),
"Full-history forked agents inherit the parent agent type; omit agent_type, or spawn without a full-history fork.".to_string(),
)
);
}
@@ -420,48 +385,7 @@ async fn multi_agent_v2_spawn_fork_turns_all_rejects_agent_type_override() {
assert_eq!(
err,
FunctionCallError::RespondToModel(
"Full-history forked agents inherit the parent agent type, model, and reasoning effort; omit agent_type, model, and reasoning_effort, or spawn without a full-history fork.".to_string(),
)
);
}
#[tokio::test]
async fn multi_agent_v2_spawn_defaults_to_full_fork_and_rejects_child_model_overrides() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
session.thread_id = root.thread_id;
let mut config = (*turn.config).clone();
config
.features
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
set_turn_config(&mut turn, config);
let err = SpawnAgentHandlerV2::default()
.handle(invocation(
Arc::new(session),
Arc::new(turn),
"spawn_agent",
function_payload(json!({
"message": "inspect this repo",
"task_name": "fork_context_v2",
"model": "gpt-5-child-override",
"reasoning_effort": "low"
})),
))
.await
.err()
.expect("default full fork should reject child model overrides");
assert_eq!(
err,
FunctionCallError::RespondToModel(
"Full-history forked agents inherit the parent agent type, model, and reasoning effort; omit agent_type, model, and reasoning_effort, or spawn without a full-history fork.".to_string(),
"Full-history forked agents inherit the parent agent type; omit agent_type, or spawn without a full-history fork.".to_string(),
)
);
}
@@ -61,26 +61,22 @@ async fn handle_spawn_agent(
let child_depth = next_thread_spawn_depth(&session_source);
let mut config =
build_agent_spawn_config(&session.get_base_instructions().await, turn.as_ref())?;
// TODO(jif): Apply `agents.default_subagent_model` as the default model here.
// TODO(jif): Apply `agents.default_subagent_reasoning_effort` as the default effort here.
if let Some(service_tier) = args.service_tier.as_ref() {
config.service_tier = Some(service_tier.clone());
}
if matches!(fork_mode, Some(SpawnAgentForkMode::FullHistory)) {
reject_full_fork_spawn_overrides(
role_name,
args.model.as_deref(),
args.reasoning_effort.clone(),
)?;
} else {
apply_requested_spawn_agent_model_overrides(
&session,
turn.as_ref(),
&mut config,
args.model.as_deref(),
args.reasoning_effort.clone(),
)
.await?;
let is_full_history_fork = matches!(fork_mode, Some(SpawnAgentForkMode::FullHistory));
if is_full_history_fork {
reject_full_fork_agent_type_override(role_name)?;
}
apply_requested_spawn_agent_model_overrides(
&session,
turn.as_ref(),
&mut config,
args.model.as_deref(),
args.reasoning_effort.clone(),
)
.await?;
if !is_full_history_fork {
apply_role_to_config(&mut config, role_name)
.await
.map_err(FunctionCallError::RespondToModel)?;
+25
View File
@@ -1,5 +1,6 @@
use anyhow::Result;
use codex_features::Feature;
use codex_protocol::openai_models::ReasoningEffort;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_function_call;
use core_test_support::responses::ev_response_created;
@@ -7,6 +8,7 @@ use core_test_support::responses::sse;
use core_test_support::responses::sse_response;
use core_test_support::responses::start_mock_server;
use core_test_support::test_codex::test_codex;
use pretty_assertions::assert_eq;
use regex_lite::Regex;
use serde_json::Value;
use serde_json::json;
@@ -292,6 +294,8 @@ async fn spawn_agents_on_csv_runs_and_exports() -> Result<()> {
.features
.enable(Feature::Sqlite)
.expect("test config should allow feature update");
config.agent_default_subagent_model = Some("gpt-5.4".to_string());
config.agent_default_subagent_reasoning_effort = Some(ReasoningEffort::High);
});
let test = builder.build(&server).await?;
@@ -319,6 +323,27 @@ async fn spawn_agents_on_csv_runs_and_exports() -> Result<()> {
assert!(output.contains("result_json"));
assert!(output.contains("item_id"));
assert!(output.contains("\"item_id\""));
let worker_settings = server
.received_requests()
.await
.unwrap_or_default()
.into_iter()
.filter_map(|request| {
let body = serde_json::from_slice::<Value>(&decode_body_bytes(&request)).ok()?;
if has_function_call_output(&body) {
return None;
}
extract_job_and_item(&body)?;
Some((
body["model"].as_str().map(str::to_string),
body["reasoning"]["effort"].as_str().map(str::to_string),
))
})
.collect::<Vec<_>>();
assert_eq!(
worker_settings,
vec![(Some("gpt-5.4".to_string()), Some("high".to_string())); 2]
);
Ok(())
}
@@ -62,6 +62,10 @@ const INHERITED_MODEL: &str = "gpt-5.2";
const INHERITED_REASONING_EFFORT: ReasoningEffort = ReasoningEffort::XHigh;
const REQUESTED_MODEL: &str = "gpt-5.4";
const REQUESTED_REASONING_EFFORT: ReasoningEffort = ReasoningEffort::Low;
const V2_DEFAULT_MODEL: &str = "gpt-5.6-terra";
const V2_DEFAULT_REASONING_EFFORT: ReasoningEffort = ReasoningEffort::High;
const V2_REQUESTED_MODEL: &str = "gpt-5.6-sol";
const V2_REQUESTED_REASONING_EFFORT: ReasoningEffort = ReasoningEffort::Low;
const ROLE_MODEL: &str = "gpt-5.4";
const ROLE_REASONING_EFFORT: ReasoningEffort = ReasoningEffort::High;
const SUBAGENT_START_CONTEXT: &str = "subagent start context reaches child";
@@ -355,6 +359,26 @@ async fn wait_for_requests(
}
}
async fn wait_for_request_with_model(
mock: &core_test_support::responses::ResponseMock,
model: &str,
) -> Result<ResponsesRequest> {
let deadline = Instant::now() + Duration::from_secs(2);
loop {
if let Some(request) = mock
.requests()
.into_iter()
.find(|request| request.body_json()["model"] == model)
{
return Ok(request);
}
if Instant::now() >= deadline {
anyhow::bail!("timed out waiting for request using model {model}");
}
sleep(Duration::from_millis(10)).await;
}
}
async fn setup_turn_one_with_spawned_child(
server: &MockServer,
child_response_delay: Option<Duration>,
@@ -900,9 +924,11 @@ async fn spawned_child_receives_forked_parent_context(
)
.await;
let _child_request_log = mount_sse_once_match(
let child_request_log = mount_sse_once_match(
&server,
|req: &wiremock::Request| body_contains(req, CHILD_PROMPT),
|req: &wiremock::Request| {
body_contains(req, CHILD_PROMPT) && !body_contains(req, SPAWN_CALL_ID)
},
sse(vec![
ev_response_created("resp-child-1"),
ev_assistant_message("msg-child-1", "child done"),
@@ -929,6 +955,10 @@ async fn spawned_child_receives_forked_parent_context(
.features
.enable(Feature::Collab)
.expect("test config should allow feature update");
config.model = Some(INHERITED_MODEL.to_string());
config.model_reasoning_effort = Some(INHERITED_REASONING_EFFORT);
config.agent_default_subagent_model = Some(REQUESTED_MODEL.to_string());
config.agent_default_subagent_reasoning_effort = Some(REQUESTED_REASONING_EFFORT);
});
let test = builder.build(&server).await?;
@@ -938,26 +968,141 @@ async fn spawned_child_receives_forked_parent_context(
test.submit_turn(TURN_1_PROMPT).await?;
let _ = spawn_turn.single_request();
let deadline = Instant::now() + Duration::from_secs(2);
let child_request = loop {
if let Some(request) = server
.received_requests()
.await
.unwrap_or_default()
.into_iter()
.find(|request| {
body_contains(request, CHILD_PROMPT) && !body_contains(request, SPAWN_CALL_ID)
})
{
break request;
}
if Instant::now() >= deadline {
anyhow::bail!("timed out waiting for forked child request");
}
sleep(Duration::from_millis(10)).await;
let child_request = wait_for_request_with_model(&child_request_log, REQUESTED_MODEL).await?;
assert!(child_request.body_contains_text(TURN_0_FORK_PROMPT));
let child_body = child_request.body_json();
assert_eq!(
(
child_body["model"].clone(),
child_body["reasoning"]["effort"].clone(),
),
(
json!(REQUESTED_MODEL),
json!(REQUESTED_REASONING_EFFORT.to_string()),
)
);
Ok(())
}
#[derive(Clone, Copy)]
enum FullHistoryV2ModelSelection {
ConfiguredDefault,
ExplicitOverride,
}
#[test_case(FullHistoryV2ModelSelection::ConfiguredDefault; "configured default with omitted fork_turns")]
#[test_case(FullHistoryV2ModelSelection::ExplicitOverride; "explicit override with fork_turns all")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn spawned_full_history_v2_child_uses_model_precedence_without_dropping_context(
selection: FullHistoryV2ModelSelection,
) -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let seed_turn = mount_sse_once_match(
&server,
|req: &wiremock::Request| body_contains(req, TURN_0_FORK_PROMPT),
sse(vec![
ev_response_created("resp-seed-1"),
ev_assistant_message("msg-seed-1", "seeded"),
ev_completed("resp-seed-1"),
]),
)
.await;
let (spawn_args, expected_model, expected_reasoning_effort) = match selection {
FullHistoryV2ModelSelection::ConfiguredDefault => (
json!({
"message": CHILD_PROMPT,
"task_name": "worker",
}),
V2_DEFAULT_MODEL,
V2_DEFAULT_REASONING_EFFORT,
),
FullHistoryV2ModelSelection::ExplicitOverride => (
json!({
"message": CHILD_PROMPT,
"task_name": "worker",
"fork_turns": "all",
"model": V2_REQUESTED_MODEL,
"reasoning_effort": V2_REQUESTED_REASONING_EFFORT,
}),
V2_REQUESTED_MODEL,
V2_REQUESTED_REASONING_EFFORT,
),
};
assert!(body_contains(&child_request, TURN_0_FORK_PROMPT));
assert!(!body_contains(&child_request, SPAWN_CALL_ID));
let spawn_args = serde_json::to_string(&spawn_args)?;
let spawn_turn = mount_sse_once_match(
&server,
|req: &wiremock::Request| body_contains(req, TURN_1_PROMPT),
sse(vec![
ev_response_created("resp-turn1-1"),
ev_function_call_with_namespace(
SPAWN_CALL_ID,
MULTI_AGENT_V2_NAMESPACE,
"spawn_agent",
&spawn_args,
),
ev_completed("resp-turn1-1"),
]),
)
.await;
let child_request_log = mount_sse_once_match(
&server,
|req: &wiremock::Request| {
body_contains(req, CHILD_PROMPT) && !body_contains(req, SPAWN_CALL_ID)
},
sse(vec![
ev_response_created("resp-child-1"),
ev_assistant_message("msg-child-1", "child done"),
ev_completed("resp-child-1"),
]),
)
.await;
let _turn1_followup = mount_sse_once_match(
&server,
|req: &wiremock::Request| body_contains(req, SPAWN_CALL_ID),
sse(vec![
ev_response_created("resp-turn1-2"),
ev_assistant_message("msg-turn1-2", "parent done"),
ev_completed("resp-turn1-2"),
]),
)
.await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::Collab)
.expect("test config should allow feature update");
config
.features
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
config.model = Some(INHERITED_MODEL.to_string());
config.model_reasoning_effort = Some(INHERITED_REASONING_EFFORT);
config.agent_default_subagent_model = Some(V2_DEFAULT_MODEL.to_string());
config.agent_default_subagent_reasoning_effort = Some(V2_DEFAULT_REASONING_EFFORT);
});
let test = builder.build(&server).await?;
test.submit_turn(TURN_0_FORK_PROMPT).await?;
let _ = seed_turn.single_request();
test.submit_turn(TURN_1_PROMPT).await?;
let _ = spawn_turn.single_request();
let child_request = wait_for_request_with_model(&child_request_log, expected_model).await?;
assert!(child_request.body_contains_text(TURN_0_FORK_PROMPT));
let child_body = child_request.body_json();
assert_eq!(
(
child_body["model"].clone(),
child_body["reasoning"]["effort"].clone(),
),
(
json!(expected_model),
json!(expected_reasoning_effort.to_string()),
)
);
Ok(())
}
@@ -975,7 +1120,12 @@ async fn spawn_agent_requested_model_and_reasoning_override_inherited_settings_w
"model": REQUESTED_MODEL,
"reasoning_effort": REQUESTED_REASONING_EFFORT,
}),
|builder| builder,
|builder| {
builder.with_config(|config| {
config.agent_default_subagent_model = Some(INHERITED_MODEL.to_string());
config.agent_default_subagent_reasoning_effort = Some(ReasoningEffort::High);
})
},
)
.await?;
@@ -988,6 +1138,30 @@ async fn spawn_agent_requested_model_and_reasoning_override_inherited_settings_w
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn spawn_agent_uses_configured_subagent_defaults() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let child_snapshot =
spawn_child_and_capture_snapshot(&server, json!({ "message": CHILD_PROMPT }), |builder| {
builder.with_config(|config| {
config.agent_default_subagent_model = Some(REQUESTED_MODEL.to_string());
config.agent_default_subagent_reasoning_effort = Some(REQUESTED_REASONING_EFFORT);
})
})
.await?;
assert_eq!(
(child_snapshot.model, child_snapshot.reasoning_effort),
(
REQUESTED_MODEL.to_string(),
Some(REQUESTED_REASONING_EFFORT)
)
);
Ok(())
}
#[test_case(true, false; "unsupported child")]
#[test_case(false, true; "supported child")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+2 -2
View File
@@ -71,12 +71,12 @@ fn render_agents_config_lines(config: &Config) -> Vec<Line<'static>> {
)
.into(),
format!(
" - default_subagent_model = {} (not yet wired)",
" - default_subagent_model = {}",
format_optional(config.agent_default_subagent_model.as_deref())
)
.into(),
format!(
" - default_subagent_reasoning_effort = {} (not yet wired)",
" - default_subagent_reasoning_effort = {}",
format_optional(config.agent_default_subagent_reasoning_effort.as_ref())
)
.into(),
@@ -8,7 +8,7 @@ expression: render_to_text(&render_agents_config_lines(&config))
- enabled = false
- max_concurrent_threads_per_session = 7
- max_depth = -2 (V1 only; ignored by V2)
- default_subagent_model = gpt-5.6-terra (not yet wired)
- default_subagent_reasoning_effort = high (not yet wired)
- default_subagent_model = gpt-5.6-terra
- default_subagent_reasoning_effort = high
- job_max_runtime_seconds = 900
- interrupt_message = false