diff --git a/codex-rs/core/src/guardian/review_session.rs b/codex-rs/core/src/guardian/review_session.rs index 67f733e0bae2..a62d0f553a9d 100644 --- a/codex-rs/core/src/guardian/review_session.rs +++ b/codex-rs/core/src/guardian/review_session.rs @@ -7,6 +7,7 @@ use std::time::Duration; use anyhow::anyhow; use codex_analytics::GuardianReviewAnalyticsResult; use codex_analytics::GuardianReviewSessionKind; +use codex_protocol::ThreadId; use codex_protocol::config_types::AutoCompactTokenLimitScope; use codex_protocol::config_types::Personality; use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; @@ -20,6 +21,7 @@ use codex_protocol::protocol::InitialHistory; use codex_protocol::protocol::Op; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::SandboxPolicy; +use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SubAgentSource; use codex_protocol::protocol::TokenUsage; use serde_json::Value; @@ -183,6 +185,20 @@ impl GuardianReviewSessionReuseKey { } } +pub(crate) fn prompt_cache_key_override_for_review_session( + session_source: &SessionSource, + parent_thread_id: Option, +) -> Option { + let SessionSource::SubAgent(SubAgentSource::Other(name)) = session_source else { + return None; + }; + if name != GUARDIAN_REVIEWER_NAME { + return None; + } + let parent_thread_id = parent_thread_id?; + Some(format!("guardian:{parent_thread_id}")) +} + impl GuardianReviewSession { async fn shutdown(&self) { self.cancel_token.cancel(); @@ -1160,6 +1176,43 @@ mod tests { ); } + #[tokio::test] + async fn guardian_prompt_cache_key_is_scoped_to_parent_thread() { + let session_source = + SessionSource::SubAgent(SubAgentSource::Other(GUARDIAN_REVIEWER_NAME.to_string())); + let parent_thread_id = ThreadId::new(); + let key = + prompt_cache_key_override_for_review_session(&session_source, Some(parent_thread_id)) + .expect("guardian prompt cache key"); + + assert_eq!(key, format!("guardian:{parent_thread_id}")); + assert!( + key.len() <= 64, + "guardian prompt cache key should fit the Responses API limit" + ); + assert_eq!( + key, + prompt_cache_key_override_for_review_session(&session_source, Some(parent_thread_id)) + .expect("same guardian prompt cache key") + ); + assert_ne!( + key, + prompt_cache_key_override_for_review_session(&session_source, Some(ThreadId::new())) + .expect("different parent guardian prompt cache key") + ); + assert_eq!( + None, + prompt_cache_key_override_for_review_session( + &SessionSource::Cli, + Some(parent_thread_id) + ) + ); + assert_eq!( + None, + prompt_cache_key_override_for_review_session(&session_source, None) + ); + } + #[tokio::test] async fn guardian_review_session_compact_scope_change_invalidates_cached_session() { let parent_config = crate::config::test_config().await;