core: collapse reference turn context state

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Charles Cunningham
2026-03-21 17:00:08 -07:00
parent 0bccddcd3f
commit e92d3a73b9
5 changed files with 44 additions and 69 deletions

View File

@@ -3664,9 +3664,9 @@ impl Session {
self.persist_rollout_items(&[RolloutItem::TurnContext(turn_context_item.clone())])
.await;
// Advance the in-memory turn-context tracker even when this turn emitted no model-visible
// context items. Regular turns become both the latest turn-settings source and the active
// model-visible reference baseline for subsequent diffing.
// Advance the stored turn-context snapshot even when this turn emitted no model-visible
// context items. Regular turns become both the `previous_turn_settings()` source and the
// active model-visible reference baseline for subsequent diffing.
self.record_regular_turn_context(turn_context_item).await;
}

View File

@@ -65,38 +65,29 @@ fn merge_surviving_segment_turn_context_state(
segment_turn_context_state: ReferenceTurnContextState,
counts_as_user_turn: bool,
) {
// Only real user turns should backfill "previous turn settings". Standalone task turns may
// carry lifecycle events, but they must not become the latest real turn context.
if counts_as_user_turn
&& reference_turn_context_state
.latest_turn_context_item()
.is_none()
&& let Some(turn_context_item) = segment_turn_context_state.latest_turn_context_item()
{
reference_turn_context_state.set_latest_turn_context_item(Some(turn_context_item));
}
// A compaction seen in this segment hides older reference baselines, but it must not erase a
// newer stored reference baseline we already captured from a later surviving user turn.
// A compaction seen in this segment shadows any older stored turn context, but it must not
// erase a newer stored turn context we already captured from a later surviving user turn.
if segment_turn_context_state.compacted_since_model_saw_reference_turn_context()
&& reference_turn_context_state
.stored_reference_turn_context_item()
.stored_turn_context_item()
.is_none()
{
reference_turn_context_state.note_compaction();
}
// The model-visible reference baseline comes from the newest surviving user turn that both
// carries a stored baseline and has not been hidden by a later surviving compaction.
// Only real user turns should establish the stored turn context. Standalone task turns may
// carry lifecycle events, but they must not become the source of `previous_turn_settings()`
// or the reference baseline.
//
// This stores the newest surviving real turn context, while preserving any later compaction
// shadow we already learned about from newer surviving segments.
if counts_as_user_turn
&& !reference_turn_context_state.compacted_since_model_saw_reference_turn_context()
&& reference_turn_context_state
.stored_reference_turn_context_item()
.stored_turn_context_item()
.is_none()
&& let Some(turn_context_item) =
segment_turn_context_state.stored_reference_turn_context_item()
&& let Some(turn_context_item) = segment_turn_context_state.stored_turn_context_item()
{
reference_turn_context_state.set_reference_context_item(Some(turn_context_item));
reference_turn_context_state.note_turn_context_during_reverse_replay(&turn_context_item);
}
}
@@ -217,7 +208,7 @@ impl Session {
if base_replacement_history.is_some()
&& pending_rollback_turns == 0
&& reference_turn_context_state
.latest_turn_context_item()
.stored_turn_context_item()
.is_some()
{
// At this point the replay-derived metadata and replacement-history base for the

View File

@@ -271,7 +271,7 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_com
serde_json::to_value(
reconstructed
.reference_turn_context_state
.latest_turn_context_item(),
.stored_turn_context_item(),
)
.expect("serialize surviving turn context item"),
serde_json::to_value(Some(first_context_item))
@@ -499,7 +499,7 @@ async fn reconstruct_history_rollback_backfills_surviving_turn_context_from_olde
serde_json::to_value(
reconstructed
.reference_turn_context_state
.latest_turn_context_item(),
.stored_turn_context_item(),
)
.expect("serialize surviving turn context item"),
serde_json::to_value(Some(first_context_item))

View File

@@ -1299,7 +1299,7 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
}
#[tokio::test]
async fn record_initial_history_forked_hydrates_previous_turn_settings() {
async fn record_initial_history_forked_advances_previous_turn_settings_to_current_baseline() {
let (session, turn_context) = make_session_and_context().await;
let previous_model = "forked-rollout-model";
let previous_context_item = TurnContextItem {
@@ -1358,7 +1358,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
assert_eq!(
session.previous_turn_settings().await,
Some(PreviousTurnSettings {
model: previous_model.to_string(),
model: turn_context.model_info.slug.clone(),
realtime_active: Some(turn_context.realtime_active),
})
);
@@ -4119,7 +4119,7 @@ async fn record_context_updates_and_set_reference_context_item_reinjects_full_co
}
#[tokio::test]
async fn record_context_updates_and_set_reference_context_item_persists_baseline_without_emitting_diffs()
async fn record_context_updates_and_set_reference_context_item_emits_model_switch_for_stored_baseline()
{
let (session, previous_context) = make_session_and_context().await;
let next_model = if previous_context.model_info.slug == "gpt-5.1" {
@@ -4160,7 +4160,7 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
let update_items = session
.build_settings_update_items(Some(&previous_context_item), &turn_context)
.await;
assert_eq!(update_items, Vec::new());
assert!(!update_items.is_empty());
session
.record_context_updates_and_set_reference_context_item(&turn_context)
@@ -4168,7 +4168,7 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
assert_eq!(
session.clone_history().await.raw_items().to_vec(),
Vec::new()
update_items
);
assert_eq!(
serde_json::to_value(session.reference_context_item().await)

View File

@@ -39,30 +39,26 @@ pub(crate) struct ContextManager {
reference_turn_context_state: ReferenceTurnContextState,
}
/// Session-owned bookkeeping for turn-context state that survives history replay,
/// rollback, and compaction.
/// Session-owned bookkeeping for the stored turn context that drives both
/// `previous_turn_settings()` and the model-visible reference baseline.
///
/// This intentionally tracks both the latest real turn context we know about and the
/// model-visible reference baseline, because those diverge when compaction hides the
/// baseline without erasing the last real turn's settings.
/// The stored turn context survives compaction so future turns can still diff
/// against the last known settings, while
/// `compacted_since_model_saw_reference_turn_context` controls whether the model
/// may still treat that stored item as the active reference baseline.
#[derive(Debug, Clone, Default)]
pub(crate) struct ReferenceTurnContextState {
/// The most recent real turn context we reconstructed or recorded, even if a later
/// compaction means the model can no longer rely on it as the active baseline.
/// The last stored turn context we reconstructed or recorded.
///
/// This drives `previous_turn_settings()` and other rollback bookkeeping, which
/// intentionally survive compaction until a newer real turn replaces them.
latest_turn_context_item: Option<TurnContextItem>,
/// The last turn context item that established the model's reference baseline.
///
/// Unlike `latest_turn_context_item`, this is only model-visible when
/// `compacted_since_model_saw_reference_turn_context` is false.
reference_turn_context_item: Option<TurnContextItem>,
/// This is the single source of truth for both `previous_turn_settings()` and
/// the reference baseline. Compaction can shadow it for model visibility
/// without erasing it.
turn_context_item: Option<TurnContextItem>,
/// Whether compaction has crossed the current reference baseline without a later
/// reinjection or real turn context re-establishing it.
///
/// When this is true, `reference_context_item()` must return `None` even if
/// `reference_turn_context_item` still retains the last stored baseline for replay or
/// `turn_context_item` still retains the last stored baseline for replay or
/// rollback bookkeeping.
compacted_since_model_saw_reference_turn_context: bool,
}
@@ -77,7 +73,7 @@ impl ReferenceTurnContextState {
}
pub(crate) fn note_compaction_during_reverse_replay(&mut self) {
if self.reference_turn_context_item.is_none() {
if self.turn_context_item.is_none() {
self.compacted_since_model_saw_reference_turn_context = true;
}
}
@@ -86,39 +82,27 @@ impl ReferenceTurnContextState {
&mut self,
turn_context_item: &TurnContextItem,
) {
if self.latest_turn_context_item.is_none() {
self.latest_turn_context_item = Some(turn_context_item.clone());
if self.turn_context_item.is_none() {
self.turn_context_item = Some(turn_context_item.clone());
}
if self.reference_turn_context_item.is_none() {
self.reference_turn_context_item = Some(turn_context_item.clone());
}
}
pub(crate) fn set_latest_turn_context_item(&mut self, item: Option<TurnContextItem>) {
self.latest_turn_context_item = item;
}
pub(crate) fn record_regular_turn_context(&mut self, turn_context_item: TurnContextItem) {
self.latest_turn_context_item = Some(turn_context_item.clone());
self.reference_turn_context_item = Some(turn_context_item);
self.turn_context_item = Some(turn_context_item);
self.compacted_since_model_saw_reference_turn_context = false;
}
pub(crate) fn set_reference_context_item(&mut self, item: Option<TurnContextItem>) {
if let Some(item) = item {
self.reference_turn_context_item = Some(item);
self.turn_context_item = Some(item);
self.compacted_since_model_saw_reference_turn_context = false;
} else {
self.note_compaction();
}
}
pub(crate) fn latest_turn_context_item(&self) -> Option<TurnContextItem> {
self.latest_turn_context_item.clone()
}
pub(crate) fn stored_reference_turn_context_item(&self) -> Option<TurnContextItem> {
self.reference_turn_context_item.clone()
pub(crate) fn stored_turn_context_item(&self) -> Option<TurnContextItem> {
self.turn_context_item.clone()
}
pub(crate) fn compacted_since_model_saw_reference_turn_context(&self) -> bool {
@@ -126,7 +110,7 @@ impl ReferenceTurnContextState {
}
pub(crate) fn previous_turn_settings(&self) -> Option<PreviousTurnSettings> {
self.latest_turn_context_item
self.turn_context_item
.as_ref()
.map(|turn_context_item| PreviousTurnSettings {
model: turn_context_item.model.clone(),
@@ -138,7 +122,7 @@ impl ReferenceTurnContextState {
if self.compacted_since_model_saw_reference_turn_context {
None
} else {
self.reference_turn_context_item.clone()
self.turn_context_item.clone()
}
}
}