Skip to content

Commit 56a8470

Browse files
felixxia-oaicopyberry
authored andcommitted
Record reasoning effort changes in conversation history behind a flag (#43110)
## What changed Add the disabled-by-default `reasoning_effort_override` feature for OpenAI models with `use_responses_lite` enabled. Append a trusted `configuration_update` after accepted input when no effort is established in surviving history or the resolved effort changes. Preserve the existing history prefix and continue sending the request-level reasoning effort. Share effort normalization through `ModelInfo::resolve_reasoning_effort` so requests and history updates resolve `ultra` consistently and translate `persistent` to `disabled`. Exclude other custom effort values from history updates, and compare only against harness-authored configuration items. ## Testing Add integration coverage for effort transitions, deduplication, history prefix and cache-key preservation, alias normalization, and feature/provider/model gating. Add unit coverage for model-specific `ultra` resolution, fallbacks, and `persistent` translation. GitOrigin-RevId: c3fe7050058076454d8ea20054a618bca9d48c7c
1 parent e67a8ae commit 56a8470

11 files changed

Lines changed: 523 additions & 39 deletions

File tree

‎codex-rs/core/config.schema.json‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,9 @@
879879
"realtime_conversation": {
880880
"type": "boolean"
881881
},
882+
"reasoning_effort_override": {
883+
"type": "boolean"
884+
},
882885
"recommended_plugins": {
883886
"type": "boolean"
884887
},
@@ -6271,6 +6274,9 @@
62716274
"realtime_conversation": {
62726275
"type": "boolean"
62736276
},
6277+
"reasoning_effort_override": {
6278+
"type": "boolean"
6279+
},
62746280
"recommended_plugins": {
62756281
"type": "boolean"
62766282
},

‎codex-rs/core/src/client.rs‎

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -183,42 +183,6 @@ pub(crate) struct CompactConversationRequestSettings {
183183
pub(crate) service_tier: Option<String>,
184184
}
185185

186-
fn reasoning_effort_for_request(
187-
model_info: &ModelInfo,
188-
effort: ReasoningEffortConfig,
189-
) -> ReasoningEffortConfig {
190-
match effort {
191-
ReasoningEffortConfig::Ultra => model_info
192-
.multi_agent_reasoning_effort
193-
.as_ref()
194-
.filter(|effort| {
195-
*effort != &ReasoningEffortConfig::Ultra
196-
&& model_info
197-
.supported_reasoning_levels
198-
.iter()
199-
.any(|preset| &preset.effort == *effort)
200-
})
201-
.cloned()
202-
.or_else(|| {
203-
let supported_reasoning_levels = &model_info.supported_reasoning_levels;
204-
supported_reasoning_levels
205-
.iter()
206-
.find(|preset| preset.effort == ReasoningEffortConfig::Max)
207-
.or_else(|| {
208-
supported_reasoning_levels
209-
.iter()
210-
.rev()
211-
.find(|preset| preset.effort != ReasoningEffortConfig::Ultra)
212-
})
213-
.map(|preset| preset.effort.clone())
214-
})
215-
.unwrap_or(ReasoningEffortConfig::Medium),
216-
// Keep "persistent" in local settings; the Responses API calls it "disabled".
217-
ReasoningEffortConfig::Persistent => ReasoningEffortConfig::Custom("disabled".to_string()),
218-
effort => effort,
219-
}
220-
}
221-
222186
fn session_telemetry_for_request(
223187
session_telemetry: &SessionTelemetry,
224188
request: &ResponsesApiRequest,
@@ -806,7 +770,7 @@ impl ModelClient {
806770
model: model_info.slug.clone(),
807771
raw_memories,
808772
reasoning: effort
809-
.map(|effort| reasoning_effort_for_request(model_info, effort))
773+
.map(|effort| model_info.resolve_reasoning_effort(effort))
810774
.map(|effort| Reasoning {
811775
effort: Some(effort),
812776
summary: None,
@@ -912,7 +876,7 @@ impl ModelClient {
912876
Reasoning {
913877
effort: effort
914878
.or_else(|| model_info.default_reasoning_level.clone())
915-
.map(|effort| reasoning_effort_for_request(model_info, effort)),
879+
.map(|effort| model_info.resolve_reasoning_effort(effort)),
916880
summary: (model_info.supports_reasoning_summary_parameter
917881
&& summary != ReasoningSummaryConfig::None)
918882
.then_some(summary),

‎codex-rs/core/src/session/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ mod mcp_refresh;
230230
mod mcp_runtime;
231231
pub(crate) mod multi_agents;
232232
mod realtime_history;
233+
mod reasoning_effort;
233234
mod retained_context;
234235
mod review;
235236
mod rollout_budget;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//! Gated reasoning-effort updates appended after accepted input.
2+
//!
3+
//! Only trusted harness items establish an effort. Updates append to surviving
4+
//! history without replacing the request-level reasoning effort.
5+
6+
use super::session::Session;
7+
use super::step_context::StepContext;
8+
use super::step_settings::ResolvedStepSettings;
9+
use codex_features::Feature;
10+
use codex_history::CodexHarnessMetadata;
11+
use codex_history::ResponseItemEnvelope;
12+
use codex_protocol::models::ConfigurationReasoning;
13+
use codex_protocol::models::ResponseItem;
14+
use codex_protocol::openai_models::ReasoningEffort;
15+
16+
impl Session {
17+
/// Establishes the selected effort in surviving history, independent of replayed settings.
18+
pub(crate) async fn record_reasoning_effort_override(&self, step_context: &StepContext) {
19+
let settings = &step_context.settings;
20+
let Some(effort) = self.effort_for_configuration_update(settings).await else {
21+
return;
22+
};
23+
let should_skip = {
24+
let state = self.state.lock().await;
25+
let established_effort =
26+
state
27+
.history
28+
.annotated_items()
29+
.iter()
30+
.rev()
31+
.find_map(|envelope| {
32+
if !envelope
33+
.metadata
34+
.as_ref()
35+
.is_some_and(|metadata| metadata.harness_authored_configuration)
36+
{
37+
return None;
38+
}
39+
match &envelope.item {
40+
ResponseItem::ConfigurationUpdate { reasoning } => {
41+
Some(&reasoning.effort)
42+
}
43+
_ => None,
44+
}
45+
});
46+
established_effort == Some(&effort)
47+
};
48+
if should_skip {
49+
return;
50+
}
51+
52+
self.record_annotated_conversation_items(
53+
step_context.turn.as_ref(),
54+
vec![ResponseItemEnvelope {
55+
item: ResponseItem::ConfigurationUpdate {
56+
reasoning: ConfigurationReasoning { effort },
57+
},
58+
metadata: Some(CodexHarnessMetadata {
59+
harness_authored_configuration: true,
60+
..Default::default()
61+
}),
62+
}],
63+
)
64+
.await;
65+
}
66+
67+
async fn effort_for_configuration_update(
68+
&self,
69+
settings: &ResolvedStepSettings,
70+
) -> Option<ReasoningEffort> {
71+
if !self.enabled(Feature::ReasoningEffortOverride)
72+
|| !settings.model_info.use_responses_lite
73+
|| !self.provider().await.is_openai()
74+
{
75+
return None;
76+
}
77+
let effort = settings
78+
.model_info
79+
.resolve_reasoning_effort(settings.effective_reasoning_effort()?);
80+
// Persistent normalizes to "disabled". Keep unknown custom values out of
81+
// durable updates so injected items stay bounded to known backend modes.
82+
if matches!(&effort, ReasoningEffort::Custom(value) if value != "disabled") {
83+
return None;
84+
}
85+
Some(effort)
86+
}
87+
}

‎codex-rs/core/src/session/turn.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,10 @@ pub(crate) async fn run_turn(
413413
.record_step_world_state_if_changed(&world_state, step_context.as_ref())
414414
.await?;
415415

416+
// Keep the override after accepted input so ordinary turn rollback removes it too.
417+
sess.record_reasoning_effort_override(step_context.as_ref())
418+
.await;
419+
416420
// Construct the input that we will send to the model.
417421
let sampling_request_input: Vec<ResponseItem> = async {
418422
sess.clone_history()

‎codex-rs/core/tests/suite/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ mod quota_exceeded;
128128
mod realtime_conversation;
129129
mod realtime_initial_items;
130130
mod realtime_sideband_endpoint;
131+
mod reasoning_effort_override;
131132
mod remote_env;
132133
mod remote_models;
133134
mod request_compression;

0 commit comments

Comments
 (0)