From 868c9edb0da913a5fc699a71664e65f44f6058b0 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Fri, 28 Aug 2026 14:42:20 +0000 Subject: [PATCH] Assign stable IDs to generated Responses input items (#41349) ## What changed - Assign type-prefixed IDs to Guardian classifier input items before sending the request, preserving existing IDs and reusing generated IDs across WebSocket retries. - Add a prefixed ID to the memory phase-one user message. ## Testing - Verify retried Guardian requests contain the same unique, prefixed item IDs. - Verify the memory phase-one request includes a prefixed user-message ID. GitOrigin-RevId: e01da49d9a661b6bfd2088f091a3ddf69d601e40 --- .../guardian-v2/src/async_scorer/extension_tests.rs | 7 +++++++ codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs | 9 +++++++++ .../guardian-v2/src/async_scorer/sampler_tests.rs | 12 +++++++++++- codex-rs/memories/write/src/phase1.rs | 3 ++- codex-rs/memories/write/src/startup_tests.rs | 7 +++++++ 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/codex-rs/ext/guardian-v2/src/async_scorer/extension_tests.rs b/codex-rs/ext/guardian-v2/src/async_scorer/extension_tests.rs index 642b83df3e..f46f6c5c89 100644 --- a/codex-rs/ext/guardian-v2/src/async_scorer/extension_tests.rs +++ b/codex-rs/ext/guardian-v2/src/async_scorer/extension_tests.rs @@ -1208,6 +1208,7 @@ classifier_instructions = "Predict future violations.\n# Security Policy\n{{ ten request["input"][1], json!({ "type": "message", + "id": request["input"][1]["id"], "role": "developer", "content": [{ "type": "input_text", @@ -1246,6 +1247,7 @@ max_classifier_instruction_tokens = 256 request["input"][1], json!({ "type": "message", + "id": request["input"][1]["id"], "role": "developer", "content": [{ "type": "input_text", @@ -1334,6 +1336,7 @@ max_recent_non_user_entries = 8 request["input"][1], json!({ "type": "message", + "id": request["input"][1]["id"], "role": "developer", "content": [{ "type": "input_text", @@ -1735,6 +1738,7 @@ async fn contributor_uses_model_defaults_and_preserves_local_overrides() -> Resu request["input"][1], json!({ "type": "message", + "id": request["input"][1]["id"], "role": "developer", "content": [{ "type": "input_text", @@ -1900,6 +1904,7 @@ async fn contributor_samples_tool_calls_with_the_existing_luna_pool() -> Result< request["input"][1], json!({ "type": "message", + "id": request["input"][1]["id"], "role": "developer", "content": [{ "type": "input_text", @@ -2321,6 +2326,7 @@ async fn contributor_uses_catalog_policy_without_a_configured_override() -> Resu request["input"][1], json!({ "type": "message", + "id": request["input"][1]["id"], "role": "developer", "content": [{ "type": "input_text", @@ -2363,6 +2369,7 @@ async fn contributor_preserves_uncapped_classifier_instructions() -> Result<()> request["input"][1], json!({ "type": "message", + "id": request["input"][1]["id"], "role": "developer", "content": [{ "type": "input_text", diff --git a/codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs b/codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs index 776a9b7a6b..b2dcdcbf2a 100644 --- a/codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs +++ b/codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs @@ -29,6 +29,7 @@ use codex_login::default_client::default_headers; use codex_model_provider::AgentIdentitySessionFallback; use codex_model_provider::ProviderAuthScope; use codex_model_provider::SharedModelProvider; +use codex_protocol::ResponseItemId; use codex_protocol::ThreadId; use codex_protocol::error::CodexErr; use codex_protocol::models::ContentItem; @@ -504,6 +505,14 @@ impl LunaSampler { phase: None, internal_chat_message_metadata_passthrough: None, }); + // Assign IDs once so retries reuse the same input item identities. + for item in &mut input { + if item.id().is_none() + && let Some(prefix) = item.id_prefix() + { + item.set_id(Some(ResponseItemId::new(prefix))); + } + } let mut request = ResponsesApiRequest { model: MODEL.to_owned(), instructions: String::new(), diff --git a/codex-rs/ext/guardian-v2/src/async_scorer/sampler_tests.rs b/codex-rs/ext/guardian-v2/src/async_scorer/sampler_tests.rs index 10d7839a74..674315dcc0 100644 --- a/codex-rs/ext/guardian-v2/src/async_scorer/sampler_tests.rs +++ b/codex-rs/ext/guardian-v2/src/async_scorer/sampler_tests.rs @@ -904,7 +904,10 @@ async fn sampler_retries_expired_websockets_on_another_warm_connection() -> Resu )) .await?; - let output = sampler.sample(sample_request("turn-1")).await?; + let mut request = sample_request("turn-1"); + request.trusted_review_evidence = vec!["trusted review".to_owned()]; + request.trusted_skill_paths = vec!["/skills/review/SKILL.md".to_owned()]; + let output = sampler.sample(request).await?; assert_eq!(output, "low"); let expired_requests = expired.single_connection(); @@ -925,6 +928,13 @@ async fn sampler_retries_expired_websockets_on_another_warm_connection() -> Resu .remove("client_metadata"); } assert_eq!(expired_request, healthy_request); + let input: Vec = serde_json::from_value(healthy_request["input"].clone())?; + let ids = input + .iter() + .map(|item| item.id().expect("classifier input item ID")) + .collect::>(); + assert_eq!(ids.len(), input.len()); + assert!(ids.iter().all(|id| id.is_prefixed())); Ok(()) } diff --git a/codex-rs/memories/write/src/phase1.rs b/codex-rs/memories/write/src/phase1.rs index 89d87e39ee..0646979939 100644 --- a/codex-rs/memories/write/src/phase1.rs +++ b/codex-rs/memories/write/src/phase1.rs @@ -9,6 +9,7 @@ use codex_config::types::MemoriesConfig; use codex_core::Prompt; use codex_core::RolloutRecorder; use codex_core::config::Config; +use codex_protocol::ResponseItemId; use codex_protocol::error::CodexErr; use codex_protocol::models::BaseInstructions; use codex_protocol::models::ContentItem; @@ -292,7 +293,7 @@ mod job { let mut prompt = Prompt::default(); prompt.input = vec![ResponseItem::Message { - id: None, + id: Some(ResponseItemId::new("msg")), role: "user".to_string(), content: vec![ContentItem::InputText { text: build_stage_one_input_message( diff --git a/codex-rs/memories/write/src/startup_tests.rs b/codex-rs/memories/write/src/startup_tests.rs index b14767a25e..53aa77793c 100644 --- a/codex-rs/memories/write/src/startup_tests.rs +++ b/codex-rs/memories/write/src/startup_tests.rs @@ -19,6 +19,7 @@ use codex_model_provider::ProviderAccountResult; use codex_model_provider::SharedModelProvider; use codex_model_provider::create_model_provider; use codex_model_provider_info::ModelProviderInfo; +use codex_protocol::ResponseItemId; use codex_protocol::ThreadId; use codex_protocol::config_types::ServiceTier; use codex_protocol::models::ContentItem; @@ -689,6 +690,12 @@ async fn memories_startup_phase1_provider_default_drives_request_model() -> anyh request.body_json()["model"].as_str(), Some(MOCK_PROVIDER_PHASE_ONE_MODEL) ); + let input: Vec = serde_json::from_value(request.body_json()["input"].clone())?; + let message = input + .iter() + .find(|item| item.is_user_message()) + .expect("phase-one input message"); + assert!(message.id().is_some_and(ResponseItemId::is_prefixed)); Ok(()) }