mirror of
https://github.com/openai/codex.git
synced 2026-11-01 19:33:35 +00:00
core: collapse reference turn context state
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
co-authored by
Codex
parent
66e13b89ba
commit
79cf16c434
@@ -187,12 +187,13 @@ pub enum SteerInputError {
|
||||
EmptyInput,
|
||||
}
|
||||
|
||||
/// Notes from the previous real user turn.
|
||||
/// Notes from the last stored turn-context snapshot that future prompt construction
|
||||
/// should diff against.
|
||||
///
|
||||
/// Conceptually this is the same role that `previous_model` used to fill, but
|
||||
/// it can carry other prior-turn settings that matter when constructing
|
||||
/// sensible state-change diffs or full-context reinjection, such as model
|
||||
/// switches or detecting a prior `realtime_active -> false` transition.
|
||||
/// Conceptually this is the same role that `previous_model` used to fill, but it
|
||||
/// can carry other prior-turn settings that matter when constructing sensible
|
||||
/// state-change diffs or full-context reinjection, such as model switches or
|
||||
/// detecting a prior `realtime_active -> false` transition.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct PreviousTurnSettings {
|
||||
pub(crate) model: String,
|
||||
@@ -2147,6 +2148,8 @@ impl Session {
|
||||
.await;
|
||||
{
|
||||
let mut state = self.state.lock().await;
|
||||
// The injected initial context becomes the new baseline for future diffs,
|
||||
// so advance both the stored settings snapshot and the reference baseline.
|
||||
state.set_reference_context_item(Some(turn_context.to_turn_context_item()));
|
||||
}
|
||||
|
||||
@@ -2174,7 +2177,7 @@ impl Session {
|
||||
.previous_turn_settings();
|
||||
let surviving_turn_context_item = reconstructed_rollout
|
||||
.reference_turn_context_state
|
||||
.latest_turn_context_item();
|
||||
.turn_context_item();
|
||||
let mut state = self.state.lock().await;
|
||||
state.replace_history_with_reference_turn_context_state(
|
||||
reconstructed_rollout.history,
|
||||
|
||||
@@ -64,39 +64,26 @@ 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.
|
||||
let segment_is_newest_stored_turn_context =
|
||||
reference_turn_context_state.turn_context_item().is_none();
|
||||
|
||||
// Only real user turns may establish the surviving stored turn-context snapshot.
|
||||
// Standalone task turns can carry lifecycle events, but they must not become the source of
|
||||
// future settings diffs or rollback metadata.
|
||||
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()
|
||||
&& segment_is_newest_stored_turn_context
|
||||
&& let Some(turn_context_item) = segment_turn_context_state.turn_context_item()
|
||||
{
|
||||
reference_turn_context_state.set_latest_turn_context_item(Some(turn_context_item));
|
||||
reference_turn_context_state.set_turn_context_item_during_reverse_replay(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.
|
||||
if segment_turn_context_state.compacted_since_model_saw_reference_turn_context()
|
||||
&& reference_turn_context_state
|
||||
.stored_reference_turn_context_item()
|
||||
.is_none()
|
||||
// A compaction seen in this segment shadows any older baseline, but it must not hide a newer
|
||||
// stored turn context we already captured from a later surviving user turn.
|
||||
if segment_is_newest_stored_turn_context
|
||||
&& segment_turn_context_state.compacted_since_model_saw_reference_turn_context()
|
||||
{
|
||||
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.
|
||||
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()
|
||||
.is_none()
|
||||
&& let Some(turn_context_item) =
|
||||
segment_turn_context_state.stored_reference_turn_context_item()
|
||||
{
|
||||
reference_turn_context_state.set_reference_context_item(Some(turn_context_item));
|
||||
}
|
||||
}
|
||||
|
||||
impl Session {
|
||||
@@ -212,9 +199,7 @@ impl Session {
|
||||
|
||||
if base_replacement_history.is_some()
|
||||
&& pending_rollback_turns == 0
|
||||
&& reference_turn_context_state
|
||||
.latest_turn_context_item()
|
||||
.is_some()
|
||||
&& reference_turn_context_state.turn_context_item().is_some()
|
||||
{
|
||||
// At this point the replay-derived metadata and replacement-history base for the
|
||||
// surviving tail are both fixed, so older rollout items cannot affect this result.
|
||||
|
||||
@@ -257,7 +257,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(),
|
||||
.turn_context_item(),
|
||||
)
|
||||
.expect("serialize surviving turn context item"),
|
||||
serde_json::to_value(Some(first_context_item))
|
||||
@@ -491,7 +491,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(),
|
||||
.turn_context_item(),
|
||||
)
|
||||
.expect("serialize surviving turn context item"),
|
||||
serde_json::to_value(Some(first_context_item))
|
||||
|
||||
@@ -1033,7 +1033,7 @@ async fn record_initial_history_reconstructs_forked_transcript() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn record_initial_history_forked_hydrates_previous_turn_settings() {
|
||||
async fn record_initial_history_forked_advances_previous_turn_settings_to_injected_baseline() {
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
let previous_model = "forked-rollout-model";
|
||||
let previous_context_item = TurnContextItem {
|
||||
@@ -1094,7 +1094,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),
|
||||
})
|
||||
);
|
||||
@@ -3914,7 +3914,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_diff_from_stored_baseline()
|
||||
{
|
||||
let (session, previous_context) = make_session_and_context().await;
|
||||
let next_model = if previous_context.model_info.slug == "gpt-5.1" {
|
||||
@@ -3955,7 +3955,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)
|
||||
@@ -3963,7 +3963,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)
|
||||
|
||||
@@ -36,30 +36,24 @@ 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 last stored turn-context snapshot that survives
|
||||
/// history replay, rollback, and compaction.
|
||||
///
|
||||
/// 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 `TurnContextItem` drives future settings diffs and rollback metadata.
|
||||
/// Compaction may shadow it for reference-baseline purposes without erasing the
|
||||
/// stored snapshot itself.
|
||||
#[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 snapshot 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 drives `previous_turn_settings()` and rollback bookkeeping even when
|
||||
/// compaction has shadowed it for reference-baseline purposes.
|
||||
turn_context_item: Option<TurnContextItem>,
|
||||
/// Whether compaction has crossed the current reference baseline without a later
|
||||
/// reinjection or real turn context re-establishing it.
|
||||
/// reinjection or newer stored 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 snapshot for replay or
|
||||
/// rollback bookkeeping.
|
||||
compacted_since_model_saw_reference_turn_context: bool,
|
||||
}
|
||||
@@ -74,7 +68,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;
|
||||
}
|
||||
}
|
||||
@@ -83,39 +77,34 @@ 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.reference_turn_context_item.is_none() {
|
||||
self.reference_turn_context_item = Some(turn_context_item.clone());
|
||||
if self.turn_context_item.is_none() {
|
||||
self.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 set_turn_context_item_during_reverse_replay(
|
||||
&mut self,
|
||||
turn_context_item: TurnContextItem,
|
||||
) {
|
||||
self.turn_context_item = Some(turn_context_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 turn_context_item(&self) -> Option<TurnContextItem> {
|
||||
self.turn_context_item.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn compacted_since_model_saw_reference_turn_context(&self) -> bool {
|
||||
@@ -123,7 +112,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(),
|
||||
@@ -135,7 +124,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user