diff --git a/codex-rs/app-server/tests/common/rollout.rs b/codex-rs/app-server/tests/common/rollout.rs index af02de8ea05a..7fae7f973584 100644 --- a/codex-rs/app-server/tests/common/rollout.rs +++ b/codex-rs/app-server/tests/common/rollout.rs @@ -242,6 +242,7 @@ fn create_fake_rollout_with_source_and_parent_thread_id( selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, @@ -333,6 +334,7 @@ pub fn create_fake_rollout_with_text_elements( selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index 020a14d3b068..f12fd89e5009 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -2588,6 +2588,7 @@ stream_max_retries = 0 selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index 39d07836ffe4..0ea4a9db9a65 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -375,6 +375,7 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 621b6ec40307..b6c407eac6cd 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -3049,6 +3049,16 @@ impl SessionContextWindow { } } +/// Exclusive position in another thread's paginated rollout history. +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, JsonSchema, TS)] +pub struct HistoryPosition { + pub thread_id: ThreadId, + /// First rollout ordinal not included from the prefix file. + pub end_ordinal_exclusive: u64, + /// Byte offset immediately after the last included JSONL record from the prefix file. + pub end_byte_offset: u64, +} + /// SessionMeta contains session-level data that doesn't correspond to a specific turn. /// /// NOTE: There used to be an `instructions` field here, which stored user_instructions, but we @@ -3098,6 +3108,9 @@ pub struct SessionMeta { pub memory_mode: Option, #[serde(default)] pub history_mode: ThreadHistoryMode, + /// Exclusive prefix of another paginated rollout inherited by this thread. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub history_base: Option, /// First rollout ordinal that belongs to this subagent's own projected history. /// /// Earlier rollout records are inherited model context and stay out of child @@ -3134,6 +3147,7 @@ impl Default for SessionMeta { selected_capability_roots: Vec::new(), memory_mode: None, history_mode: ThreadHistoryMode::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, @@ -5854,6 +5868,7 @@ mod tests { }))?; assert_eq!(session_meta.history_mode, ThreadHistoryMode::Legacy); + assert_eq!(session_meta.history_base, None); let serialized = serde_json::to_value(&session_meta)?; assert_eq!(serialized["history_mode"], json!("legacy")); let mut unknown = serialized; diff --git a/codex-rs/rollout/src/compression_tests.rs b/codex-rs/rollout/src/compression_tests.rs index 6b2f8d328f53..2c2688ff30f1 100644 --- a/codex-rs/rollout/src/compression_tests.rs +++ b/codex-rs/rollout/src/compression_tests.rs @@ -606,6 +606,7 @@ fn write_rollout(path: &std::path::Path, thread_id: ThreadId, message: &str) -> selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, diff --git a/codex-rs/rollout/src/metadata_tests.rs b/codex-rs/rollout/src/metadata_tests.rs index 2399a92fd2aa..bea534bf0c78 100644 --- a/codex-rs/rollout/src/metadata_tests.rs +++ b/codex-rs/rollout/src/metadata_tests.rs @@ -53,6 +53,7 @@ async fn extract_metadata_from_rollout_uses_session_meta() { selected_capability_roots: Vec::new(), memory_mode: None, history_mode: ThreadHistoryMode::Paginated, + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, @@ -150,6 +151,7 @@ async fn extract_metadata_from_rollout_returns_latest_memory_mode() { selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, @@ -424,6 +426,7 @@ fn write_rollout_in_sessions_with_cwd( selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, diff --git a/codex-rs/rollout/src/recorder.rs b/codex-rs/rollout/src/recorder.rs index 40e03e4cb425..c8db914f9fc1 100644 --- a/codex-rs/rollout/src/recorder.rs +++ b/codex-rs/rollout/src/recorder.rs @@ -831,6 +831,7 @@ impl RolloutRecorder { selected_capability_roots, memory_mode: (!config.generate_memories()).then_some("disabled".to_string()), history_mode, + history_base: None, subagent_history_start_ordinal, multi_agent_version, context_window: initial_window_id.map(SessionContextWindow::new), diff --git a/codex-rs/rollout/src/recorder_tests.rs b/codex-rs/rollout/src/recorder_tests.rs index 855215ec865e..0eba013d5b99 100644 --- a/codex-rs/rollout/src/recorder_tests.rs +++ b/codex-rs/rollout/src/recorder_tests.rs @@ -178,6 +178,7 @@ async fn state_db_init_backfills_before_returning() -> anyhow::Result<()> { selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, diff --git a/codex-rs/rollout/src/session_index_tests.rs b/codex-rs/rollout/src/session_index_tests.rs index 6498c20d3cd8..dee363f73cc8 100644 --- a/codex-rs/rollout/src/session_index_tests.rs +++ b/codex-rs/rollout/src/session_index_tests.rs @@ -45,6 +45,7 @@ fn write_rollout_with_metadata(path: &Path, thread_id: ThreadId) -> std::io::Res selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, diff --git a/codex-rs/rollout/src/state_db_tests.rs b/codex-rs/rollout/src/state_db_tests.rs index 60fbe4589672..33eae943ba5c 100644 --- a/codex-rs/rollout/src/state_db_tests.rs +++ b/codex-rs/rollout/src/state_db_tests.rs @@ -178,6 +178,7 @@ fn write_rollout_with_user_message( selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, diff --git a/codex-rs/rollout/src/tests.rs b/codex-rs/rollout/src/tests.rs index 8e013309416c..0dbbbc8a90b2 100644 --- a/codex-rs/rollout/src/tests.rs +++ b/codex-rs/rollout/src/tests.rs @@ -1336,6 +1336,7 @@ async fn test_updated_at_uses_file_mtime() -> Result<()> { selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index fe5cfdf80e58..796852280862 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -381,6 +381,7 @@ mod tests { selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, @@ -629,6 +630,7 @@ mod tests { selected_capability_roots: Vec::new(), memory_mode: None, history_mode: ThreadHistoryMode::Legacy, + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, diff --git a/codex-rs/state/src/runtime/threads.rs b/codex-rs/state/src/runtime/threads.rs index 4780c0451b30..bdefa53c0433 100644 --- a/codex-rs/state/src/runtime/threads.rs +++ b/codex-rs/state/src/runtime/threads.rs @@ -2183,6 +2183,7 @@ mod tests { selected_capability_roots: Vec::new(), memory_mode: Some("polluted".to_string()), history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, @@ -2249,6 +2250,7 @@ mod tests { selected_capability_roots: Vec::new(), memory_mode: None, history_mode: Default::default(), + history_base: None, subagent_history_start_ordinal: None, multi_agent_version: None, context_window: None, diff --git a/codex-rs/thread-store/src/in_memory.rs b/codex-rs/thread-store/src/in_memory.rs index 890e59930eab..6ae8e7d8ca3c 100644 --- a/codex-rs/thread-store/src/in_memory.rs +++ b/codex-rs/thread-store/src/in_memory.rs @@ -443,6 +443,7 @@ impl InMemoryThreadStore { memory_mode: matches!(params.metadata.memory_mode, ThreadMemoryMode::Disabled) .then_some("disabled".to_string()), history_mode: params.history_mode, + history_base: None, subagent_history_start_ordinal: params.subagent_history_start_ordinal, multi_agent_version: params.multi_agent_version, context_window: Some(SessionContextWindow::new(params.initial_window_id.clone())),