Preserve reasoning effort through compaction and reset it on success (#43796)

## Why

With reasoning effort overrides enabled, compaction used the selected effort even when sampling requests still used an earlier pinned effort. Successful compaction also left that old pin active in the new context window.

## What changed

- Use the pinned request effort for local and remote compaction when it matches the model.
- Resolve fallback models' effort without mutating the live pin, preserving it if compaction fails.
- Reset the pin after successful compaction so the next sampling request establishes the selected effort as its baseline without a redundant `configuration_update`.

## Testing

Add regression coverage for remote compaction success and failure, fallback model effort selection, pin preservation during fallback lookup, and compaction after resuming with overrides disabled.

GitOrigin-RevId: 35e666b0c8026553500715ddbe7bef02cd759f4a
This commit is contained in:
felixxia-oai
2026-09-08 10:46:07 +00:00
committed by copyberry
parent 31ccaf40c2
commit 35d9e4bc4d
11 changed files with 598 additions and 18 deletions
+6 -1
View File
@@ -17,6 +17,7 @@ use crate::responses_metadata::CodexResponsesRequestKind;
use crate::responses_metadata::CompactionTurnMetadata;
#[cfg(test)]
use crate::session::PreviousTurnSettings;
use crate::session::RequestEffortUsage;
use crate::session::session::Session;
use crate::session::step_context::StepContext;
use crate::session::turn::get_last_assistant_message_from_turn;
@@ -771,7 +772,11 @@ async fn drain_to_completed(
prompt,
turn_context.model_info(),
&turn_context.session_telemetry,
turn_context.reasoning_effort().cloned(),
sess.reasoning_effort_for_request(
&turn_context.initial_settings,
RequestEffortUsage::Compaction,
)
.await,
turn_context.reasoning_summary(),
turn_context.config.service_tier.clone(),
responses_metadata,
+7 -1
View File
@@ -7,6 +7,7 @@ use crate::client::CompactConversationRequestSettings;
use crate::compact::CompactionAnalyticsDetails;
use crate::responses_metadata::CodexResponsesRequestKind;
use crate::responses_metadata::CompactionTurnMetadata;
use crate::session::RequestEffortUsage;
use crate::session::session::Session;
use crate::session::step_context::StepContext;
use codex_protocol::auth::AuthMode;
@@ -83,7 +84,12 @@ pub(super) async fn run_remote_compact_attempt(
turn_context.model_info(),
turn_state,
CompactConversationRequestSettings {
effort: turn_context.reasoning_effort().cloned(),
effort: sess
.reasoning_effort_for_request(
&turn_context.initial_settings,
RequestEffortUsage::Compaction,
)
.await,
summary: turn_context.reasoning_summary(),
service_tier: if sess.services.auth_manager.auth_mode() == Some(AuthMode::ApiKey) {
None
+6 -1
View File
@@ -26,6 +26,7 @@ use crate::responses_metadata::CompactionTurnMetadata;
use crate::responses_retry::ResponsesStreamRequest;
use crate::responses_retry::ResponsesStreamRetryState;
use crate::responses_retry::handle_retryable_response_stream_error;
use crate::session::RequestEffortUsage;
use crate::session::session::Session;
use crate::session::step_context::StepContext;
use crate::session::turn_context::TurnContext;
@@ -388,7 +389,11 @@ async fn run_remote_compaction_request_v2(
prompt,
turn_context.model_info(),
&turn_context.session_telemetry,
turn_context.reasoning_effort().cloned(),
sess.reasoning_effort_for_request(
&turn_context.initial_settings,
RequestEffortUsage::Compaction,
)
.await,
turn_context.reasoning_summary(),
step_context.settings.service_tier.clone(),
responses_metadata,
+3 -1
View File
@@ -224,6 +224,8 @@ mod environment;
pub(crate) mod extension_metrics;
mod handlers;
mod inject;
mod reasoning_effort;
pub(crate) use reasoning_effort::RequestEffortUsage;
mod input_queue;
mod mcp;
mod mcp_prewarm;
@@ -231,7 +233,6 @@ mod mcp_refresh;
mod mcp_runtime;
pub(crate) mod multi_agents;
mod realtime_history;
mod reasoning_effort;
mod retained_context;
mod review;
mod rollout_budget;
@@ -3831,6 +3832,7 @@ impl Session {
);
compacted_item.guardian_history = state.history.guardian_history_checkpoint();
compacted_item.retained_context = Some(state.history.retained_context().clone());
state.reasoning_effort_pin = ReasoningEffortPin::Compacted;
if let Some(world_state) = world_state_baseline {
let snapshot = world_state.snapshot();
world_state_item = Some(WorldStateItem::full(snapshot.clone().into_object()));
+42 -9
View File
@@ -1,7 +1,7 @@
//! Cache-preserving effort updates and the original sampling request effort.
//! Cache-preserving effort updates and the request-effort baseline for a context window.
//!
//! Only trusted harness items establish overrides. Replay invalidates the runtime
//! pin so the next send re-establishes the currently selected effort.
//! Only trusted harness items establish overrides. Replay invalidates the runtime pin;
//! successful compaction retires the overrides and allows a fresh request baseline.
use super::session::Session;
use super::step_context::StepContext;
@@ -14,6 +14,13 @@ use codex_protocol::models::ConfigurationReasoning;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ReasoningEffort;
/// Sampling can establish a pin; compaction must not change live state before it succeeds.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RequestEffortUsage {
Sampling,
Compaction,
}
impl Session {
/// Establishes the selected effort in surviving history, independent of replayed settings.
pub(crate) async fn record_reasoning_effort_override(&self, step_context: &StepContext) {
@@ -22,7 +29,14 @@ impl Session {
return;
};
let should_skip = {
let state = self.state.lock().await;
let mut state = self.state.lock().await;
if matches!(state.reasoning_effort_pin, ReasoningEffortPin::Compacted) {
// Replay leaves the pin Unset. Only successful compaction allows the
// request baseline to establish the selection without another update.
state
.reasoning_effort_pin
.pin(&settings.model_info.slug, effort.clone());
}
let established_effort =
state
.history
@@ -68,26 +82,41 @@ impl Session {
.await;
}
/// Reuses the original request effort while updates carry the selected effort.
/// Sampling and compaction share the original request effort for this context window.
pub(crate) async fn reasoning_effort_for_request(
&self,
settings: &ResolvedStepSettings,
usage: RequestEffortUsage,
) -> Option<ReasoningEffort> {
let selected_effort = settings.reasoning_effort().cloned();
if !self.enabled(Feature::ReasoningEffortOverride) {
return selected_effort;
}
if usage == RequestEffortUsage::Compaction
&& let Some(pinned) = self
.state
.lock()
.await
.reasoning_effort_pin
.get(&settings.model_info.slug)
{
return Some(pinned);
}
let effort = self.effort_for_configuration_update(settings).await;
let mut state = self.state.lock().await;
let Some(effort) = effort else {
state.reasoning_effort_pin = ReasoningEffortPin::Unset;
if usage == RequestEffortUsage::Sampling {
state.reasoning_effort_pin = ReasoningEffortPin::Unset;
}
return selected_effort;
};
Some(
state
Some(match usage {
RequestEffortUsage::Sampling => state
.reasoning_effort_pin
.pin(&settings.model_info.slug, effort),
)
// Failed compaction and fallback-model lookups must not mutate the live pin.
RequestEffortUsage::Compaction => effort,
})
}
async fn effort_for_configuration_update(
@@ -111,3 +140,7 @@ impl Session {
Some(effort)
}
}
#[cfg(test)]
#[path = "reasoning_effort_tests.rs"]
mod tests;
@@ -0,0 +1,45 @@
//! Request-effort lookup must leave live state untouched during compaction.
use super::RequestEffortUsage;
use crate::session::tests::make_session_and_context;
use codex_features::Feature;
use codex_protocol::openai_models::ReasoningEffort;
use pretty_assertions::assert_eq;
use std::sync::Arc;
#[tokio::test]
async fn compaction_effort_lookup_preserves_pin_for_fallback_models() {
let (mut session, turn_context) = make_session_and_context().await;
session
.features
.enable(Feature::ReasoningEffortOverride)
.unwrap();
session
.state
.lock()
.await
.reasoning_effort_pin
.pin("original", ReasoningEffort::Low);
let mut settings = (*turn_context.initial_settings).clone();
let effort = ReasoningEffort::Medium;
let model = Arc::make_mut(&mut settings.model_info);
model.slug = "fallback".to_string();
model.use_responses_lite = true;
model.default_reasoning_level = Some(effort.clone());
assert_eq!(
session
.reasoning_effort_for_request(&settings, RequestEffortUsage::Compaction)
.await,
Some(effort)
);
assert_eq!(
session
.state
.lock()
.await
.reasoning_effort_pin
.get("original"),
Some(ReasoningEffort::Low)
);
}
+5 -2
View File
@@ -2313,8 +2313,11 @@ async fn try_run_sampling_request(
prompt,
&step_context.settings.model_info,
&step_context.session_telemetry,
sess.reasoning_effort_for_request(&step_context.settings)
.await,
sess.reasoning_effort_for_request(
&step_context.settings,
super::RequestEffortUsage::Sampling,
)
.await,
step_context.settings.reasoning_summary,
step_context.settings.service_tier.clone(),
responses_metadata,
+2 -1
View File
@@ -15,6 +15,7 @@ use crate::client::ModelClientSession;
use crate::guardian::routes_approval_to_guardian;
use crate::responses_metadata::CodexResponsesRequestKind;
use crate::session::INITIAL_SUBMIT_ID;
use crate::session::RequestEffortUsage;
use crate::session::session::Session;
use crate::session::turn::build_prompt;
use codex_features::Feature;
@@ -325,7 +326,7 @@ async fn schedule_startup_prewarm_inner(
&step_context.settings.model_info,
&step_context.session_telemetry,
session
.reasoning_effort_for_request(&step_context.settings)
.reasoning_effort_for_request(&step_context.settings, RequestEffortUsage::Sampling)
.await,
step_context.settings.reasoning_summary,
step_context.settings.service_tier.clone(),
+4 -2
View File
@@ -30,9 +30,11 @@ use codex_protocol::protocol::TurnContextItem;
use codex_utils_output_truncation::TruncationPolicy;
use tokio_util::task::AbortOnDropHandle;
/// Runtime request effort; startup and replay must re-establish the selected effort.
/// Runtime request effort. A successful compaction can establish a fresh baseline, while
/// startup and replay must explicitly re-establish the selected effort in surviving history.
pub(crate) enum ReasoningEffortPin {
Unset,
Compacted,
Active {
model: String,
effort: ReasoningEffort,
@@ -46,7 +48,7 @@ impl ReasoningEffortPin {
model: pinned_model,
effort,
} if pinned_model == model => Some(effort.clone()),
Self::Unset | Self::Active { .. } => None,
Self::Unset | Self::Compacted | Self::Active { .. } => None,
}
}
+123
View File
@@ -987,6 +987,129 @@ async fn manual_compact_uses_custom_prompt() {
}
}
#[test_case::test_case(false; "success")]
#[test_case::test_case(true; "failure")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn reasoning_effort_override_remote_v2_compaction_resets_pinned_effort(
fail_compaction: bool,
) -> Result<()> {
use codex_protocol::openai_models::ReasoningEffort;
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let request_log = mount_sse_sequence(
&server,
vec![
sse(vec![
ev_assistant_message("m1", "first reply"),
ev_completed("r1"),
]),
sse(vec![
ev_assistant_message("m2", "second reply"),
ev_completed("r2"),
]),
if fail_compaction {
sse_failed("r3", "server_error", "compaction failed")
} else {
remote_v2_compaction_response()
},
sse(vec![
ev_assistant_message("m4", "third reply"),
ev_completed("r4"),
]),
],
)
.await;
let test = test_codex()
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
.with_model_info_override("gpt-5.4", |model| {
model.use_responses_lite = true;
})
.with_config(move |config| {
config
.features
.enable(Feature::RemoteCompactionV2)
.expect("enable remote compaction v2");
config.model_provider.stream_max_retries = Some(0);
config.model_reasoning_effort = Some(ReasoningEffort::Medium);
config
.features
.enable(Feature::ReasoningEffortOverride)
.expect("test config should allow feature update");
set_test_compact_prompt(config);
})
.build_with_auto_env(&server)
.await?;
test.submit_text_turn("first message").await?;
core_test_support::submit_thread_settings(
&test.codex,
ThreadSettingsOverrides {
effort: Some(Some(ReasoningEffort::High)),
..Default::default()
},
)
.await?;
test.submit_text_turn("second message").await?;
test.codex.submit(Op::Compact).await?;
if fail_compaction {
wait_for_event(&test.codex, |event| matches!(event, EventMsg::Error(_))).await;
}
wait_for_event(&test.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
})
.await;
test.submit_text_turn("after compaction").await?;
let requests = request_log.requests();
assert_eq!(requests.len(), 4);
let first_body = requests[0].body_json();
let second_body = requests[1].body_json();
let post_compaction_body = requests[3].body_json();
assert_eq!(first_body["reasoning"]["effort"], "medium");
assert_eq!(second_body["reasoning"]["effort"], "medium");
assert_eq!(requests[2].body_json()["reasoning"]["effort"], "medium");
assert_eq!(
post_compaction_body["reasoning"]["effort"],
if fail_compaction { "medium" } else { "high" }
);
let updates = requests
.iter()
.map(|request| {
request
.input()
.into_iter()
.filter(|item| item["type"] == "configuration_update")
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let medium = json!({
"type": "configuration_update",
"reasoning": {"effort": "medium"},
});
let high = json!({
"type": "configuration_update",
"reasoning": {"effort": "high"},
});
assert_eq!(
updates,
[
vec![medium.clone()],
vec![medium.clone(), high.clone()],
vec![medium.clone(), high.clone()],
if fail_compaction {
vec![medium, high]
} else {
vec![]
},
]
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn manual_compact_records_durable_and_local_token_usage() {
skip_if_no_network!();
@@ -6,6 +6,7 @@ use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::ThreadIdleInput;
use codex_extension_api::ThreadLifecycleContributor;
use codex_features::Feature;
use codex_login::CodexAuth;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::openai_models::ReasoningEffortPreset;
use codex_protocol::protocol::EventMsg;
@@ -25,6 +26,7 @@ use std::time::Duration;
use test_case::test_case;
use tokio::sync::Notify;
use tokio::time::timeout;
use wiremock::ResponseTemplate;
fn override_builder() -> TestCodexBuilder {
test_codex()
@@ -495,6 +497,101 @@ async fn reasoning_effort_override_unavailable_uses_request_effort(
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn reasoning_effort_override_disabled_on_resume_retires_update_at_compaction()
-> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
let mut mocks = Vec::new();
for (id, reply) in [
("initial", "initial reply"),
("resumed", "resumed reply"),
("after", "after compaction reply"),
] {
mocks.push(
responses::mount_sse_once(
&server,
responses::sse(vec![
responses::ev_assistant_message(id, reply),
responses::ev_completed(id),
]),
)
.await,
);
}
let compact = responses::mount_compact_json_once(
&server,
serde_json::json!({
"output": [{"type": "compaction", "encrypted_content": "compacted-history"}]
}),
)
.await;
let initial = override_builder().build_with_auto_env(&server).await?;
initial.submit_text_turn("before resume").await?;
let chatgpt_base_url = format!("{}/backend-api", server.uri());
let resumed = override_builder()
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
.with_config(move |config| {
config
.features
.disable(Feature::ReasoningEffortOverride)
.expect("disable overrides");
config
.features
.disable(Feature::RemoteCompactionV2)
.expect("disable remote compaction v2");
config.model_reasoning_effort = Some(ReasoningEffort::High);
config.chatgpt_base_url = chatgpt_base_url;
})
.restart(&server, &initial)
.await?;
resumed.submit_text_turn("after resume").await?;
resumed.codex.submit(Op::Compact).await?;
wait_for_event(&resumed.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
})
.await;
resumed.submit_text_turn("after compaction").await?;
let requests = [
mocks[0].single_request(),
mocks[1].single_request(),
compact.single_request(),
mocks[2].single_request(),
];
assert_eq!(
requests
.iter()
.map(|request| (
request.body_json()["reasoning"]["effort"].clone(),
effort_updates(request),
))
.collect::<Vec<_>>(),
[
(
Value::from("medium"),
vec![effort_update(ReasoningEffort::Medium)]
),
(
Value::from("high"),
vec![effort_update(ReasoningEffort::Medium)]
),
(
Value::from("high"),
vec![effort_update(ReasoningEffort::Medium)]
),
(Value::from("high"), Vec::new()),
],
);
assert!(
requests[3]
.input()
.iter()
.any(|item| item["type"] == "compaction")
);
Ok(())
}
#[test_case(1, ReasoningEffort::High; "later turn keep changed selection")]
#[test_case(1, ReasoningEffort::Medium; "later turn match restored settings")]
#[test_case(2, ReasoningEffort::High; "through first turn")]
@@ -734,3 +831,261 @@ async fn reasoning_effort_override_resume_refreshes_selected_effort(
);
Ok(())
}
#[test_case(false; "success")]
#[test_case(true; "failure")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn reasoning_effort_override_remote_v1_compaction_uses_pin(
fail_compaction: bool,
) -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
let first = responses::mount_sse_once(
&server,
responses::sse(vec![responses::ev_completed("first")]),
)
.await;
let after = responses::mount_sse_once(
&server,
responses::sse(vec![responses::ev_completed("after")]),
)
.await;
let unchanged = responses::mount_sse_once(
&server,
responses::sse(vec![responses::ev_completed("unchanged")]),
)
.await;
let changed = responses::mount_sse_once(
&server,
responses::sse(vec![responses::ev_completed("changed")]),
)
.await;
let compact = responses::mount_compact_response_once(
&server,
if fail_compaction {
ResponseTemplate::new(400).set_body_json(serde_json::json!({
"error": {"code": "invalid_request_error", "message": "compaction failed"}
}))
} else {
ResponseTemplate::new(200).set_body_json(serde_json::json!({
"output": [{"type": "compaction", "encrypted_content": "compacted-history"}]
}))
},
)
.await;
let chatgpt_base_url = format!("{}/backend-api", server.uri());
let test = override_builder()
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
.with_config(move |config| {
config.chatgpt_base_url = chatgpt_base_url;
config
.features
.disable(Feature::RemoteCompactionV2)
.expect("disable remote compaction v2");
})
.build_with_auto_env(&server)
.await?;
test.submit_text_turn("first").await?;
submit_thread_settings(
&test.codex,
ThreadSettingsOverrides {
effort: Some(Some(ReasoningEffort::High)),
..Default::default()
},
)
.await?;
test.codex.submit(Op::Compact).await?;
if fail_compaction {
wait_for_event(&test.codex, |event| matches!(event, EventMsg::Error(_))).await;
}
wait_for_event(&test.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
})
.await;
test.submit_text_turn("after compaction").await?;
test.submit_text_turn("unchanged effort").await?;
submit_thread_settings(
&test.codex,
ThreadSettingsOverrides {
effort: Some(Some(ReasoningEffort::Low)),
..Default::default()
},
)
.await?;
test.submit_text_turn("changed effort").await?;
let medium = effort_update(ReasoningEffort::Medium);
let after_updates = if fail_compaction {
vec![medium.clone(), effort_update(ReasoningEffort::High)]
} else {
Vec::new()
};
let mut changed_updates = after_updates.clone();
changed_updates.push(effort_update(ReasoningEffort::Low));
let after_effort = Value::from(if fail_compaction { "medium" } else { "high" });
assert_eq!(
[
first.single_request(),
compact.single_request(),
after.single_request(),
unchanged.single_request(),
changed.single_request(),
]
.map(|request| (
request.body_json()["reasoning"]["effort"].clone(),
effort_updates(&request),
)),
[
(Value::from("medium"), vec![medium.clone()]),
(Value::from("medium"), vec![medium]),
(after_effort.clone(), after_updates.clone()),
(after_effort.clone(), after_updates),
(after_effort, changed_updates),
],
);
Ok(())
}
#[test_case(false; "remote v1")]
#[test_case(true; "remote v2")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn reasoning_effort_override_compaction_fallback_uses_each_models_effort(
remote_v2: bool,
) -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
let first = responses::mount_sse_once(
&server,
responses::sse(vec![responses::ev_completed("first")]),
)
.await;
let second = responses::mount_sse_once(
&server,
responses::sse(vec![responses::ev_completed("second")]),
)
.await;
let compaction = serde_json::json!({
"type": "compaction", "encrypted_content": "fallback-summary"
});
let failure = ResponseTemplate::new(/*s*/ 400).set_body_json(serde_json::json!({
"error": {"message": "previous model cannot compact this history"}
}));
let compactions = if remote_v2 {
responses::mount_response_sequence(
&server,
vec![
failure,
responses::sse_response(responses::sse(vec![
serde_json::json!({"type": "response.output_item.done", "item": compaction}),
responses::ev_completed("fallback"),
])),
],
)
.await
} else {
responses::mount_compact_response_sequence(
&server,
vec![
failure,
ResponseTemplate::new(/*s*/ 200).set_body_json(serde_json::json!({
"output": [compaction]
})),
],
)
.await
};
let after = responses::mount_sse_once(
&server,
responses::sse(vec![responses::ev_completed("after")]),
)
.await;
let chatgpt_base_url = format!("{}/backend-api", server.uri());
let test = override_builder()
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
.with_model_info_override("gpt-5.4", |model| {
model.comp_hash = Some("original".to_string());
})
.with_model_info_override("gpt-5.2", |model| {
model.comp_hash = Some("fallback".to_string());
model.use_responses_lite = true;
})
.with_model("gpt-5.4")
.with_config(move |config| {
config.chatgpt_base_url = chatgpt_base_url;
config.model_provider.stream_max_retries = Some(0);
if remote_v2 {
config
.features
.enable(Feature::RemoteCompactionV2)
.expect("enable remote compaction v2");
} else {
config
.features
.disable(Feature::RemoteCompactionV2)
.expect("disable remote compaction v2");
}
})
.build_with_auto_env(&server)
.await?;
test.submit_text_turn("first").await?;
submit_thread_settings(
&test.codex,
ThreadSettingsOverrides {
effort: Some(Some(ReasoningEffort::High)),
..Default::default()
},
)
.await?;
test.submit_text_turn("second").await?;
submit_thread_settings(
&test.codex,
ThreadSettingsOverrides {
model: Some("gpt-5.2".to_string()),
..Default::default()
},
)
.await?;
test.submit_text_turn("switch model").await?;
let compaction_requests = compactions.requests();
assert_eq!(compaction_requests.len(), 2);
let requests = [
first.single_request(),
second.single_request(),
compaction_requests[0].clone(),
compaction_requests[1].clone(),
after.single_request(),
];
assert_eq!(
requests.each_ref().map(|request| {
let body = request.body_json();
serde_json::json!([body["model"], body["reasoning"]["effort"]])
}),
[
serde_json::json!(["gpt-5.4", "medium"]),
serde_json::json!(["gpt-5.4", "medium"]),
serde_json::json!(["gpt-5.4", "medium"]),
serde_json::json!(["gpt-5.2", "high"]),
serde_json::json!(["gpt-5.2", "high"]),
]
);
assert_eq!(
requests.each_ref().map(effort_updates),
[
vec![effort_update(ReasoningEffort::Medium)],
vec![
effort_update(ReasoningEffort::Medium),
effort_update(ReasoningEffort::High)
],
vec![
effort_update(ReasoningEffort::Medium),
effort_update(ReasoningEffort::High)
],
vec![
effort_update(ReasoningEffort::Medium),
effort_update(ReasoningEffort::High)
],
vec![],
]
);
Ok(())
}