Centralize compacted rollout item construction (#34533)

## What changed

- Pass only compaction message and window metadata into
  `Session::replace_compacted_history`.
- Build the persisted `CompactedItem` there after assigning any missing response
  item IDs, so its replacement history matches the live history without callers
  cloning or assembling it themselves.

GitOrigin-RevId: a6106c33b6d3eef39ac0b563a50e17f6782970dc
This commit is contained in:
Charlie Marsh authored and copyberry committed 2026-07-21 12:53:00 +00:00
1 parent c44c4de7b4
commit f69f88f811
4 files changed
+41 -38

No files matched your search

+16 -10
View File
@@ -18,6 +18,7 @@ use crate::session::session::Session;
use crate::session::step_context::StepContext; use crate::session::step_context::StepContext;
use crate::session::turn::get_last_assistant_message_from_turn; use crate::session::turn::get_last_assistant_message_from_turn;
use crate::session::turn_context::TurnContext; use crate::session::turn_context::TurnContext;
use crate::state::AutoCompactWindowIds;
use crate::util::backoff; use crate::util::backoff;
use codex_analytics::CodexCompactionEvent; use codex_analytics::CodexCompactionEvent;
use codex_analytics::CompactionImplementation; use codex_analytics::CompactionImplementation;
@@ -35,7 +36,6 @@ use codex_protocol::models::ContentItem;
use codex_protocol::models::InternalChatMessageMetadataPassthrough; use codex_protocol::models::InternalChatMessageMetadataPassthrough;
use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ResponseItem; use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::CompactedItem;
use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::RawResponseCompletedEvent; use codex_protocol::protocol::RawResponseCompletedEvent;
use codex_protocol::protocol::TurnStartedEvent; use codex_protocol::protocol::TurnStartedEvent;
@@ -72,6 +72,16 @@ pub(crate) enum InitialContextInjection {
DoNotInject, DoNotInject,
} }
/// Metadata for a new compaction checkpoint, kept separate from its replacement history.
///
/// `Session::replace_compacted_history` assigns missing item IDs before constructing the persisted
/// `CompactedItem`, ensuring the live and persisted histories remain identical.
pub(crate) struct CompactedHistoryMetadata {
pub(crate) message: String,
pub(crate) window_number: u64,
pub(crate) window_ids: AutoCompactWindowIds,
}
pub(crate) async fn build_compaction_initial_context( pub(crate) async fn build_compaction_initial_context(
sess: &Session, sess: &Session,
initial_context_injection: &InitialContextInjection, initial_context_injection: &InitialContextInjection,
@@ -357,20 +367,16 @@ async fn run_compact_task_inner_impl(
Some(turn_context.to_turn_context_item()) Some(turn_context.to_turn_context_item())
} }
}; };
let compacted_item = CompactedItem {
message: summary_text.clone(),
replacement_history: Some(new_history.clone()),
window_number: Some(window_number),
first_window_id: Some(window_ids.first_window_id.to_string()),
previous_window_id: window_ids.previous_window_id.map(|id| id.to_string()),
window_id: Some(window_ids.window_id.to_string()),
};
sess.replace_compacted_history( sess.replace_compacted_history(
turn_context.as_ref(), turn_context.as_ref(),
new_history, new_history,
reference_context_item, reference_context_item,
world_state_baseline, world_state_baseline,
compacted_item, CompactedHistoryMetadata {
message: summary_text,
window_number,
window_ids,
},
) )
.await; .await;
sess.recompute_token_usage(&turn_context).await; sess.recompute_token_usage(&turn_context).await;
+6 -10
View File
@@ -1,6 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use std::sync::OnceLock; use std::sync::OnceLock;
use crate::compact::CompactedHistoryMetadata;
use crate::compact::CompactionAnalyticsAttempt; use crate::compact::CompactionAnalyticsAttempt;
use crate::compact::CompactionAnalyticsDetails; use crate::compact::CompactionAnalyticsDetails;
use crate::compact::InitialContextInjection; use crate::compact::InitialContextInjection;
@@ -32,7 +33,6 @@ use codex_protocol::models::BaseInstructions;
use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputBody;
use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::ResponseItem; use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::CompactedItem;
use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::TurnStartedEvent; use codex_protocol::protocol::TurnStartedEvent;
use codex_rollout_trace::CompactionCheckpointTracePayload; use codex_rollout_trace::CompactionCheckpointTracePayload;
@@ -269,14 +269,6 @@ async fn run_remote_compact_task_inner_impl(
Some(compaction_turn_context.to_turn_context_item()) Some(compaction_turn_context.to_turn_context_item())
} }
}; };
let compacted_item = CompactedItem {
message: String::new(),
replacement_history: Some(new_history.clone()),
window_number: Some(new_window_number),
first_window_id: Some(new_window_ids.first_window_id.to_string()),
previous_window_id: new_window_ids.previous_window_id.map(|id| id.to_string()),
window_id: Some(new_window_ids.window_id.to_string()),
};
// Install is the semantic boundary where the compact endpoint's output becomes live // Install is the semantic boundary where the compact endpoint's output becomes live
// thread history. Keep it distinct from the later inference request so the reducer can // thread history. Keep it distinct from the later inference request so the reducer can
// still represent repeated developer/context prefix items exactly as the model saw them. // still represent repeated developer/context prefix items exactly as the model saw them.
@@ -291,7 +283,11 @@ async fn run_remote_compact_task_inner_impl(
new_history, new_history,
reference_context_item, reference_context_item,
world_state_baseline, world_state_baseline,
compacted_item, CompactedHistoryMetadata {
message: String::new(),
window_number: new_window_number,
window_ids: new_window_ids,
},
) )
.await; .await;
sess.recompute_token_usage(compaction_turn_context).await; sess.recompute_token_usage(compaction_turn_context).await;
+6 -10
View File
@@ -4,6 +4,7 @@ use crate::Prompt;
use crate::ResponseStream; use crate::ResponseStream;
use crate::client::ModelClientSession; use crate::client::ModelClientSession;
use crate::client_common::ResponseEvent; use crate::client_common::ResponseEvent;
use crate::compact::CompactedHistoryMetadata;
use crate::compact::CompactionAnalyticsAttempt; use crate::compact::CompactionAnalyticsAttempt;
use crate::compact::CompactionAnalyticsDetails; use crate::compact::CompactionAnalyticsDetails;
use crate::compact::InitialContextInjection; use crate::compact::InitialContextInjection;
@@ -33,7 +34,6 @@ use codex_protocol::items::ContextCompactionItem;
use codex_protocol::items::TurnItem; use codex_protocol::items::TurnItem;
use codex_protocol::models::ContentItem; use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem; use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::CompactedItem;
use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::TokenUsage; use codex_protocol::protocol::TokenUsage;
use codex_protocol::protocol::TruncationPolicy; use codex_protocol::protocol::TruncationPolicy;
@@ -293,14 +293,6 @@ async fn run_remote_compact_task_inner_impl(
Some(compaction_turn_context.to_turn_context_item()) Some(compaction_turn_context.to_turn_context_item())
} }
}; };
let compacted_item = CompactedItem {
message: String::new(),
replacement_history: Some(new_history.clone()),
window_number: Some(new_window_number),
first_window_id: Some(new_window_ids.first_window_id.to_string()),
previous_window_id: new_window_ids.previous_window_id.map(|id| id.to_string()),
window_id: Some(new_window_ids.window_id.to_string()),
};
if let Some(trace_input_history) = trace_input_history.as_deref() { if let Some(trace_input_history) = trace_input_history.as_deref() {
compaction_trace.record_installed(&CompactionCheckpointTracePayload { compaction_trace.record_installed(&CompactionCheckpointTracePayload {
input_history: trace_input_history, input_history: trace_input_history,
@@ -312,7 +304,11 @@ async fn run_remote_compact_task_inner_impl(
new_history, new_history,
reference_context_item, reference_context_item,
world_state_baseline, world_state_baseline,
compacted_item, CompactedHistoryMetadata {
message: String::new(),
window_number: new_window_number,
window_ids: new_window_ids,
},
) )
.await; .await;
sess.recompute_token_usage(compaction_turn_context).await; sess.recompute_token_usage(compaction_turn_context).await;
+13 -8
View File
@@ -20,6 +20,7 @@ use crate::attestation::AttestationProvider;
use crate::audio_preparation::prepare_response_items as prepare_audio_response_items; use crate::audio_preparation::prepare_response_items as prepare_audio_response_items;
use crate::build_available_skills; use crate::build_available_skills;
use crate::compact; use crate::compact;
use crate::compact::CompactedHistoryMetadata;
use crate::config::ManagedFeatures; use crate::config::ManagedFeatures;
use crate::config::resolve_tool_suggest_config_from_layer_stack; use crate::config::resolve_tool_suggest_config_from_layer_stack;
use crate::context::ApprovedCommandPrefixSaved; use crate::context::ApprovedCommandPrefixSaved;
@@ -3052,7 +3053,7 @@ impl Session {
items: Vec<ResponseItem>, items: Vec<ResponseItem>,
reference_context_item: Option<TurnContextItem>, reference_context_item: Option<TurnContextItem>,
world_state_baseline: Option<Arc<WorldState>>, world_state_baseline: Option<Arc<WorldState>>,
compacted_item: CompactedItem, metadata: CompactedHistoryMetadata,
) { ) {
let items = if turn_context.item_ids_enabled() { let items = if turn_context.item_ids_enabled() {
Self::assign_missing_response_item_ids(Cow::Owned(items)).into_owned() Self::assign_missing_response_item_ids(Cow::Owned(items)).into_owned()
@@ -3060,8 +3061,15 @@ impl Session {
items items
}; };
let compacted_item = CompactedItem { let compacted_item = CompactedItem {
message: metadata.message,
replacement_history: Some(items.clone()), replacement_history: Some(items.clone()),
..compacted_item window_number: Some(metadata.window_number),
first_window_id: Some(metadata.window_ids.first_window_id.to_string()),
previous_window_id: metadata
.window_ids
.previous_window_id
.map(|id| id.to_string()),
window_id: Some(metadata.window_ids.window_id.to_string()),
}; };
// Compaction starts a new history window, so its WorldState baseline must be full. // Compaction starts a new history window, so its WorldState baseline must be full.
let mut world_state_item = None; let mut world_state_item = None;
@@ -3544,13 +3552,10 @@ impl Session {
context_items, context_items,
Some(turn_context_item), Some(turn_context_item),
Some(world_state), Some(world_state),
CompactedItem { CompactedHistoryMetadata {
message: String::new(), message: String::new(),
replacement_history: None, window_number,
window_number: Some(window_number), window_ids,
first_window_id: Some(window_ids.first_window_id.to_string()),
previous_window_id: window_ids.previous_window_id.map(|id| id.to_string()),
window_id: Some(window_ids.window_id.to_string()),
}, },
) )
.await; .await;