mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
core: persist initial context window metadata (#29519)
## Why PR #29494 made context-window IDs visible to the model by wrapping the token-budget window payload in `<context_window>`, but rollout JSONL consumers still could not see the initial window identity by tailing the session file. Compacted rollout items carry window IDs only after compaction has happened, so a session with no compaction had no durable JSONL record for window 0. This change gives tailing consumers a stable initial-window record at session creation time. ## What Changed - Added `session_meta.context_window.window_id` for the initial context-window identity. - `CreateThreadParams` now requires `initial_window_id: String`, so thread-store callers cannot accidentally create new threads without window-0 metadata. - Live thread creation derives the persisted initial window ID from the same `AutoCompactWindowIds` used to initialize `SessionState`, keeping runtime state and JSONL metadata aligned. - Rollout reconstruction uses `session_meta.context_window.window_id` as the initial-window fallback and derives `window_number = 0`, `first_window_id = window_id`, and `previous_window_id = None` internally. - Fork reconstruction intentionally uses the same rollout reconstruction path; consumers that need to distinguish copied initial-window metadata can use the rollout `thread_id`. - Legacy compactions without `window_number` still use compaction-count fallback accounting instead of being reset to window 0 by the initial-window fallback. - Compacted rollout metadata still takes precedence once compaction records exist, preserving the richer chain fields there. ## JSONL Shape Real rollout JSONL is one object per line. This example is expanded for readability, but shows the new initial `session_meta.context_window` record followed by the existing compacted rollout item shape that also carries window IDs: ```jsonl { "timestamp": "2026-06-22T12:00:00.000Z", "type": "session_meta", "payload": { "session_id": "<THREAD_ID>", "id": "<THREAD_ID>", "timestamp": "2026-06-22T12:00:00.000Z", "cwd": "/repo", "originator": "codex", "cli_version": "0.0.0", "source": "cli", "model_provider": "<MODEL_PROVIDER>", "context_window": { "window_id": "<INITIAL_WINDOW_ID>" } } } ... { "timestamp": "2026-06-22T12:34:56.000Z", "type": "compacted", "payload": { "message": "<COMPACTION_SUMMARY>", "replacement_history": [ "..." ], "window_number": 1, "first_window_id": "<INITIAL_WINDOW_ID>", "previous_window_id": "<INITIAL_WINDOW_ID>", "window_id": "<NEXT_WINDOW_ID>" } } ``` The nested `context_window` object is intentional: it gives rollout consumers a stable namespace for context-window metadata while only writing the non-derivable initial `window_id`. For the initial window, `window_number`, `first_window_id`, and `previous_window_id` are derived internally instead of being written to the rollout. ## Verification - `just test -p codex-protocol` - `just test -p codex-rollout recorder_materializes_on_flush_with_pending_items` - `just test -p codex-core reconstruct_history` - `just test -p codex-core record_initial_history_reconstructs_forked_transcript` - `just test -p codex-thread-store` - `just test -p codex-state` - `just test -p codex-app-server thread_read_returns_summary_without_turns` - `just test -p codex-rollout persistence_metrics`
This commit is contained in:
1 parent
c26f961b85
commit
01f89c8c59
30 files changed
+254
-13
No files matched your search
@@ -215,6 +215,7 @@ impl ExternalAgentSessionImporter {
|
|||||||
},
|
},
|
||||||
dynamic_tools: Vec::new(),
|
dynamic_tools: Vec::new(),
|
||||||
multi_agent_version: Some(MultiAgentVersion::V1),
|
multi_agent_version: Some(MultiAgentVersion::V1),
|
||||||
|
initial_window_id: uuid::Uuid::now_v7().to_string(),
|
||||||
metadata: ThreadPersistenceMetadata {
|
metadata: ThreadPersistenceMetadata {
|
||||||
cwd: Some(cwd.clone()),
|
cwd: Some(cwd.clone()),
|
||||||
model_provider: model_provider.clone(),
|
model_provider: model_provider.clone(),
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ fn create_fake_rollout_with_source_and_parent_thread_id(
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
};
|
};
|
||||||
let payload = serde_json::to_value(SessionMetaLine {
|
let payload = serde_json::to_value(SessionMetaLine {
|
||||||
meta,
|
meta,
|
||||||
@@ -289,6 +290,7 @@ pub fn create_fake_rollout_with_text_elements(
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
};
|
};
|
||||||
let payload = serde_json::to_value(SessionMetaLine {
|
let payload = serde_json::to_value(SessionMetaLine {
|
||||||
meta,
|
meta,
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ async fn get_conversation_summary_by_thread_id_reads_pathless_store_thread() ->
|
|||||||
base_instructions: BaseInstructions::default(),
|
base_instructions: BaseInstructions::default(),
|
||||||
dynamic_tools: Vec::new(),
|
dynamic_tools: Vec::new(),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
initial_window_id: Uuid::now_v7().to_string(),
|
||||||
metadata: ThreadPersistenceMetadata {
|
metadata: ThreadPersistenceMetadata {
|
||||||
cwd: None,
|
cwd: None,
|
||||||
model_provider: "test-provider".to_string(),
|
model_provider: "test-provider".to_string(),
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ async fn thread_delete_with_non_local_thread_store_does_not_create_local_persist
|
|||||||
base_instructions: BaseInstructions::default(),
|
base_instructions: BaseInstructions::default(),
|
||||||
dynamic_tools: Vec::new(),
|
dynamic_tools: Vec::new(),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
initial_window_id: Uuid::now_v7().to_string(),
|
||||||
metadata: ThreadPersistenceMetadata {
|
metadata: ThreadPersistenceMetadata {
|
||||||
cwd: Some(codex_home.path().to_path_buf()),
|
cwd: Some(codex_home.path().to_path_buf()),
|
||||||
model_provider: "mock_provider".to_string(),
|
model_provider: "mock_provider".to_string(),
|
||||||
|
|||||||
@@ -1367,6 +1367,7 @@ async fn seed_pathless_store_thread(
|
|||||||
base_instructions: BaseInstructions::default(),
|
base_instructions: BaseInstructions::default(),
|
||||||
dynamic_tools: Vec::new(),
|
dynamic_tools: Vec::new(),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
initial_window_id: Uuid::now_v7().to_string(),
|
||||||
metadata: ThreadPersistenceMetadata {
|
metadata: ThreadPersistenceMetadata {
|
||||||
cwd: None,
|
cwd: None,
|
||||||
model_provider: "test-provider".to_string(),
|
model_provider: "test-provider".to_string(),
|
||||||
|
|||||||
@@ -2072,6 +2072,7 @@ stream_max_retries = 0
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
};
|
};
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
&rollout_path,
|
&rollout_path,
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ async fn thread_unarchive_preserves_pathless_store_metadata() -> Result<()> {
|
|||||||
base_instructions: BaseInstructions::default(),
|
base_instructions: BaseInstructions::default(),
|
||||||
dynamic_tools: Vec::new(),
|
dynamic_tools: Vec::new(),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
initial_window_id: Uuid::now_v7().to_string(),
|
||||||
metadata: ThreadPersistenceMetadata {
|
metadata: ThreadPersistenceMetadata {
|
||||||
cwd: None,
|
cwd: None,
|
||||||
model_provider: "test-provider".to_string(),
|
model_provider: "test-provider".to_string(),
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::R
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: None,
|
git: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::context_manager::is_user_turn_boundary;
|
use crate::context_manager::is_user_turn_boundary;
|
||||||
|
use codex_protocol::protocol::SessionContextWindow;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
// Return value of `Session::reconstruct_history_from_rollout`, bundling the rebuilt history with
|
// Return value of `Session::reconstruct_history_from_rollout`, bundling the rebuilt history with
|
||||||
@@ -113,6 +114,22 @@ impl Session {
|
|||||||
// stopping once a surviving replacement-history checkpoint and the required resume metadata
|
// stopping once a surviving replacement-history checkpoint and the required resume metadata
|
||||||
// are both known; then replay only the buffered surviving tail forward to preserve exact
|
// are both known; then replay only the buffered surviving tail forward to preserve exact
|
||||||
// history semantics.
|
// history semantics.
|
||||||
|
let has_legacy_compaction_without_window_number =
|
||||||
|
rollout_items.iter().any(|item| {
|
||||||
|
matches!(item, RolloutItem::Compacted(compacted) if compacted.window_number.is_none())
|
||||||
|
});
|
||||||
|
let initial_window = if has_legacy_compaction_without_window_number {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
rollout_items.iter().find_map(|item| match item {
|
||||||
|
RolloutItem::SessionMeta(session_meta) => session_meta
|
||||||
|
.meta
|
||||||
|
.context_window
|
||||||
|
.as_ref()
|
||||||
|
.and_then(reconstructed_window_from_session_context_window),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
};
|
||||||
let mut base_replacement_history: Option<&[ResponseItem]> = None;
|
let mut base_replacement_history: Option<&[ResponseItem]> = None;
|
||||||
let mut previous_turn_settings = None;
|
let mut previous_turn_settings = None;
|
||||||
let mut reference_context_item = TurnReferenceContextItem::NeverSet;
|
let mut reference_context_item = TurnReferenceContextItem::NeverSet;
|
||||||
@@ -348,7 +365,7 @@ impl Session {
|
|||||||
reference_context_item
|
reference_context_item
|
||||||
};
|
};
|
||||||
|
|
||||||
let window = window.unwrap_or(ReconstructedWindow {
|
let window = window.or(initial_window).unwrap_or(ReconstructedWindow {
|
||||||
number: fallback_window_number,
|
number: fallback_window_number,
|
||||||
first_id: None,
|
first_id: None,
|
||||||
previous_id: None,
|
previous_id: None,
|
||||||
@@ -371,3 +388,15 @@ fn parse_uuid_v7(value: &str) -> Option<Uuid> {
|
|||||||
.ok()
|
.ok()
|
||||||
.filter(|uuid| uuid.get_version_num() == 7)
|
.filter(|uuid| uuid.get_version_num() == 7)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reconstructed_window_from_session_context_window(
|
||||||
|
context_window: &SessionContextWindow,
|
||||||
|
) -> Option<ReconstructedWindow> {
|
||||||
|
let id = parse_uuid_v7(&context_window.window_id)?;
|
||||||
|
Some(ReconstructedWindow {
|
||||||
|
number: 0,
|
||||||
|
first_id: Some(id),
|
||||||
|
previous_id: None,
|
||||||
|
id: Some(id),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -9,8 +9,12 @@ use codex_protocol::protocol::CompactedItem;
|
|||||||
use codex_protocol::protocol::InitialHistory;
|
use codex_protocol::protocol::InitialHistory;
|
||||||
use codex_protocol::protocol::InterAgentCommunication;
|
use codex_protocol::protocol::InterAgentCommunication;
|
||||||
use codex_protocol::protocol::ResumedHistory;
|
use codex_protocol::protocol::ResumedHistory;
|
||||||
|
use codex_protocol::protocol::SessionContextWindow;
|
||||||
|
use codex_protocol::protocol::SessionMeta;
|
||||||
|
use codex_protocol::protocol::SessionMetaLine;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
fn user_message(text: &str) -> ResponseItem {
|
fn user_message(text: &str) -> ResponseItem {
|
||||||
ResponseItem::Message {
|
ResponseItem::Message {
|
||||||
@@ -909,6 +913,116 @@ async fn record_initial_history_resumed_does_not_seed_reference_context_item_aft
|
|||||||
assert!(session.reference_context_item().await.is_none());
|
assert!(session.reference_context_item().await.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
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 rollout_items = vec![RolloutItem::SessionMeta(SessionMetaLine {
|
||||||
|
meta: SessionMeta {
|
||||||
|
session_id: thread_id.into(),
|
||||||
|
id: thread_id,
|
||||||
|
context_window: Some(SessionContextWindow {
|
||||||
|
window_id: initial_window_id.to_string(),
|
||||||
|
}),
|
||||||
|
..SessionMeta::default()
|
||||||
|
},
|
||||||
|
git: None,
|
||||||
|
})];
|
||||||
|
|
||||||
|
let reconstructed = session
|
||||||
|
.reconstruct_history_from_rollout(&turn_context, &rollout_items)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(reconstructed.window_number, 0);
|
||||||
|
assert_eq!(reconstructed.first_window_id, Some(initial_window_id));
|
||||||
|
assert_eq!(reconstructed.previous_window_id, None);
|
||||||
|
assert_eq!(reconstructed.window_id, Some(initial_window_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
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 rollout_items = vec![
|
||||||
|
RolloutItem::SessionMeta(SessionMetaLine {
|
||||||
|
meta: SessionMeta {
|
||||||
|
session_id: thread_id.into(),
|
||||||
|
id: thread_id,
|
||||||
|
context_window: Some(SessionContextWindow {
|
||||||
|
window_id: initial_window_id.to_string(),
|
||||||
|
}),
|
||||||
|
..SessionMeta::default()
|
||||||
|
},
|
||||||
|
git: None,
|
||||||
|
}),
|
||||||
|
RolloutItem::Compacted(CompactedItem {
|
||||||
|
message: String::new(),
|
||||||
|
replacement_history: Some(Vec::new()),
|
||||||
|
window_number: Some(2),
|
||||||
|
first_window_id: Some(compacted_first_window_id.to_string()),
|
||||||
|
previous_window_id: Some(compacted_previous_window_id.to_string()),
|
||||||
|
window_id: Some(compacted_window_id.to_string()),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
let reconstructed = session
|
||||||
|
.reconstruct_history_from_rollout(&turn_context, &rollout_items)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(reconstructed.window_number, 2);
|
||||||
|
assert_eq!(
|
||||||
|
reconstructed.first_window_id,
|
||||||
|
Some(compacted_first_window_id)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
reconstructed.previous_window_id,
|
||||||
|
Some(compacted_previous_window_id)
|
||||||
|
);
|
||||||
|
assert_eq!(reconstructed.window_id, Some(compacted_window_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
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 rollout_items = vec![
|
||||||
|
RolloutItem::SessionMeta(SessionMetaLine {
|
||||||
|
meta: SessionMeta {
|
||||||
|
session_id: thread_id.into(),
|
||||||
|
id: thread_id,
|
||||||
|
context_window: Some(SessionContextWindow {
|
||||||
|
window_id: initial_window_id.to_string(),
|
||||||
|
}),
|
||||||
|
..SessionMeta::default()
|
||||||
|
},
|
||||||
|
git: None,
|
||||||
|
}),
|
||||||
|
RolloutItem::Compacted(CompactedItem {
|
||||||
|
message: "legacy summary".to_string(),
|
||||||
|
replacement_history: None,
|
||||||
|
window_number: None,
|
||||||
|
first_window_id: None,
|
||||||
|
previous_window_id: None,
|
||||||
|
window_id: None,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
let reconstructed = session
|
||||||
|
.reconstruct_history_from_rollout(&turn_context, &rollout_items)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(reconstructed.window_number, 1);
|
||||||
|
assert_eq!(reconstructed.first_window_id, None);
|
||||||
|
assert_eq!(reconstructed.previous_window_id, None);
|
||||||
|
assert_eq!(reconstructed.window_id, None);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn reconstruct_history_legacy_compaction_without_replacement_history_does_not_inject_current_initial_context()
|
async fn reconstruct_history_legacy_compaction_without_replacement_history_does_not_inject_current_initial_context()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -546,6 +546,7 @@ impl Session {
|
|||||||
SessionId::from(thread_id)
|
SessionId::from(thread_id)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let initial_auto_compact_window_ids = AutoCompactWindowIds::new_initial();
|
||||||
let agent_control = agent_control.with_session_id(
|
let agent_control = agent_control.with_session_id(
|
||||||
session_id,
|
session_id,
|
||||||
config
|
config
|
||||||
@@ -585,6 +586,9 @@ impl Session {
|
|||||||
},
|
},
|
||||||
dynamic_tools: session_configuration.dynamic_tools.clone(),
|
dynamic_tools: session_configuration.dynamic_tools.clone(),
|
||||||
multi_agent_version: initial_multi_agent_version,
|
multi_agent_version: initial_multi_agent_version,
|
||||||
|
initial_window_id: initial_auto_compact_window_ids
|
||||||
|
.window_id
|
||||||
|
.to_string(),
|
||||||
metadata: ThreadPersistenceMetadata {
|
metadata: ThreadPersistenceMetadata {
|
||||||
cwd: Some(config.cwd.to_path_buf()),
|
cwd: Some(config.cwd.to_path_buf()),
|
||||||
model_provider: config.model_provider_id.clone(),
|
model_provider: config.model_provider_id.clone(),
|
||||||
@@ -889,7 +893,10 @@ impl Session {
|
|||||||
session_configuration.thread_name = thread_name.clone();
|
session_configuration.thread_name = thread_name.clone();
|
||||||
validate_config_lock_if_configured(&session_configuration).await?;
|
validate_config_lock_if_configured(&session_configuration).await?;
|
||||||
export_config_lock_if_configured(&session_configuration, thread_id).await?;
|
export_config_lock_if_configured(&session_configuration, thread_id).await?;
|
||||||
let state = SessionState::new(session_configuration.clone());
|
let state = SessionState::new_with_auto_compact_window_ids(
|
||||||
|
session_configuration.clone(),
|
||||||
|
initial_auto_compact_window_ids,
|
||||||
|
);
|
||||||
let managed_network_requirements_configured = config
|
let managed_network_requirements_configured = config
|
||||||
.config_layer_stack
|
.config_layer_stack
|
||||||
.requirements_toml()
|
.requirements_toml()
|
||||||
|
|||||||
@@ -3844,6 +3844,7 @@ async fn attach_thread_persistence(session: &mut Session) -> PathBuf {
|
|||||||
base_instructions: BaseInstructions::default(),
|
base_instructions: BaseInstructions::default(),
|
||||||
dynamic_tools: Vec::new(),
|
dynamic_tools: Vec::new(),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
initial_window_id: Uuid::now_v7().to_string(),
|
||||||
metadata: ThreadPersistenceMetadata {
|
metadata: ThreadPersistenceMetadata {
|
||||||
cwd: Some(config.cwd.to_path_buf()),
|
cwd: Some(config.cwd.to_path_buf()),
|
||||||
model_provider: config.model_provider_id.clone(),
|
model_provider: config.model_provider_id.clone(),
|
||||||
@@ -6715,6 +6716,7 @@ async fn shutdown_complete_does_not_append_to_thread_store_after_shutdown() {
|
|||||||
base_instructions: BaseInstructions::default(),
|
base_instructions: BaseInstructions::default(),
|
||||||
dynamic_tools: Vec::new(),
|
dynamic_tools: Vec::new(),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
initial_window_id: Uuid::now_v7().to_string(),
|
||||||
metadata: ThreadPersistenceMetadata {
|
metadata: ThreadPersistenceMetadata {
|
||||||
cwd: Some(config.cwd.to_path_buf()),
|
cwd: Some(config.cwd.to_path_buf()),
|
||||||
model_provider: config.model_provider_id.clone(),
|
model_provider: config.model_provider_id.clone(),
|
||||||
|
|||||||
@@ -8,6 +8,17 @@ pub(crate) struct AutoCompactWindowIds {
|
|||||||
pub(crate) window_id: Uuid,
|
pub(crate) window_id: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AutoCompactWindowIds {
|
||||||
|
pub(crate) fn new_initial() -> Self {
|
||||||
|
let window_id = Uuid::now_v7();
|
||||||
|
Self {
|
||||||
|
first_window_id: window_id,
|
||||||
|
previous_window_id: None,
|
||||||
|
window_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub(crate) struct AutoCompactWindowSnapshot {
|
pub(crate) struct AutoCompactWindowSnapshot {
|
||||||
pub(crate) prefill_input_tokens: Option<i64>,
|
pub(crate) prefill_input_tokens: Option<i64>,
|
||||||
@@ -34,15 +45,10 @@ pub(super) struct AutoCompactWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AutoCompactWindow {
|
impl AutoCompactWindow {
|
||||||
pub(super) fn new() -> Self {
|
pub(super) fn new_with_ids(ids: AutoCompactWindowIds) -> Self {
|
||||||
let window_id = Uuid::now_v7();
|
|
||||||
Self {
|
Self {
|
||||||
window_number: 0,
|
window_number: 0,
|
||||||
ids: AutoCompactWindowIds {
|
ids,
|
||||||
first_window_id: window_id,
|
|
||||||
previous_window_id: None,
|
|
||||||
window_id,
|
|
||||||
},
|
|
||||||
new_context_window_requested: false,
|
new_context_window_requested: false,
|
||||||
prefill_input_tokens: None,
|
prefill_input_tokens: None,
|
||||||
token_budget_reminder_delivered: false,
|
token_budget_reminder_delivered: false,
|
||||||
@@ -135,7 +141,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tracks_prefill_and_window_boundaries() {
|
fn tracks_prefill_and_window_boundaries() {
|
||||||
let mut window = AutoCompactWindow::new();
|
let mut window = AutoCompactWindow::new_with_ids(AutoCompactWindowIds::new_initial());
|
||||||
|
|
||||||
assert_eq!(window.window_number(), 0);
|
assert_eq!(window.window_number(), 0);
|
||||||
let initial_window_id = window.ids().window_id;
|
let initial_window_id = window.ids().window_id;
|
||||||
|
|||||||
@@ -47,7 +47,18 @@ pub(crate) struct SessionState {
|
|||||||
|
|
||||||
impl SessionState {
|
impl SessionState {
|
||||||
/// Create a new session state mirroring previous `State::default()` semantics.
|
/// Create a new session state mirroring previous `State::default()` semantics.
|
||||||
|
#[cfg(test)]
|
||||||
pub(crate) fn new(session_configuration: SessionConfiguration) -> Self {
|
pub(crate) fn new(session_configuration: SessionConfiguration) -> Self {
|
||||||
|
Self::new_with_auto_compact_window_ids(
|
||||||
|
session_configuration,
|
||||||
|
AutoCompactWindowIds::new_initial(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_with_auto_compact_window_ids(
|
||||||
|
session_configuration: SessionConfiguration,
|
||||||
|
auto_compact_window_ids: AutoCompactWindowIds,
|
||||||
|
) -> Self {
|
||||||
let history = ContextManager::new();
|
let history = ContextManager::new();
|
||||||
Self {
|
Self {
|
||||||
session_configuration,
|
session_configuration,
|
||||||
@@ -57,7 +68,7 @@ impl SessionState {
|
|||||||
mcp_dependency_prompted: HashSet::new(),
|
mcp_dependency_prompted: HashSet::new(),
|
||||||
additional_context: AdditionalContextStore::default(),
|
additional_context: AdditionalContextStore::default(),
|
||||||
previous_turn_settings: None,
|
previous_turn_settings: None,
|
||||||
auto_compact_window: AutoCompactWindow::new(),
|
auto_compact_window: AutoCompactWindow::new_with_ids(auto_compact_window_ids),
|
||||||
startup_prewarm: None,
|
startup_prewarm: None,
|
||||||
current_time_reminder: CurrentTimeReminderState::default(),
|
current_time_reminder: CurrentTimeReminderState::default(),
|
||||||
active_connector_selection: HashSet::new(),
|
active_connector_selection: HashSet::new(),
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::R
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: None,
|
git: None,
|
||||||
};
|
};
|
||||||
@@ -128,6 +129,7 @@ async fn write_rollout_with_meta_only(dir: &Path, thread_id: ThreadId) -> io::Re
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: None,
|
git: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ async fn backfill_scans_existing_rollouts() -> Result<()> {
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: None,
|
git: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2918,6 +2918,18 @@ pub enum MultiAgentVersion {
|
|||||||
V2,
|
V2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS)]
|
||||||
|
pub struct SessionContextWindow {
|
||||||
|
/// UUIDv7 identity of this context window.
|
||||||
|
pub window_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionContextWindow {
|
||||||
|
pub fn new(window_id: String) -> Self {
|
||||||
|
Self { window_id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// SessionMeta contains session-level data that doesn't correspond to a specific turn.
|
/// 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
|
/// NOTE: There used to be an `instructions` field here, which stored user_instructions, but we
|
||||||
@@ -2964,6 +2976,9 @@ pub struct SessionMeta {
|
|||||||
pub memory_mode: Option<String>,
|
pub memory_mode: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub multi_agent_version: Option<MultiAgentVersion>,
|
pub multi_agent_version: Option<MultiAgentVersion>,
|
||||||
|
/// Initial context-window identity for consumers that tail rollout JSONL before compaction.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub context_window: Option<SessionContextWindow>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SessionMeta {
|
impl Default for SessionMeta {
|
||||||
@@ -2988,6 +3003,7 @@ impl Default for SessionMeta {
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -474,6 +474,7 @@ fn write_rollout(path: &std::path::Path, thread_id: ThreadId, message: &str) ->
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: None,
|
git: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ async fn extract_metadata_from_rollout_uses_session_meta() {
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
};
|
};
|
||||||
let session_meta_line = SessionMetaLine {
|
let session_meta_line = SessionMetaLine {
|
||||||
meta: session_meta,
|
meta: session_meta,
|
||||||
@@ -107,6 +108,7 @@ async fn extract_metadata_from_rollout_returns_latest_memory_mode() {
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
};
|
};
|
||||||
let polluted_meta = SessionMeta {
|
let polluted_meta = SessionMeta {
|
||||||
memory_mode: Some("polluted".to_string()),
|
memory_mode: Some("polluted".to_string()),
|
||||||
@@ -375,6 +377,7 @@ fn write_rollout_in_sessions_with_cwd(
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
};
|
};
|
||||||
let session_meta_line = SessionMetaLine {
|
let session_meta_line = SessionMetaLine {
|
||||||
meta: session_meta,
|
meta: session_meta,
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ use codex_protocol::protocol::MultiAgentVersion;
|
|||||||
use codex_protocol::protocol::ResumedHistory;
|
use codex_protocol::protocol::ResumedHistory;
|
||||||
use codex_protocol::protocol::RolloutItem;
|
use codex_protocol::protocol::RolloutItem;
|
||||||
use codex_protocol::protocol::RolloutLine;
|
use codex_protocol::protocol::RolloutLine;
|
||||||
|
use codex_protocol::protocol::SessionContextWindow;
|
||||||
use codex_protocol::protocol::SessionMeta;
|
use codex_protocol::protocol::SessionMeta;
|
||||||
use codex_protocol::protocol::SessionMetaLine;
|
use codex_protocol::protocol::SessionMetaLine;
|
||||||
use codex_protocol::protocol::SessionSource;
|
use codex_protocol::protocol::SessionSource;
|
||||||
@@ -91,6 +92,7 @@ pub enum RolloutRecorderParams {
|
|||||||
base_instructions: BaseInstructions,
|
base_instructions: BaseInstructions,
|
||||||
dynamic_tools: Vec<DynamicToolSpec>,
|
dynamic_tools: Vec<DynamicToolSpec>,
|
||||||
multi_agent_version: Option<MultiAgentVersion>,
|
multi_agent_version: Option<MultiAgentVersion>,
|
||||||
|
initial_window_id: Option<String>,
|
||||||
},
|
},
|
||||||
Resume {
|
Resume {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
@@ -178,6 +180,7 @@ impl RolloutRecorderParams {
|
|||||||
base_instructions,
|
base_instructions,
|
||||||
dynamic_tools,
|
dynamic_tools,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
initial_window_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +205,17 @@ impl RolloutRecorderParams {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_initial_window_id(mut self, initial_window_id: String) -> Self {
|
||||||
|
if let Self::Create {
|
||||||
|
initial_window_id: window_id,
|
||||||
|
..
|
||||||
|
} = &mut self
|
||||||
|
{
|
||||||
|
*window_id = Some(initial_window_id);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resume(path: PathBuf) -> Self {
|
pub fn resume(path: PathBuf) -> Self {
|
||||||
Self::Resume { path }
|
Self::Resume { path }
|
||||||
}
|
}
|
||||||
@@ -715,6 +729,7 @@ impl RolloutRecorder {
|
|||||||
base_instructions,
|
base_instructions,
|
||||||
dynamic_tools,
|
dynamic_tools,
|
||||||
multi_agent_version,
|
multi_agent_version,
|
||||||
|
initial_window_id,
|
||||||
} => {
|
} => {
|
||||||
let log_file_info = precompute_log_file_info(config, conversation_id)?;
|
let log_file_info = precompute_log_file_info(config, conversation_id)?;
|
||||||
let path = log_file_info.path.clone();
|
let path = log_file_info.path.clone();
|
||||||
@@ -752,6 +767,7 @@ impl RolloutRecorder {
|
|||||||
},
|
},
|
||||||
memory_mode: (!config.generate_memories()).then_some("disabled".to_string()),
|
memory_mode: (!config.generate_memories()).then_some("disabled".to_string()),
|
||||||
multi_agent_version,
|
multi_agent_version,
|
||||||
|
context_window: initial_window_id.map(SessionContextWindow::new),
|
||||||
};
|
};
|
||||||
|
|
||||||
(None, Some(log_file_info), path, Some(session_meta))
|
(None, Some(log_file_info), path, Some(session_meta))
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ async fn state_db_init_backfills_before_returning() -> anyhow::Result<()> {
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: None,
|
git: None,
|
||||||
};
|
};
|
||||||
@@ -375,6 +376,7 @@ async fn recorder_materializes_on_flush_with_pending_items() -> std::io::Result<
|
|||||||
let config = test_config(home.path());
|
let config = test_config(home.path());
|
||||||
let session_id = SessionId::default();
|
let session_id = SessionId::default();
|
||||||
let thread_id = ThreadId::new();
|
let thread_id = ThreadId::new();
|
||||||
|
let initial_window_id = Uuid::now_v7().to_string();
|
||||||
let recorder = RolloutRecorder::new(
|
let recorder = RolloutRecorder::new(
|
||||||
&config,
|
&config,
|
||||||
RolloutRecorderParams::new(
|
RolloutRecorderParams::new(
|
||||||
@@ -386,7 +388,8 @@ async fn recorder_materializes_on_flush_with_pending_items() -> std::io::Result<
|
|||||||
BaseInstructions::default(),
|
BaseInstructions::default(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
)
|
)
|
||||||
.with_session_id(session_id),
|
.with_session_id(session_id)
|
||||||
|
.with_initial_window_id(initial_window_id.clone()),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -437,6 +440,13 @@ async fn recorder_materializes_on_flush_with_pending_items() -> std::io::Result<
|
|||||||
panic!("expected session metadata in rollout");
|
panic!("expected session metadata in rollout");
|
||||||
};
|
};
|
||||||
assert_eq!(session_meta.meta.session_id, session_id);
|
assert_eq!(session_meta.meta.session_id, session_id);
|
||||||
|
assert_eq!(
|
||||||
|
session_meta
|
||||||
|
.meta
|
||||||
|
.context_window
|
||||||
|
.map(|window| window.window_id),
|
||||||
|
Some(initial_window_id)
|
||||||
|
);
|
||||||
let buffered_idx = text
|
let buffered_idx = text
|
||||||
.find("buffered-event")
|
.find("buffered-event")
|
||||||
.expect("buffered event in rollout");
|
.expect("buffered event in rollout");
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ fn write_rollout_with_metadata(path: &Path, thread_id: ThreadId) -> std::io::Res
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: None,
|
git: None,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ fn write_rollout_with_user_message(
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: None,
|
git: None,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1290,6 +1290,7 @@ async fn test_updated_at_uses_file_mtime() -> Result<()> {
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: None,
|
git: None,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -341,6 +341,7 @@ mod tests {
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: None,
|
git: None,
|
||||||
}),
|
}),
|
||||||
@@ -532,6 +533,7 @@ mod tests {
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: None,
|
git: None,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1975,6 +1975,7 @@ mod tests {
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: Some("polluted".to_string()),
|
memory_mode: Some("polluted".to_string()),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: None,
|
git: None,
|
||||||
})];
|
})];
|
||||||
@@ -2037,6 +2038,7 @@ mod tests {
|
|||||||
dynamic_tools: None,
|
dynamic_tools: None,
|
||||||
memory_mode: None,
|
memory_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
context_window: None,
|
||||||
},
|
},
|
||||||
git: Some(GitInfo {
|
git: Some(GitInfo {
|
||||||
commit_hash: Some(codex_git_utils::GitSha::new("rollout-sha")),
|
commit_hash: Some(codex_git_utils::GitSha::new("rollout-sha")),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use codex_protocol::ThreadId;
|
|||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::RolloutItem;
|
use codex_protocol::protocol::RolloutItem;
|
||||||
|
use codex_protocol::protocol::SessionContextWindow;
|
||||||
use codex_protocol::protocol::SessionMeta;
|
use codex_protocol::protocol::SessionMeta;
|
||||||
use codex_protocol::protocol::SessionMetaLine;
|
use codex_protocol::protocol::SessionMetaLine;
|
||||||
use codex_protocol::protocol::ThreadMemoryMode;
|
use codex_protocol::protocol::ThreadMemoryMode;
|
||||||
@@ -120,6 +121,7 @@ mod tests {
|
|||||||
base_instructions: BaseInstructions::default(),
|
base_instructions: BaseInstructions::default(),
|
||||||
dynamic_tools: Vec::new(),
|
dynamic_tools: Vec::new(),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
initial_window_id: uuid::Uuid::now_v7().to_string(),
|
||||||
metadata: ThreadPersistenceMetadata {
|
metadata: ThreadPersistenceMetadata {
|
||||||
cwd: None,
|
cwd: None,
|
||||||
model_provider: "test-provider".to_string(),
|
model_provider: "test-provider".to_string(),
|
||||||
@@ -248,6 +250,7 @@ impl InMemoryThreadStore {
|
|||||||
memory_mode: matches!(params.metadata.memory_mode, ThreadMemoryMode::Disabled)
|
memory_mode: matches!(params.metadata.memory_mode, ThreadMemoryMode::Disabled)
|
||||||
.then_some("disabled".to_string()),
|
.then_some("disabled".to_string()),
|
||||||
multi_agent_version: params.multi_agent_version,
|
multi_agent_version: params.multi_agent_version,
|
||||||
|
context_window: Some(SessionContextWindow::new(params.initial_window_id.clone())),
|
||||||
..SessionMeta::default()
|
..SessionMeta::default()
|
||||||
};
|
};
|
||||||
state
|
state
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ pub(super) async fn create_thread(
|
|||||||
params.dynamic_tools,
|
params.dynamic_tools,
|
||||||
)
|
)
|
||||||
.with_session_id(params.session_id)
|
.with_session_id(params.session_id)
|
||||||
.with_multi_agent_version(params.multi_agent_version),
|
.with_multi_agent_version(params.multi_agent_version)
|
||||||
|
.with_initial_window_id(params.initial_window_id),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ThreadStoreError::Internal {
|
.map_err(|err| ThreadStoreError::Internal {
|
||||||
|
|||||||
@@ -1133,6 +1133,7 @@ mod tests {
|
|||||||
base_instructions: BaseInstructions::default(),
|
base_instructions: BaseInstructions::default(),
|
||||||
dynamic_tools: Vec::new(),
|
dynamic_tools: Vec::new(),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
initial_window_id: uuid::Uuid::now_v7().to_string(),
|
||||||
metadata: thread_metadata(),
|
metadata: thread_metadata(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ pub struct CreateThreadParams {
|
|||||||
pub dynamic_tools: Vec<DynamicToolSpec>,
|
pub dynamic_tools: Vec<DynamicToolSpec>,
|
||||||
/// Multi-agent runtime selected when the thread was created.
|
/// Multi-agent runtime selected when the thread was created.
|
||||||
pub multi_agent_version: Option<MultiAgentVersion>,
|
pub multi_agent_version: Option<MultiAgentVersion>,
|
||||||
|
/// Initial context-window identity captured when the thread was created.
|
||||||
|
pub initial_window_id: String,
|
||||||
/// Metadata captured for the newly created thread.
|
/// Metadata captured for the newly created thread.
|
||||||
pub metadata: ThreadPersistenceMetadata,
|
pub metadata: ThreadPersistenceMetadata,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user