From b5aeb30d59b4ca228b04b29f4f2bca29f2bbb9c5 Mon Sep 17 00:00:00 2001 From: Charles Cunningham Date: Mon, 16 Feb 2026 11:03:52 -0800 Subject: [PATCH] compact: strip incoming model-switch before compaction --- codex-rs/core/src/compact.rs | 110 ++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 3 deletions(-) diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index bc7d2fc7e5..9806d34d1e 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -88,6 +88,17 @@ pub(crate) fn extract_trailing_model_switch_update_for_compaction_request( Some(model_switch_item) } +fn extract_latest_model_switch_update_from_items( + items: &mut Vec, +) -> Option { + let model_switch_index = items + .iter() + .enumerate() + .rev() + .find_map(|(i, item)| Session::is_model_switch_developer_message(item).then_some(i))?; + Some(items.remove(model_switch_index)) +} + pub(crate) async fn run_inline_auto_compact_task( sess: Arc, turn_context: Arc, @@ -152,10 +163,14 @@ async fn run_compact_task_inner( let initial_input_for_turn: ResponseInputItem = ResponseInputItem::from(input); let mut history = sess.clone_history().await; + let mut incoming_items = incoming_items; // Keep compaction prompts in-distribution: if a model-switch update was injected at the - // tail of history (between turns), exclude it from the compaction request payload. - let stripped_model_switch_item = - extract_trailing_model_switch_update_for_compaction_request(&mut history); + // tail of incoming turn items (pre-turn path) or between turns in history, exclude it from + // the compaction request payload. + let stripped_model_switch_item = incoming_items + .as_mut() + .and_then(extract_latest_model_switch_update_from_items) + .or_else(|| extract_trailing_model_switch_update_for_compaction_request(&mut history)); if let Some(incoming_items) = incoming_items.as_ref() { history.record_items(incoming_items.iter(), turn_context.truncation_policy); } @@ -710,6 +725,95 @@ mod tests { ); } + #[test] + fn extract_model_switch_update_for_compaction_request_prefers_incoming_items() { + let mut history = ContextManager::new(); + history.replace(vec![ + ResponseItem::Message { + id: None, + role: "user".to_string(), + content: vec![ContentItem::InputText { + text: "USER_MESSAGE".to_string(), + }], + end_turn: None, + phase: None, + }, + ResponseItem::Message { + id: None, + role: "assistant".to_string(), + content: vec![ContentItem::OutputText { + text: "ASSISTANT_REPLY".to_string(), + }], + end_turn: None, + phase: None, + }, + ResponseItem::Message { + id: None, + role: "developer".to_string(), + content: vec![ContentItem::InputText { + text: "\nHISTORY_MODEL_INSTRUCTIONS".to_string(), + }], + end_turn: None, + phase: None, + }, + ]); + let mut incoming_items = vec![ + ResponseItem::Message { + id: None, + role: "developer".to_string(), + content: vec![ContentItem::InputText { + text: "\nINCOMING_MODEL_INSTRUCTIONS".to_string(), + }], + end_turn: None, + phase: None, + }, + ResponseItem::Message { + id: None, + role: "user".to_string(), + content: vec![ContentItem::InputText { + text: "INCOMING_USER_MESSAGE".to_string(), + }], + end_turn: None, + phase: None, + }, + ]; + + let model_switch_item = Some(&mut incoming_items) + .and_then(extract_latest_model_switch_update_from_items) + .or_else(|| extract_trailing_model_switch_update_for_compaction_request(&mut history)); + + assert_eq!( + model_switch_item, + Some(ResponseItem::Message { + id: None, + role: "developer".to_string(), + content: vec![ContentItem::InputText { + text: "\nINCOMING_MODEL_INSTRUCTIONS".to_string(), + }], + end_turn: None, + phase: None, + }) + ); + assert_eq!( + incoming_items, + vec![ResponseItem::Message { + id: None, + role: "user".to_string(), + content: vec![ContentItem::InputText { + text: "INCOMING_USER_MESSAGE".to_string(), + }], + end_turn: None, + phase: None, + }] + ); + assert!( + history + .raw_items() + .iter() + .any(Session::is_model_switch_developer_message) + ); + } + #[test] fn collect_user_messages_extracts_user_text_only() { let items = vec![