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
+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,