Introduce a dedicated context window ID type (#59328)

## What changed

Add `ContextWindowId` as a string-backed protocol type with UUIDv7 generation and parsing. Use it throughout compaction state, rollout reconstruction, token budget context, and request metadata in place of raw UUIDs, preserving the serialized string format and UUIDv7 validation.

GitOrigin-RevId: 786b74fcc331707ca5d6765f429b6e5e322d9328
This commit is contained in:
Ruslan Nigmatullin
2026-11-04 06:05:16 +00:00
committed by copyberry
parent 3a2788e6fc
commit 19438adcb6
28 changed files with 260 additions and 187 deletions
@@ -490,7 +490,7 @@ impl ExternalAgentSessionImporter {
history_mode: ThreadHistoryMode::Legacy,
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: uuid::Uuid::now_v7().to_string(),
initial_window_id: codex_protocol::ContextWindowId::new_uuid_v7().to_string(),
metadata: ThreadPersistenceMetadata {
cwd: Some(cwd.clone()),
model_provider: model_provider.clone(),
@@ -140,7 +140,7 @@ async fn get_conversation_summary_by_thread_id_reads_pathless_store_thread() ->
history_mode: Default::default(),
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: Uuid::now_v7().to_string(),
initial_window_id: codex_protocol::ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: None,
@@ -13,6 +13,7 @@
//! is a stop-gap that prevents additional local persistence writes from slipping
//! in unnoticed.
use codex_protocol::ContextWindowId;
use std::collections::BTreeSet;
use std::path::Path;
use std::sync::Arc;
@@ -300,7 +301,7 @@ async fn thread_list_repeated_cursor_returns_internal_error() -> Result<()> {
history_mode: Default::default(),
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: Uuid::now_v7().to_string(),
initial_window_id: ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: Some(codex_home.path().to_path_buf()),
@@ -548,7 +549,7 @@ async fn thread_delete_with_non_local_thread_store_does_not_create_local_persist
history_mode: Default::default(),
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: Uuid::now_v7().to_string(),
initial_window_id: ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: Some(codex_home.path().to_path_buf()),
@@ -63,6 +63,7 @@ use codex_core::ARCHIVED_SESSIONS_SUBDIR;
use codex_core::config::ConfigBuilder;
use codex_exec_server::EnvironmentManager;
use codex_feedback::CodexFeedback;
use codex_protocol::ContextWindowId;
use codex_protocol::items::AgentMessageContent;
use codex_protocol::items::AgentMessageItem;
use codex_protocol::items::TurnItem as CoreTurnItem;
@@ -653,7 +654,7 @@ async fn thread_search_occurrences_reads_paginated_projection() -> Result<()> {
history_mode: codex_protocol::protocol::ThreadHistoryMode::Paginated,
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: Uuid::now_v7().to_string(),
initial_window_id: ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: Some(codex_home.path().to_path_buf()),
@@ -1864,7 +1865,7 @@ async fn paginated_history_lists_and_legacy_reads_use_projected_turns_and_items(
history_mode: codex_protocol::protocol::ThreadHistoryMode::Paginated,
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: Uuid::now_v7().to_string(),
initial_window_id: ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: Some(codex_home.path().to_path_buf()),
@@ -2833,7 +2834,7 @@ async fn seed_pathless_store_thread(
history_mode: Default::default(),
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: Uuid::now_v7().to_string(),
initial_window_id: ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: None,
@@ -37,7 +37,6 @@ use codex_thread_store::ThreadStore;
use codex_utils_absolute_path::test_support::PathExt;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use uuid::Uuid;
#[tokio::test]
async fn timeline_pages_mix_items_and_resolve_the_opening_realtime_session() -> Result<()> {
@@ -77,7 +76,7 @@ async fn timeline_pages_mix_items_and_resolve_the_opening_realtime_session() ->
history_mode: ThreadHistoryMode::Paginated,
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: Uuid::now_v7().to_string(),
initial_window_id: codex_protocol::ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: Some(codex_home.path().to_path_buf()),
@@ -265,7 +265,7 @@ async fn thread_unarchive_preserves_pathless_store_metadata() -> Result<()> {
history_mode: Default::default(),
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: Uuid::now_v7().to_string(),
initial_window_id: codex_protocol::ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: None,
@@ -64,7 +64,7 @@ pub(crate) struct BackgroundCompactionCandidate {
pub(crate) struct BackgroundCompactionInput {
prefix: CompactionPrefix,
turn_state: Option<String>,
window: (String, u64, uuid::Uuid),
window: (String, u64, codex_protocol::ContextWindowId),
base_instructions: BaseInstructions,
input: Vec<ResponseItem>,
metadata: Vec<Option<CodexHarnessMetadata>>,
@@ -283,7 +283,7 @@ pub(crate) async fn capture_background_input(
base_instructions: BaseInstructions,
turn_state: Option<String>,
auth_owner_generation: Option<u64>,
window: (String, u64, uuid::Uuid),
window: (String, u64, codex_protocol::ContextWindowId),
) -> BackgroundCompactionInput {
let base_instructions = if step_context.settings.model_info.use_responses_lite
&& snapshot
@@ -6,6 +6,7 @@ use crate::context::world_state::WorldStateUpdate;
use codex_context_fragments::AnnotatedContent;
use codex_context_fragments::message_from_parts;
use codex_protocol::AgentPath;
use codex_protocol::ContextWindowId;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ContentItemKind;
use codex_protocol::models::ResponseItem;
@@ -14,22 +15,21 @@ use codex_protocol::protocol::CONTEXT_WINDOW_CLOSE_TAG;
use codex_protocol::protocol::CONTEXT_WINDOW_GUIDANCE_CLOSE_TAG;
use codex_protocol::protocol::CONTEXT_WINDOW_GUIDANCE_OPEN_TAG;
use codex_protocol::protocol::CONTEXT_WINDOW_OPEN_TAG;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct TokenBudgetContext {
agent_path: AgentPath,
first_window_id: Uuid,
previous_window_id: Option<Uuid>,
window_id: Uuid,
first_window_id: ContextWindowId,
previous_window_id: Option<ContextWindowId>,
window_id: ContextWindowId,
}
impl TokenBudgetContext {
pub(crate) fn new(
agent_path: AgentPath,
first_window_id: Uuid,
previous_window_id: Option<Uuid>,
window_id: Uuid,
first_window_id: ContextWindowId,
previous_window_id: Option<ContextWindowId>,
window_id: ContextWindowId,
) -> Self {
Self {
agent_path,
@@ -73,14 +73,14 @@ impl ContextualUserFragment for TokenBudgetContext {
}
fn body(&self) -> ContentItem {
let first_window_id = self.first_window_id;
let window_id = self.window_id;
let first_window_id = &self.first_window_id;
let window_id = &self.window_id;
let mut lines = vec![
format!("Agent name: {}", self.agent_path),
format!("First context window id: {first_window_id}"),
format!("Current context window id: {window_id}"),
];
if let Some(previous_window_id) = self.previous_window_id {
if let Some(previous_window_id) = &self.previous_window_id {
lines.push(format!("Previous context window id: {previous_window_id}"));
}
input_text(format!("\n{}\n", lines.join("\n")))
+4 -4
View File
@@ -8,6 +8,7 @@ use codex_analytics::CompactionReason;
use codex_analytics::CompactionStrategy;
use codex_analytics::CompactionTrigger;
use codex_git_utils::SanitizedGitUrl;
use codex_protocol::ContextWindowId;
use codex_protocol::ThreadId;
use codex_protocol::mcp::McpAttribution;
use codex_protocol::protocol::SessionSource;
@@ -19,7 +20,6 @@ use http::HeaderMap as ApiHeaderMap;
use http::HeaderValue;
use serde::Serialize;
use serde_json::Value;
use uuid::Uuid;
use crate::client::X_CODEX_INSTALLATION_ID_HEADER;
use crate::client::X_CODEX_PARENT_THREAD_ID_HEADER;
@@ -248,7 +248,7 @@ pub struct CodexResponsesMetadata {
pub(crate) routing_hint: Option<HeaderValue>,
pub(crate) window_id: String,
pub(crate) window_number: Option<u64>,
pub(crate) context_window_id: Option<Uuid>,
pub(crate) context_window_id: Option<ContextWindowId>,
pub(crate) history_initialization: Option<HistoryInitialization>,
pub(crate) request_kind: Option<CodexResponsesRequestKind>,
pub(crate) forked_from_thread_id: Option<ThreadId>,
@@ -439,7 +439,7 @@ impl CodexResponsesMetadata {
window_id: has_request_identity.then_some(self.window_id.as_str()),
window_number: has_request_identity.then_some(self.window_number).flatten(),
context_window_id: has_request_identity
.then_some(self.context_window_id)
.then_some(self.context_window_id.as_ref())
.flatten(),
request_kind: request_kind_value,
history_initialization: self.history_initialization,
@@ -572,7 +572,7 @@ struct CodexTurnMetadataPayload<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
window_number: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
context_window_id: Option<Uuid>,
context_window_id: Option<&'a ContextWindowId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
request_kind: Option<&'static str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -0,0 +1,90 @@
//! Checks reconstructed history against live compaction and persisted replacement items.
#![cfg(test)]
use super::*;
use pretty_assertions::assert_eq;
#[tokio::test]
async fn reconstruct_history_matches_live_compactions() {
let (session, turn_context) = make_session_and_context().await;
let (rollout_items, expected) = sample_rollout(&session, &turn_context).await;
let reconstruction_turn = session.new_default_turn().await;
let reconstructed = session
.reconstruct_history_from_rollout(reconstruction_turn.as_ref(), &rollout_items)
.await;
assert_eq!(expected, raw_envelopes(&reconstructed.history));
assert_eq!(2, reconstructed.window_number);
assert_eq!(
reconstructed
.window_id
.map(|window_id| Uuid::parse_str(&window_id.to_string())
.unwrap()
.get_version_num()),
Some(7)
);
}
#[tokio::test]
async fn reconstruct_history_uses_replacement_history_verbatim() {
let (session, turn_context) = make_session_and_context().await;
let summary_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None,
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: "summary".to_string(),
}],
phase: None,
internal_chat_message_metadata_passthrough: Some(InternalChatMessageMetadataPassthrough {
turn_id: Some("compact-turn".to_string()),
..Default::default()
}),
};
let replacement_history = vec![
ResponseItemEnvelope {
item: summary_item.clone(),
metadata: Some(CodexHarnessMetadata::default()),
},
ResponseItemEnvelope::new(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None,
role: "developer".to_string(),
content: vec![ContentItem::InputText {
text: "stale developer instructions".to_string(),
}],
phase: None,
internal_chat_message_metadata_passthrough: None,
}),
];
let first_window_id = ContextWindowId::new_uuid_v7();
let previous_window_id = ContextWindowId::new_uuid_v7();
let window_id = ContextWindowId::new_uuid_v7();
let rollout_items = vec![RolloutItem::Compacted(Box::new(CompactedItem {
message: String::new(),
replacement_history: Some(replacement_history.clone()),
retained_context: None,
guardian_history: None,
mcp_resource_origins: None,
window_number: Some(42),
first_window_id: Some(first_window_id.to_string()),
previous_window_id: Some(previous_window_id.to_string()),
window_id: Some(window_id.to_string()),
compaction_response_id: None,
latest_token_usage_record: None,
resume_metadata: None,
}))];
let reconstructed = session
.reconstruct_history_from_rollout(&turn_context, &rollout_items)
.await;
assert_eq!(reconstructed.history, replacement_history);
assert_eq!(42, reconstructed.window_number);
assert_eq!(Some(first_window_id), reconstructed.first_window_id);
assert_eq!(Some(previous_window_id), reconstructed.previous_window_id);
assert_eq!(Some(window_id), reconstructed.window_id);
}
@@ -6,12 +6,13 @@ use crate::context_manager::ContextManager;
use codex_protocol::models::BaseInstructions;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::user_input::UserInput;
use codex_protocol_test_support::uuid_v7_item_id_generator_for_test;
use codex_utils_output_truncation::TruncationPolicy;
use pretty_assertions::assert_eq;
#[test]
fn history_sums_measured_and_unknown_file_audio() {
let mut history = ContextManager::new();
let mut history = ContextManager::new(uuid_v7_item_id_generator_for_test());
let item = ResponseInputItem::from(
["short", "unknown"]
.map(|file_id| UserInput::Audio {
@@ -20,7 +20,10 @@ impl Session {
mcp_resource_origins: self.services.mcp_runtime.resource_origin_checkpoint(),
window_number: Some(state.auto_compact_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()),
previous_window_id: window_ids
.previous_window_id
.as_ref()
.map(ToString::to_string),
window_id: Some(window_ids.window_id.to_string()),
compaction_response_id: None,
latest_token_usage_record: state.latest_token_usage_record.clone(),
+10 -6
View File
@@ -367,6 +367,7 @@ use codex_mcp::effective_mcp_servers;
use codex_otel::SessionTelemetry;
use codex_otel::THREAD_STARTED_METRIC;
use codex_otel::TelemetryAuthMode;
use codex_protocol::ContextWindowId;
use codex_protocol::ResponseItemId;
use codex_protocol::approvals::ExecApprovalKind;
use codex_protocol::config_types::CollaborationMode;
@@ -2017,11 +2018,11 @@ impl Session {
state.history.set_world_state_baseline(world_state);
}
let fallback_ids = state.auto_compact_window_ids();
let window_id = window_id.unwrap_or(fallback_ids.window_id);
let window_id = window_id.unwrap_or_else(|| fallback_ids.window_id.clone());
state.restore_auto_compact_window(
window_number,
AutoCompactWindowIds {
first_window_id: first_window_id.unwrap_or(window_id),
first_window_id: first_window_id.unwrap_or_else(|| window_id.clone()),
previous_window_id,
window_id,
},
@@ -4952,7 +4953,7 @@ impl Session {
let state = self.state.lock().await;
(
state.session_configuration.session_source.clone(),
state.auto_compact_window_ids(),
state.auto_compact_window_ids().clone(),
)
};
let separate_guardian_developer_message =
@@ -5254,15 +5255,18 @@ impl Session {
self.current_window().await.0
}
pub(crate) async fn current_window(&self) -> (String, u64, Uuid) {
pub(crate) async fn current_window(&self) -> (String, u64, ContextWindowId) {
let state = self.state.lock().await;
self.current_window_from_state(&state)
}
pub(crate) fn current_window_from_state(&self, state: &SessionState) -> (String, u64, Uuid) {
pub(crate) fn current_window_from_state(
&self,
state: &SessionState,
) -> (String, u64, ContextWindowId) {
let thread_id = self.thread_id;
let window_number = state.auto_compact_window_number();
let context_window_id = state.auto_compact_window_ids().window_id;
let context_window_id = state.auto_compact_window_ids().window_id.clone();
(
format!("{thread_id}:{window_number}"),
window_number,
@@ -3,11 +3,11 @@ use crate::context::world_state::WorldStateSnapshot;
use crate::context_manager::is_user_turn_boundary;
use codex_history::ResponseItemEnvelope;
use codex_history::TurnAttribution;
use codex_protocol::ContextWindowId;
use codex_protocol::protocol::SessionContextWindow;
use codex_protocol::protocol::ThreadHistoryMode;
use codex_utils_output_truncation::TruncationPolicy;
use std::collections::BTreeSet;
use uuid::Uuid;
// Return value of `Session::reconstruct_history_from_rollout`, bundling the rebuilt history with
// the resume/fork hydration metadata derived from the same replay.
@@ -23,17 +23,17 @@ pub(crate) struct RolloutReconstruction {
pub(super) turn_attribution: Option<TurnAttribution>,
pub(super) world_state_baseline: Option<WorldStateSnapshot>,
pub(super) window_number: u64,
pub(super) first_window_id: Option<Uuid>,
pub(super) previous_window_id: Option<Uuid>,
pub(super) window_id: Option<Uuid>,
pub(super) first_window_id: Option<ContextWindowId>,
pub(super) previous_window_id: Option<ContextWindowId>,
pub(super) window_id: Option<ContextWindowId>,
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
struct ReconstructedWindow {
number: u64,
first_id: Option<Uuid>,
previous_id: Option<Uuid>,
id: Option<Uuid>,
first_id: Option<ContextWindowId>,
previous_id: Option<ContextWindowId>,
id: Option<ContextWindowId>,
}
#[derive(Debug, Default)]
@@ -653,31 +653,31 @@ impl ContextManager {
}
}
fn parse_uuid_v7(value: &str) -> Option<Uuid> {
Uuid::parse_str(value)
.ok()
.filter(|uuid| uuid.get_version_num() == 7)
}
fn reconstructed_window_from_compaction(compacted: &CompactedItem) -> Option<ReconstructedWindow> {
Some(ReconstructedWindow {
number: compacted.window_number?,
first_id: compacted.first_window_id.as_deref().and_then(parse_uuid_v7),
first_id: compacted
.first_window_id
.as_deref()
.and_then(ContextWindowId::parse),
previous_id: compacted
.previous_window_id
.as_deref()
.and_then(parse_uuid_v7),
id: compacted.window_id.as_deref().and_then(parse_uuid_v7),
.and_then(ContextWindowId::parse),
id: compacted
.window_id
.as_deref()
.and_then(ContextWindowId::parse),
})
}
fn reconstructed_window_from_session_context_window(
context_window: &SessionContextWindow,
) -> Option<ReconstructedWindow> {
let id = parse_uuid_v7(&context_window.window_id)?;
let id = ContextWindowId::parse(&context_window.window_id)?;
Some(ReconstructedWindow {
number: 0,
first_id: Some(id),
first_id: Some(id.clone()),
previous_id: None,
id: Some(id),
})
@@ -10,6 +10,7 @@ use codex_history::InitialHistory;
use codex_history::ResponseItemEnvelope;
use codex_history::ResumedHistory;
use codex_protocol::AgentPath;
use codex_protocol::ContextWindowId;
use codex_protocol::ThreadId;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem;
@@ -30,7 +31,6 @@ use serde_json::json;
use std::collections::BTreeMap;
use std::path::PathBuf;
use test_case::test_case;
use uuid::Uuid;
#[tokio::test]
async fn recorded_questions_share_queued_input_order_across_resume() {
@@ -1444,7 +1444,7 @@ async fn record_initial_history_resumed_does_not_seed_reference_context_item_aft
async fn reconstruct_history_restores_initial_window_from_session_meta() {
let (session, turn_context) = make_session_and_context().await;
let thread_id = ThreadId::default();
let initial_window_id = Uuid::now_v7();
let initial_window_id = ContextWindowId::new_uuid_v7();
let rollout_items = vec![RolloutItem::SessionMeta(Box::new(SessionMetaLine {
meta: SessionMeta {
session_id: thread_id.into(),
@@ -1462,7 +1462,10 @@ async fn reconstruct_history_restores_initial_window_from_session_meta() {
.await;
assert_eq!(reconstructed.window_number, 0);
assert_eq!(reconstructed.first_window_id, Some(initial_window_id));
assert_eq!(
reconstructed.first_window_id.as_ref(),
Some(&initial_window_id)
);
assert_eq!(reconstructed.previous_window_id, None);
assert_eq!(reconstructed.window_id, Some(initial_window_id));
}
@@ -1471,10 +1474,10 @@ async fn reconstruct_history_restores_initial_window_from_session_meta() {
async fn reconstruct_history_prefers_compacted_window_over_session_meta() {
let (session, turn_context) = make_session_and_context().await;
let thread_id = ThreadId::default();
let initial_window_id = Uuid::now_v7();
let compacted_first_window_id = Uuid::now_v7();
let compacted_previous_window_id = Uuid::now_v7();
let compacted_window_id = Uuid::now_v7();
let initial_window_id = ContextWindowId::new_uuid_v7();
let compacted_first_window_id = ContextWindowId::new_uuid_v7();
let compacted_previous_window_id = ContextWindowId::new_uuid_v7();
let compacted_window_id = ContextWindowId::new_uuid_v7();
let rollout_items = vec![
RolloutItem::SessionMeta(Box::new(SessionMetaLine {
meta: SessionMeta {
@@ -1593,7 +1596,11 @@ async fn bounded_replay_matches_full_replay_after_empty_turn_compactions(
user_message("original task").into(),
)],
));
let window_ids = [Uuid::now_v7(), Uuid::now_v7(), Uuid::now_v7()];
let window_ids = [
ContextWindowId::new_uuid_v7(),
ContextWindowId::new_uuid_v7(),
ContextWindowId::new_uuid_v7(),
];
let current = matches!(compaction_format, CompactionFormat::Current);
let mut latest_compaction_index = 0;
for window_number in 1..=2 {
@@ -1961,7 +1968,7 @@ async fn completed_turn_suffix_after_compaction_overrides_resume_metadata(
async fn reconstruct_history_preserves_legacy_compaction_count_with_session_meta_window() {
let (session, turn_context) = make_session_and_context().await;
let thread_id = ThreadId::default();
let initial_window_id = Uuid::now_v7();
let initial_window_id = ContextWindowId::new_uuid_v7();
let rollout_items = vec![
RolloutItem::SessionMeta(Box::new(SessionMetaLine {
meta: SessionMeta {
+4 -3
View File
@@ -33,6 +33,7 @@ use codex_http_client::RouteAwareClientPool;
use codex_login::auth::AgentIdentityAuthPolicy;
use codex_model_provider::SharedModelProvider;
use codex_prompts::render_model_instructions;
use codex_protocol::ContextWindowId;
use codex_protocol::ItemIdGenerator;
use codex_protocol::SessionId;
use codex_protocol::capabilities::SelectedCapabilityRoot;
@@ -815,7 +816,7 @@ impl Session {
&self,
turn_context: &TurnContext,
compaction_metadata: CompactionTurnMetadata,
(window_id, window_number, context_window_id): (String, u64, uuid::Uuid),
(window_id, window_number, context_window_id): (String, u64, ContextWindowId),
) -> CodexResponsesMetadata {
let responses_metadata = turn_context.turn_metadata_state.to_responses_metadata(
self.installation_id.clone(),
@@ -835,7 +836,7 @@ impl Session {
turn_context: &TurnContext,
responses_metadata: CodexResponsesMetadata,
window_number: u64,
context_window_id: uuid::Uuid,
context_window_id: ContextWindowId,
) -> CodexResponsesMetadata {
CodexResponsesMetadata {
history_initialization: Some(self.history_initialization),
@@ -1631,7 +1632,7 @@ impl Session {
session_configuration.thread_name = thread_name.clone();
let mut state = SessionState::new_with_auto_compact_window_ids(
session_configuration.clone(),
initial_auto_compact_window_ids,
initial_auto_compact_window_ids.clone(),
ContextManager::for_session(
session_configuration.root_capabilities_restricted,
&config.features,
+8 -87
View File
@@ -1,4 +1,8 @@
use crate::agent::LocalAgentControl;
use codex_protocol::ContextWindowId;
#[path = "compaction_replay_tests.rs"]
mod compaction_replay_tests;
#[path = "notification_tests.rs"]
mod notification_tests;
@@ -2465,89 +2469,6 @@ fn collect_explicit_app_ids_from_skill_items_skips_plain_mentions_with_skill_con
assert_eq!(connector_ids, HashSet::<String>::new());
}
#[tokio::test]
async fn reconstruct_history_matches_live_compactions() {
let (session, turn_context) = make_session_and_context().await;
let (rollout_items, expected) = sample_rollout(&session, &turn_context).await;
let reconstruction_turn = session.new_default_turn().await;
let reconstructed = session
.reconstruct_history_from_rollout(reconstruction_turn.as_ref(), &rollout_items)
.await;
assert_eq!(expected, raw_envelopes(&reconstructed.history));
assert_eq!(2, reconstructed.window_number);
assert_eq!(
reconstructed
.window_id
.map(|window_id| window_id.get_version_num()),
Some(7)
);
}
#[tokio::test]
async fn reconstruct_history_uses_replacement_history_verbatim() {
let (session, turn_context) = make_session_and_context().await;
let summary_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None,
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: "summary".to_string(),
}],
phase: None,
internal_chat_message_metadata_passthrough: Some(InternalChatMessageMetadataPassthrough {
turn_id: Some("compact-turn".to_string()),
..Default::default()
}),
};
let replacement_history = vec![
ResponseItemEnvelope {
item: summary_item.clone(),
metadata: Some(CodexHarnessMetadata::default()),
},
ResponseItemEnvelope::new(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None,
role: "developer".to_string(),
content: vec![ContentItem::InputText {
text: "stale developer instructions".to_string(),
}],
phase: None,
internal_chat_message_metadata_passthrough: None,
}),
];
let first_window_id = Uuid::now_v7();
let previous_window_id = Uuid::now_v7();
let window_id = Uuid::now_v7();
let rollout_items = vec![RolloutItem::Compacted(Box::new(CompactedItem {
message: String::new(),
replacement_history: Some(replacement_history.clone()),
retained_context: None,
guardian_history: None,
mcp_resource_origins: None,
window_number: Some(42),
first_window_id: Some(first_window_id.to_string()),
previous_window_id: Some(previous_window_id.to_string()),
window_id: Some(window_id.to_string()),
compaction_response_id: None,
latest_token_usage_record: None,
resume_metadata: None,
}))];
let reconstructed = session
.reconstruct_history_from_rollout(&turn_context, &rollout_items)
.await;
assert_eq!(reconstructed.history, replacement_history);
assert_eq!(42, reconstructed.window_number);
assert_eq!(Some(first_window_id), reconstructed.first_window_id);
assert_eq!(Some(previous_window_id), reconstructed.previous_window_id);
assert_eq!(Some(window_id), reconstructed.window_id);
}
#[tokio::test]
async fn record_initial_history_reconstructs_resumed_transcript() {
let (session, turn_context) = make_session_and_context().await;
@@ -5075,7 +4996,7 @@ async fn open_thread_persistence(session: &mut Session) -> PathBuf {
history_mode: Default::default(),
subagent_history_start_ordinal: None,
history_base: None,
initial_window_id: Uuid::now_v7().to_string(),
initial_window_id: ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: Some(config.cwd.to_path_buf()),
@@ -8867,7 +8788,7 @@ async fn shutdown_complete_does_not_append_to_thread_store_after_shutdown() {
history_mode: Default::default(),
subagent_history_start_ordinal: None,
history_base: None,
initial_window_id: Uuid::now_v7().to_string(),
initial_window_id: ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: Some(config.cwd.to_path_buf()),
@@ -9006,7 +8927,7 @@ async fn submission_loop_channel_close_runs_full_thread_teardown() -> anyhow::Re
history_mode: Default::default(),
subagent_history_start_ordinal: None,
history_base: None,
initial_window_id: Uuid::now_v7().to_string(),
initial_window_id: ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: Some(config.cwd.to_path_buf()),
@@ -12008,7 +11929,7 @@ async fn attach_in_memory_thread_store(
history_mode: Default::default(),
subagent_history_start_ordinal: None,
history_base: None,
initial_window_id: Uuid::now_v7().to_string(),
initial_window_id: ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: Some(config.cwd.to_path_buf()),
+2 -2
View File
@@ -41,7 +41,7 @@ pub(super) fn apply_persistent_defaults(config: &mut Config) {
pub(crate) struct CurrentTimeReminderState {
last_delivery_time: Option<DateTime<Utc>>,
last_window_id: Option<String>,
last_clock_failure: Option<(String, uuid::Uuid)>,
last_clock_failure: Option<(String, codex_protocol::ContextWindowId)>,
pending_user_or_tool_output_boundary: bool,
}
@@ -134,7 +134,7 @@ impl Session {
let mut state = self.state.lock().await;
let failure = (
turn_context.sub_id.clone(),
state.auto_compact_window_ids().window_id,
state.auto_compact_window_ids().window_id.clone(),
);
// A compacted window may no longer contain the earlier notice.
if state.current_time_reminder.last_clock_failure.as_ref() == Some(&failure) {
+1 -1
View File
@@ -195,7 +195,7 @@ impl Session {
.resolved_context_window()
.is_some();
if token_budget_enabled {
let window_ids = self.state.lock().await.auto_compact_window_ids();
let window_ids = self.state.lock().await.auto_compact_window_ids().clone();
world_state.add_section(TokenBudgetContext::new(
turn_context
.session_source
+37 -25
View File
@@ -1,18 +1,18 @@
use codex_protocol::ContextWindowId;
use codex_protocol::protocol::TokenUsage;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct AutoCompactWindowIds {
pub(crate) first_window_id: Uuid,
pub(crate) previous_window_id: Option<Uuid>,
pub(crate) window_id: Uuid,
pub(crate) first_window_id: ContextWindowId,
pub(crate) previous_window_id: Option<ContextWindowId>,
pub(crate) window_id: ContextWindowId,
}
impl AutoCompactWindowIds {
pub(crate) fn new_initial() -> Self {
let window_id = Uuid::now_v7();
let window_id = ContextWindowId::new_uuid_v7();
Self {
first_window_id: window_id,
first_window_id: window_id.clone(),
previous_window_id: None,
window_id,
}
@@ -30,7 +30,7 @@ enum AutoCompactWindowPrefill {
Estimated(i64),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct AutoCompactWindow {
window_number: u64,
ids: AutoCompactWindowIds,
@@ -65,8 +65,8 @@ impl AutoCompactWindow {
self.window_number
}
pub(super) fn ids(&self) -> AutoCompactWindowIds {
self.ids
pub(super) fn ids(&self) -> &AutoCompactWindowIds {
&self.ids
}
pub(super) fn restore(&mut self, window_number: u64, ids: AutoCompactWindowIds) {
@@ -76,12 +76,14 @@ impl AutoCompactWindow {
pub(super) fn advance(&mut self) -> (u64, AutoCompactWindowIds) {
self.window_number = self.window_number.saturating_add(1);
self.ids.previous_window_id = Some(self.ids.window_id);
self.ids.window_id = Uuid::now_v7();
self.ids.previous_window_id = Some(std::mem::replace(
&mut self.ids.window_id,
ContextWindowId::new_uuid_v7(),
));
self.new_context_window_requested = false;
self.token_budget_reminder_delivered = false;
self.auto_compact_fallback_delivered = false;
(self.window_number, self.ids)
(self.window_number, self.ids.clone())
}
pub(super) fn claim_token_budget_reminder(&mut self) -> bool {
@@ -151,25 +153,30 @@ mod tests {
let mut window = AutoCompactWindow::new_with_ids(AutoCompactWindowIds::new_initial());
assert_eq!(window.window_number(), 0);
let initial_window_id = window.ids().window_id;
assert_eq!(initial_window_id.get_version_num(), 7);
let initial_window_id = window.ids().window_id.clone();
assert_eq!(
uuid::Uuid::parse_str(&initial_window_id.to_string())
.unwrap()
.get_version_num(),
7
);
assert_eq!(
window.ids(),
AutoCompactWindowIds {
first_window_id: initial_window_id,
&AutoCompactWindowIds {
first_window_id: initial_window_id.clone(),
previous_window_id: None,
window_id: initial_window_id,
window_id: initial_window_id.clone(),
}
);
let first_window_id = initial_window_id;
let restored_window_id = Uuid::now_v7();
let restored_previous_window_id = Uuid::now_v7();
let restored_window_id = ContextWindowId::new_uuid_v7();
let restored_previous_window_id = ContextWindowId::new_uuid_v7();
window.restore(
/*window_number*/ 3,
AutoCompactWindowIds {
first_window_id,
first_window_id: first_window_id.clone(),
previous_window_id: Some(restored_previous_window_id),
window_id: restored_window_id,
window_id: restored_window_id.clone(),
},
);
assert_eq!(window.window_number(), 3);
@@ -185,10 +192,15 @@ mod tests {
let (window_number, ids) = window.advance();
assert_eq!(window_number, 4);
assert_eq!(window.window_number(), 4);
assert_eq!(window.ids(), ids);
assert_eq!(window.ids(), &ids);
assert_eq!(ids.first_window_id, first_window_id);
assert_eq!(ids.previous_window_id, Some(restored_window_id));
assert_eq!(ids.window_id.get_version_num(), 7);
assert_eq!(ids.previous_window_id, Some(restored_window_id.clone()));
assert_eq!(
uuid::Uuid::parse_str(&ids.window_id.to_string())
.unwrap()
.get_version_num(),
7
);
assert_ne!(ids.window_id, restored_window_id);
assert!(!window.take_new_context_window_request());
assert!(window.claim_token_budget_reminder());
+1 -1
View File
@@ -337,7 +337,7 @@ impl SessionState {
self.auto_compact_window.window_number()
}
pub(crate) fn auto_compact_window_ids(&self) -> AutoCompactWindowIds {
pub(crate) fn auto_compact_window_ids(&self) -> &AutoCompactWindowIds {
self.auto_compact_window.ids()
}
@@ -0,0 +1,30 @@
//! Context window identity used for compaction, rollout restoration, and request metadata.
use serde::Serialize;
use std::fmt;
use uuid::Uuid;
#[derive(derive_more::Debug, Clone, PartialEq, Eq, Serialize)]
#[debug("{_0}")]
#[serde(transparent)]
pub struct ContextWindowId(String);
impl ContextWindowId {
pub fn new_uuid_v7() -> Self {
Self(Uuid::now_v7().to_string())
}
/// Reads a persisted window ID, ignoring IDs outside the supported format.
pub fn parse(value: &str) -> Option<Self> {
Uuid::parse_str(value)
.ok()
.filter(|uuid| uuid.get_version_num() == 7)
.map(|uuid| Self(uuid.to_string()))
}
}
impl fmt::Display for ContextWindowId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(formatter)
}
}
+2
View File
@@ -1,6 +1,7 @@
pub mod account;
mod agent_path;
pub mod auth;
mod context_window_id;
mod item_id_generator;
mod response_item_id;
mod response_usage;
@@ -9,6 +10,7 @@ mod session_id;
mod thread_id;
mod tool_name;
pub use agent_path::AgentPath;
pub use context_window_id::ContextWindowId;
pub use item_id_generator::ItemIdGenerator;
#[cfg(test)]
use item_id_generator::uuid_v7_item_id_generator_for_test;
+1 -1
View File
@@ -698,7 +698,7 @@ async fn recorder_materializes_on_flush_with_pending_items() -> std::io::Result<
let config = test_config(home.path());
let session_id = SessionId::default();
let thread_id = ThreadId::new();
let initial_window_id = Uuid::now_v7().to_string();
let initial_window_id = codex_protocol::ContextWindowId::new_uuid_v7().to_string();
let recorder = RolloutRecorder::new(
&config,
RolloutRecorderParams::new(
+3 -2
View File
@@ -64,6 +64,7 @@ mod tests {
use crate::StoredTurnItemsView;
use crate::ThreadPersistenceMetadata;
use crate::ThreadSortKey;
use codex_protocol::ContextWindowId;
use codex_protocol::models::BaseInstructions;
use codex_protocol::protocol::SessionSource;
@@ -184,7 +185,7 @@ mod tests {
history_mode: ThreadHistoryMode::Legacy,
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: uuid::Uuid::now_v7().to_string(),
initial_window_id: ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: None,
@@ -461,7 +462,7 @@ mod tests {
history_mode,
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: uuid::Uuid::now_v7().to_string(),
initial_window_id: ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: thread_metadata(),
}
+1 -1
View File
@@ -2393,7 +2393,7 @@ mod tests {
history_mode: ThreadHistoryMode::Legacy,
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: uuid::Uuid::now_v7().to_string(),
initial_window_id: codex_protocol::ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: thread_metadata(),
}
@@ -600,7 +600,7 @@ fn create_thread_params(thread_id: ThreadId) -> CreateThreadParams {
history_mode: ThreadHistoryMode::Legacy,
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: uuid::Uuid::now_v7().to_string(),
initial_window_id: codex_protocol::ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: Some(std::env::current_dir().expect("cwd")),
@@ -504,7 +504,7 @@ mod tests {
history_mode: ThreadHistoryMode::Legacy,
history_base: None,
subagent_history_start_ordinal: None,
initial_window_id: uuid::Uuid::now_v7().to_string(),
initial_window_id: codex_protocol::ContextWindowId::new_uuid_v7().to_string(),
runtime_workspace_roots: None,
metadata: ThreadPersistenceMetadata {
cwd: None,