diff --git a/codex-rs/codex-api/README.md b/codex-rs/codex-api/README.md index 7849520c0f..e772e83348 100644 --- a/codex-rs/codex-api/README.md +++ b/codex-rs/codex-api/README.md @@ -13,7 +13,7 @@ The public interface of this crate is intentionally small and uniform: - **Responses endpoint** - Input: - - `ResponsesApiRequest` for the request body (`model`, `instructions`, `input`, `tools`, `parallel_tool_calls`, reasoning/text controls). + - `ResponsesApiRequest` for the request body (`model`, `input`, `tools`, `parallel_tool_calls`, reasoning/text controls). - `ResponsesOptions` for transport/header concerns (`conversation_id`, `session_source`, `extra_headers`, `compression`, `turn_state`). - Output: a `ResponseStream` of `ResponseEvent` (both re-exported from `common`). diff --git a/codex-rs/codex-api/src/common.rs b/codex-rs/codex-api/src/common.rs index 553012b6b6..42c9fecd1a 100644 --- a/codex-rs/codex-api/src/common.rs +++ b/codex-rs/codex-api/src/common.rs @@ -283,8 +283,6 @@ pub struct ResponsesApiRequest { pub stream: bool, #[serde(skip_serializing_if = "Option::is_none")] pub service_tier: Option, - #[serde(skip_serializing_if = "String::is_empty")] - pub instructions: String, pub input: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub tools: Option, @@ -309,7 +307,6 @@ impl<'a> From<&'a ResponsesApiRequest> for ResponseCreateWsRequest<'a> { fn from(request: &'a ResponsesApiRequest) -> Self { Self { model: &request.model, - instructions: &request.instructions, previous_response_id: None, input: &request.input, tools: request.tools.as_ref().map(ResponsesApiTools::as_raw_value), @@ -338,8 +335,6 @@ pub struct ResponseCreateWsRequest<'a> { pub stream: bool, #[serde(skip_serializing_if = "Option::is_none")] pub service_tier: Option<&'a str>, - #[serde(skip_serializing_if = "str::is_empty")] - pub instructions: &'a str, #[serde(skip_serializing_if = "Option::is_none")] pub previous_response_id: Option, pub input: &'a [ResponseItem], diff --git a/codex-rs/codex-api/src/endpoint/responses_websocket.rs b/codex-rs/codex-api/src/endpoint/responses_websocket.rs index bc9f4577b7..9e79edda92 100644 --- a/codex-rs/codex-api/src/endpoint/responses_websocket.rs +++ b/codex-rs/codex-api/src/endpoint/responses_websocket.rs @@ -945,7 +945,6 @@ mod tests { fn direct_serialization_preserves_websocket_request_payload() { let api_request = ResponsesApiRequest { model: "gpt-test".to_string(), - instructions: "Use the available tools.".to_string(), input: vec![ResponseItem::Message { id: Some(ResponseItemId::with_suffix("msg", "1")), role: "user".to_string(), @@ -998,7 +997,7 @@ mod tests { let request_text = serialize_websocket_request(&request).expect("serialize websocket request"); assert!(request_text.starts_with( - r#"{"type":"response.create","model":"gpt-test","stream":true,"service_tier":"priority","instructions":"Use the available tools.","previous_response_id":"resp-1","input":"# + r#"{"type":"response.create","model":"gpt-test","stream":true,"service_tier":"priority","previous_response_id":"resp-1","input":"# )); let wire_payload = serde_json::from_str::(&request_text).expect("parse websocket request"); diff --git a/codex-rs/codex-api/tests/clients.rs b/codex-rs/codex-api/tests/clients.rs index 1d72e88e73..b17da45344 100644 --- a/codex-rs/codex-api/tests/clients.rs +++ b/codex-rs/codex-api/tests/clients.rs @@ -339,7 +339,6 @@ async fn responses_client_stream_request_preserves_item_ids() -> Result<()> { let client = ResponsesClient::new(transport, provider("openai"), Arc::new(NoAuth)); let request = ResponsesApiRequest { model: "gpt-test".into(), - instructions: "Say hi".into(), input: vec![ResponseItem::Message { id: Some(ResponseItemId::with_suffix("msg", "1")), role: "user".into(), @@ -394,7 +393,6 @@ async fn responses_client_stream_request_sends_routing_fields_ahead_of_large_inp let large_input = "x".repeat(2 * 1024 * 1024); let request = ResponsesApiRequest { model: "gpt-test".into(), - instructions: "Say hi".into(), input: vec![ResponseItem::Message { id: None, role: "user".into(), @@ -434,7 +432,7 @@ async fn responses_client_stream_request_sends_routing_fields_ahead_of_large_inp ); } assert!(body.starts_with( - r#"{"model":"gpt-test","stream":true,"service_tier":"priority","instructions":"Say hi","input":[{"type":"message""# + r#"{"model":"gpt-test","stream":true,"service_tier":"priority","input":[{"type":"message""# )); assert!(body.len() > 2 * 1024 * 1024); assert_eq!(serde_json::from_str::(body)?, expected); @@ -491,7 +489,6 @@ async fn streaming_client_retries_on_transport_error() -> Result<()> { let request = ResponsesApiRequest { model: "gpt-test".into(), - instructions: "Say hi".into(), input: Vec::new(), tools: Some(empty_tools().into()), tool_choice: "auto".into(), @@ -606,7 +603,6 @@ async fn azure_store_sends_ids_and_headers() -> Result<()> { let request = ResponsesApiRequest { model: "gpt-test".into(), - instructions: "Say hi".into(), input: vec![ResponseItem::Message { id: Some(ResponseItemId::with_suffix("msg", "1")), role: "user".into(), diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 270fa96695..24f2964ffe 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -344,7 +344,6 @@ fn responses_request_properties_match( ) -> bool { let ResponsesApiRequest { model: previous_model, - instructions: previous_instructions, input: _, tools: previous_tools, tool_choice: previous_tool_choice, @@ -362,7 +361,6 @@ fn responses_request_properties_match( } = previous; let ResponsesApiRequest { model: current_model, - instructions: current_instructions, input: _, tools: current_tools, tool_choice: current_tool_choice, @@ -380,7 +378,6 @@ fn responses_request_properties_match( } = current; previous_model == current_model - && previous_instructions == current_instructions && previous_tools == current_tools && previous_tool_choice == current_tool_choice && previous_parallel_tool_calls == current_parallel_tool_calls @@ -908,14 +905,14 @@ impl ModelClient { input.retain(|item| !matches!(item, ResponseItem::ConfigurationUpdate { .. })); } let is_openai = self.state.provider.info().is_openai(); - let (instructions, tools) = if model_info.use_responses_lite { - // These prompt-only items are rebuilt on every request. Hash their visible payloads - // within the thread so retries and resumed sessions preserve their identity. - let prefix_namespace = Uuid::new_v5( - &Uuid::NAMESPACE_OID, - self.state.thread_id.to_string().as_bytes(), - ); - let mut prefix = Vec::new(); + // These prompt-only items are rebuilt on every request. Hash their visible payloads + // within the thread so retries and resumed sessions preserve their identity. + let prefix_namespace = Uuid::new_v5( + &Uuid::NAMESPACE_OID, + self.state.thread_id.to_string().as_bytes(), + ); + let mut prefix = Vec::new(); + let tools = if model_info.use_responses_lite { if !prompt.tools.is_empty() { let tools = create_tools_json_for_responses_lite(&prompt.tools)?; prefix.push(ResponseItem::AdditionalTools { @@ -927,24 +924,21 @@ impl ModelClient { tools, }); } - if !prompt.base_instructions.text.is_empty() { - let mut instructions = ContextualUserFragment::into(BaseInstructionsFragment( - prompt.base_instructions.text.clone(), - )); - instructions.set_id(Some(ResponseItemId::with_suffix( - "msg", - Uuid::new_v5(&prefix_namespace, prompt.base_instructions.text.as_bytes()), - ))); - prefix.push(instructions); - } - input.splice(0..0, prefix); - (String::new(), None) + None } else { - ( - prompt.base_instructions.text.clone(), - Some(create_tools_raw_json_for_responses_api(&prompt.tools)?.into()), - ) + Some(create_tools_raw_json_for_responses_api(&prompt.tools)?.into()) }; + if !prompt.base_instructions.text.is_empty() { + let mut instructions = ContextualUserFragment::into(BaseInstructionsFragment( + prompt.base_instructions.text.clone(), + )); + instructions.set_id(Some(ResponseItemId::with_suffix( + "msg", + Uuid::new_v5(&prefix_namespace, prompt.base_instructions.text.as_bytes()), + ))); + prefix.push(instructions); + } + input.splice(0..0, prefix); if !is_openai { for item in &mut input { item.clear_internal_chat_message_metadata_passthrough(); @@ -1001,7 +995,6 @@ impl ModelClient { let client_metadata = responses_metadata.client_metadata(include_internal); let request = ResponsesApiRequest { model: model_info.slug.clone(), - instructions, input, tools, tool_choice: "auto".to_string(), diff --git a/codex-rs/core/src/client_common_tests.rs b/codex-rs/core/src/client_common_tests.rs index 1cda4ef13d..5af77bec75 100644 --- a/codex-rs/core/src/client_common_tests.rs +++ b/codex-rs/core/src/client_common_tests.rs @@ -112,7 +112,6 @@ fn serializes_text_verbosity_when_set() { let input: Vec = vec![]; let req = ResponsesApiRequest { model: "gpt-5.4".to_string(), - instructions: "i".to_string(), input, tools: Some(empty_tools().into()), tool_choice: "auto".to_string(), @@ -160,7 +159,6 @@ fn serializes_text_schema_with_strict_format() { let req = ResponsesApiRequest { model: "gpt-5.4".to_string(), - instructions: "i".to_string(), input, tools: Some(empty_tools().into()), tool_choice: "auto".to_string(), @@ -222,7 +220,6 @@ fn omits_text_when_not_set() { let input: Vec = vec![]; let req = ResponsesApiRequest { model: "gpt-5.4".to_string(), - instructions: "i".to_string(), input, tools: Some(empty_tools().into()), tool_choice: "auto".to_string(), @@ -247,7 +244,6 @@ fn omits_text_when_not_set() { fn serializes_flex_service_tier_when_set() { let req = ResponsesApiRequest { model: "gpt-5.4".to_string(), - instructions: "i".to_string(), input: vec![], tools: Some(empty_tools().into()), tool_choice: "auto".to_string(), diff --git a/codex-rs/core/src/client_tests.rs b/codex-rs/core/src/client_tests.rs index 5e50bb3acc..779a40ca2b 100644 --- a/codex-rs/core/src/client_tests.rs +++ b/codex-rs/core/src/client_tests.rs @@ -767,6 +767,10 @@ fn responses_request_preserves_result_metadata_above_previous_aggregate_budget() crate::tools::ExecutedToolCalls::new(&features, &codex_history::InitialHistory::New); let mut prompt = Prompt { input: history.clone(), + base_instructions: BaseInstructions { + text: String::new(), + ..Default::default() + }, ..Default::default() }; // Follow the sampling path: budget the request copy before client serialization. @@ -976,6 +980,10 @@ async fn responses_http_preserves_raw_tool_metadata_for_openai_custom_endpoint() }))); let prompt = Prompt { input: vec![output.clone()], + base_instructions: BaseInstructions { + text: String::new(), + ..Default::default() + }, ..Default::default() }; let responses_metadata = test_responses_metadata_for_client( @@ -1014,12 +1022,14 @@ async fn responses_http_preserves_raw_tool_metadata_for_openai_custom_endpoint() Ok(()) } -#[test] -fn responses_lite_prefix_ids_track_thread_and_payload() -> anyhow::Result<()> { +#[test_case::test_case(false; "standard")] +#[test_case::test_case(true; "responses_lite")] +fn prefix_ids_track_thread_and_payload(responses_lite: bool) -> anyhow::Result<()> { let thread_id = ThreadId::new(); let client = test_model_client_with_thread_id(thread_id, SessionSource::Cli); let mut model = test_model_info(); - model.use_responses_lite = true; + model.use_responses_lite = responses_lite; + let instructions_index = usize::from(responses_lite); let mut tool = codex_tools::FreeformTool { name: "exec".to_string(), description: "Execute JavaScript.".to_string(), @@ -1061,25 +1071,38 @@ fn responses_lite_prefix_ids_track_thread_and_payload() -> anyhow::Result<()> { prompt.base_instructions.text.push_str(" with an update"); let changed_instructions = build(&client, &prompt)?; - assert_eq!(changed_instructions.input[0], original.input[0]); - assert_ne!(changed_instructions.input[1].id(), original.input[1].id()); + assert_ne!( + changed_instructions.input[instructions_index].id(), + original.input[instructions_index].id() + ); tool.description .push_str(" Updated execution instructions."); prompt.tools = vec![codex_tools::ToolSpec::Freeform(tool)].into(); let changed_tools = build(&client, &prompt)?; - assert_ne!( - changed_tools.input[0].id(), - changed_instructions.input[0].id() + assert_eq!( + changed_tools.input[instructions_index], + changed_instructions.input[instructions_index] ); - assert_eq!(changed_tools.input[1], changed_instructions.input[1]); + if responses_lite { + assert_eq!(changed_instructions.input[0], original.input[0]); + assert_ne!( + changed_tools.input[0].id(), + changed_instructions.input[0].id() + ); + } let independent = build( &test_model_client_with_thread_id(ThreadId::new(), SessionSource::Cli), &prompt, )?; - assert_ne!(independent.input[0].id(), changed_tools.input[0].id()); - assert_ne!(independent.input[1].id(), changed_tools.input[1].id()); + assert_ne!( + independent.input[instructions_index].id(), + changed_tools.input[instructions_index].id() + ); + if responses_lite { + assert_ne!(independent.input[0].id(), changed_tools.input[0].id()); + } Ok(()) } diff --git a/codex-rs/core/src/client_tool_metadata_tests.rs b/codex-rs/core/src/client_tool_metadata_tests.rs index 4d093231db..0839e5c5e5 100644 --- a/codex-rs/core/src/client_tool_metadata_tests.rs +++ b/codex-rs/core/src/client_tool_metadata_tests.rs @@ -10,6 +10,7 @@ use codex_protocol::models::ToolResultSource; use codex_protocol::models::ToolResultSources; use pretty_assertions::assert_eq; use serde_json::json; +use std::collections::HashMap; fn request() -> ResponsesApiRequest { request_with_metadata(&json!({ @@ -43,7 +44,6 @@ fn request_with_metadata_and_source( output.mark_tool_calls_complete(); ResponsesApiRequest { model: "test".to_string(), - instructions: "x".to_string(), input: vec![output], tools: None, tool_choice: "auto".to_string(), @@ -56,18 +56,22 @@ fn request_with_metadata_and_source( service_tier: None, prompt_cache_key: None, text: None, - client_metadata: None, + client_metadata: Some(HashMap::from([("padding".to_string(), "x".to_string())])), access_programs: None, } } +fn set_padding(request: &mut ResponsesApiRequest, padding: String) { + request.client_metadata = Some(HashMap::from([("padding".to_string(), padding)])); +} + #[test] fn http_message_budget_preserves_resources_until_the_message_still_exceeds_limit() { for overage in [0, 4 * 1024, 20 * 1024, 40 * 1024] { let mut request = request(); let target_bytes = MAX_RESPONSE_MESSAGE_BYTES + overage; - request.instructions = - "x".repeat(target_bytes - serialized_json_bytes(&request).unwrap() + 1); + let padding = "x".repeat(target_bytes - serialized_json_bytes(&request).unwrap() + 1); + set_padding(&mut request, padding); assert_eq!(serialized_json_bytes(&request).unwrap(), target_bytes); assert!(metadata_metrics::metadata_bytes(&request.input) < 32 * 1024); let original_input = request.input.clone(); @@ -158,10 +162,11 @@ fn message_budget_removes_residual_values_and_markers_when_ordinary_input_fits() } else { serialized_json_bytes(&ordinary).unwrap() }; - // The original instructions contain one byte, so leave exactly one + // The original padding contains one byte, so leave exactly one // byte below the limit after removing only result metadata. - ordinary.instructions = "x".repeat(MAX_RESPONSE_MESSAGE_BYTES - ordinary_bytes); - request.instructions = ordinary.instructions.clone(); + let padding = "x".repeat(MAX_RESPONSE_MESSAGE_BYTES - ordinary_bytes); + set_padding(&mut ordinary, padding.clone()); + set_padding(&mut request, padding); let original_input = request.input.clone(); let mut expected = ordinary.clone(); if metadata.get("openai/resource_access").is_some() { @@ -235,9 +240,10 @@ fn residual_marker_removal_preserves_earlier_resources_and_stops_when_message_fi } let mut expected = request.clone(); expected.input[1].clear_tool_result_metadata(); - expected.instructions = + let padding = "x".repeat(MAX_RESPONSE_MESSAGE_BYTES - serialized_json_bytes(&expected).unwrap() + 1); - request.instructions = expected.instructions.clone(); + set_padding(&mut expected, padding.clone()); + set_padding(&mut request, padding); assert_eq!( serialized_json_bytes(&expected).unwrap(), MAX_RESPONSE_MESSAGE_BYTES @@ -303,8 +309,9 @@ fn inventory_only_message_budget_counts_the_complete_encoded_envelope() { } else { serialized_json_bytes(&ordinary).unwrap() }; - request.instructions = "x".repeat(MAX_RESPONSE_MESSAGE_BYTES - ordinary_bytes + 1); - ordinary.instructions = request.instructions.clone(); + let padding = "x".repeat(MAX_RESPONSE_MESSAGE_BYTES - ordinary_bytes + 1); + set_padding(&mut request, padding.clone()); + set_padding(&mut ordinary, padding); if websocket { let mut message = ResponsesWsRequest::ResponseCreate(ResponseCreateWsRequest::from(&request)); diff --git a/codex-rs/core/src/guardian/request_budget.rs b/codex-rs/core/src/guardian/request_budget.rs index b71891d26b..6c958dc809 100644 --- a/codex-rs/core/src/guardian/request_budget.rs +++ b/codex-rs/core/src/guardian/request_budget.rs @@ -52,13 +52,11 @@ pub(super) fn estimate_request_tokens(request: &ResponsesApiRequest) -> usize { .iter() .map(estimate_item_token_count) .fold(0i64, i64::saturating_add); - let instructions = TruncationPolicy::Bytes(request.instructions.len()).token_budget(); let metadata = serde_json::to_vec(&(&request.tools, &request.text)) .map(|bytes| TruncationPolicy::Bytes(bytes.len()).token_budget()) .unwrap_or(usize::MAX); usize::try_from(input) .unwrap_or(usize::MAX) - .saturating_add(instructions) .saturating_add(metadata) } diff --git a/codex-rs/core/src/session/snapshots/codex_core__codex_tests__fork_startup_context_then_first_turn_diff.snap b/codex-rs/core/src/session/snapshots/codex_core__codex_tests__fork_startup_context_then_first_turn_diff.snap index b41176616c..9ea3b45252 100644 --- a/codex-rs/core/src/session/snapshots/codex_core__codex_tests__fork_startup_context_then_first_turn_diff.snap +++ b/codex-rs/core/src/session/snapshots/codex_core__codex_tests__fork_startup_context_then_first_turn_diff.snap @@ -7,13 +7,15 @@ Scenario: First request after fork when startup preserves the parent baseline, t ## Window 1 -- request 1 (turn; First Forked Turn Request) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: fork seed -03:message/developer[2]: +04:message/developer[2]: [01] [02] -04:message/user: +05:message/user: after fork diff --git a/codex-rs/core/tests/common/context_snapshot.rs b/codex-rs/core/tests/common/context_snapshot.rs index 068d363b76..542d9a73bc 100644 --- a/codex-rs/core/tests/common/context_snapshot.rs +++ b/codex-rs/core/tests/common/context_snapshot.rs @@ -132,7 +132,7 @@ fn capture_request(number: usize, entry: &SnapshotEntry<'_>) -> CapturedRequest } SnapshotSource::Items(items) => ("items".to_string(), Value::Array(items.to_vec()), None), }; - // Responses Lite moves base instructions into an annotated developer content part. Keep its + // Base instructions live in an annotated developer content part. Keep its // location for rendering, while still excluding transport metadata from window comparison. let mut model_instruction_parts: Vec<_> = input .as_array() @@ -150,21 +150,22 @@ fn capture_request(number: usize, entry: &SnapshotEntry<'_>) -> CapturedRequest .map(move |(part_index, _)| (item_index, part_index)) }) .collect(); - // Providers can remove annotations. The Lite prefix still identifies the position, but an + // Providers can remove annotations. The prefix still identifies the position, but an // empty base prompt also leaves ordinary developer guidance there; require a complete catalog prompt. + let instructions_index = usize::from(input[0]["type"] == "additional_tools"); + let instructions = &input[instructions_index]; if model_instruction_parts.is_empty() - && input[0]["type"] == "additional_tools" - && input[0]["role"] == "developer" - && input[1]["type"] == "message" - && input[1]["role"] == "developer" - && input[1]["content"] + && instructions["type"] == "message" + && instructions["role"] == "developer" + && instructions["content"] .as_array() .is_some_and(|content| content.len() == 1) - && input[1]["internal_chat_message_metadata_passthrough"]["content_item_kinds"][0].is_null() - && let Some(text) = input[1]["content"][0]["text"].as_str() + && instructions["internal_chat_message_metadata_passthrough"]["content_item_kinds"][0] + .is_null() + && let Some(text) = instructions["content"][0]["text"].as_str() && is_bundled_model_instructions(text) { - model_instruction_parts.push((1, 0)); + model_instruction_parts.push((instructions_index, 0)); } let input = strip_metadata_from_json(strip_response_item_ids_from_json(input)); CapturedRequest { diff --git a/codex-rs/core/tests/common/responses.rs b/codex-rs/core/tests/common/responses.rs index 842bd19dc3..de8507feb2 100644 --- a/codex-rs/core/tests/common/responses.rs +++ b/codex-rs/core/tests/common/responses.rs @@ -227,10 +227,10 @@ impl ResponsesRequest { } pub fn instructions_text(&self) -> String { - self.body_json()["instructions"] - .as_str() - .unwrap() - .to_string() + self.message_input_texts("developer") + .into_iter() + .next() + .expect("base instructions developer message") } /// Returns all `input_text` spans from `message` inputs for the provided role. diff --git a/codex-rs/core/tests/suite/agent_websocket.rs b/codex-rs/core/tests/suite/agent_websocket.rs index 75f8c6db10..2262343cf1 100644 --- a/codex-rs/core/tests/suite/agent_websocket.rs +++ b/codex-rs/core/tests/suite/agent_websocket.rs @@ -328,9 +328,9 @@ async fn websocket_first_turn_uses_startup_prewarm_and_create( .expect("missing warmup request") .body_json(); let turn = connection.get(1).expect("missing turn request").body_json(); - assert_eq!(warmup["instructions"], turn["instructions"]); + assert_eq!(turn["previous_response_id"], "warm-1"); assert_eq!( - warmup["instructions"] + warmup["input"][0]["content"][0]["text"] .as_str() .expect("warmup base instructions") .contains("update_plan"), diff --git a/codex-rs/core/tests/suite/agents_md.rs b/codex-rs/core/tests/suite/agents_md.rs index f2f7261360..4c157e8801 100644 --- a/codex-rs/core/tests/suite/agents_md.rs +++ b/codex-rs/core/tests/suite/agents_md.rs @@ -2367,7 +2367,9 @@ async fn fork_injects_changed_agents_md_once() -> Result<()> { let requests = response_mock.requests(); assert_eq!(requests.len(), 4); let parent_input = requests[0].input(); - let fork_input = requests[1].input(); + let mut fork_input = requests[1].input(); + // The request prefix has a thread-scoped ID; retained history IDs stay unchanged. + fork_input[0]["id"] = parent_input[0]["id"].clone(); assert_eq!( fork_input.get(..parent_input.len()), Some(parent_input.as_slice()), @@ -2553,7 +2555,9 @@ async fn run_subagent_global_instruction_case(fork_context: bool) -> Result<()> ); if fork_context { let seed_input = seed_request.input(); - let child_input = child_request.input(); + let mut child_input = child_request.input(); + // The request prefix has a thread-scoped ID; retained history IDs stay unchanged. + child_input[0]["id"] = seed_input[0]["id"].clone(); assert_eq!( child_input.get(..seed_input.len()), Some(seed_input.as_slice()), diff --git a/codex-rs/core/tests/suite/cli_stream.rs b/codex-rs/core/tests/suite/cli_stream.rs index 715da9dc5b..26f1898d21 100644 --- a/codex-rs/core/tests/suite/cli_stream.rs +++ b/codex-rs/core/tests/suite/cli_stream.rs @@ -347,14 +347,9 @@ async fn exec_cli_applies_model_instructions_file() { assert!(output.status.success()); // Inspect the captured request and verify our custom base instructions were - // included in the `instructions` field. + // included in the base instructions developer message. let request = resp_mock.single_request(); - let body = request.body_json(); - let instructions = body - .get("instructions") - .and_then(|v| v.as_str()) - .unwrap_or_default() - .to_string(); + let instructions = request.instructions_text(); assert!( instructions.contains(marker), "instructions did not contain custom marker; got: {instructions}" @@ -419,12 +414,7 @@ async fn exec_cli_profile_applies_model_instructions_file() { assert!(output.status.success()); let request = resp_mock.single_request(); - let body = request.body_json(); - let instructions = body - .get("instructions") - .and_then(|v| v.as_str()) - .unwrap_or_default() - .to_string(); + let instructions = request.instructions_text(); assert!( instructions.contains(marker), "instructions did not contain profile marker; got: {instructions}" diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 1d7359de86..c232097e72 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -330,7 +330,7 @@ async fn openai_stateless_responses_requests_preserve_item_turn_metadata_across_ let first_input = first["input"].as_array().expect("first input"); let second_input = second["input"].as_array().expect("second input"); assert_eq!(&second_input[..first_input.len()], first_input.as_slice()); - for item in first_input { + for item in &first_input[1..] { assert_eq!( item["internal_chat_message_metadata_passthrough"]["turn_id"].as_str(), Some(first_turn_id) @@ -1778,14 +1778,7 @@ async fn includes_base_instructions_override_in_request() { wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; let request = resp_mock.single_request(); - let request_body = request.body_json(); - - assert!( - request_body["instructions"] - .as_str() - .unwrap() - .contains("test instructions") - ); + assert_eq!(request.instructions_text(), "test instructions"); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -1998,12 +1991,7 @@ async fn includes_user_instructions_message_in_request() { let request = resp_mock.single_request(); let request_body = request.body_json(); - assert!( - !request_body["instructions"] - .as_str() - .unwrap() - .contains("be nice") - ); + assert!(!request.instructions_text().contains("be nice")); assert_message_role(&request_body["input"][0], "developer"); let developer_texts = request_body["input"] .as_array() @@ -2019,8 +2007,8 @@ async fn includes_user_instructions_message_in_request() { "expected permissions message to mention sandbox_mode, got {developer_texts:?}" ); - assert_message_role(&request_body["input"][1], "user"); - let user_context_texts = message_input_texts(&request_body["input"][1]); + assert_message_role(&request_body["input"][2], "user"); + let user_context_texts = message_input_texts(&request_body["input"][2]); assert!( user_context_texts .iter() @@ -3038,12 +3026,7 @@ async fn includes_developer_instructions_message_in_request() { let request = resp_mock.single_request(); let request_body = request.body_json(); - assert!( - !request_body["instructions"] - .as_str() - .unwrap() - .contains("be nice") - ); + assert!(!request.instructions_text().contains("be nice")); assert_message_role(&request_body["input"][0], "developer"); let developer_texts = request_body["input"] .as_array() @@ -3063,8 +3046,8 @@ async fn includes_developer_instructions_message_in_request() { "expected developer instructions in a developer message, got {developer_texts:?}" ); - assert_message_role(&request_body["input"][1], "user"); - let user_context_texts = message_input_texts(&request_body["input"][1]); + assert_message_role(&request_body["input"][2], "user"); + let user_context_texts = message_input_texts(&request_body["input"][2]); assert!( user_context_texts .iter() @@ -3336,23 +3319,20 @@ async fn azure_responses_request_does_not_store_and_preserves_prefixed_item_ids( assert_eq!(body["store"], serde_json::Value::Bool(false)); assert_eq!(body["stream"], serde_json::Value::Bool(true)); - assert_eq!(body["input"].as_array().map(Vec::len), Some(10)); - assert_eq!(body["input"][0]["id"].as_str(), Some("rs_reasoning-id")); - assert_eq!(body["input"][1]["id"].as_str(), Some("msg_message-id")); - assert_eq!(body["input"][2]["id"].as_str(), Some("ws_web-search-id")); - assert_eq!(body["input"][3]["id"].as_str(), Some("fc_function-id")); - assert_eq!( - body["input"][4]["call_id"].as_str(), - Some("function-call-id") - ); - assert_eq!(body["input"][5]["id"].as_str(), Some("lsh_local-shell-id")); - assert_eq!(body["input"][6]["id"].as_str(), Some("ctc_custom-tool-id")); - assert_eq!( - body["input"][7]["call_id"].as_str(), - Some("custom-tool-call-id") - ); - assert_eq!(body["input"][8].get("id"), None); - assert_eq!(body["input"][9].get("id"), None); + let input = body["input"].as_array().expect("request input"); + assert_eq!(input[0]["role"], "developer"); + let input = &input[1..]; + assert_eq!(input.len(), 10); + assert_eq!(input[0]["id"].as_str(), Some("rs_reasoning-id")); + assert_eq!(input[1]["id"].as_str(), Some("msg_message-id")); + assert_eq!(input[2]["id"].as_str(), Some("ws_web-search-id")); + assert_eq!(input[3]["id"].as_str(), Some("fc_function-id")); + assert_eq!(input[4]["call_id"].as_str(), Some("function-call-id")); + assert_eq!(input[5]["id"].as_str(), Some("lsh_local-shell-id")); + assert_eq!(input[6]["id"].as_str(), Some("ctc_custom-tool-id")); + assert_eq!(input[7]["call_id"].as_str(), Some("custom-tool-call-id")); + assert_eq!(input[8].get("id"), None); + assert_eq!(input[9].get("id"), None); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index b2d1adc3ad..6659bba1f4 100755 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -666,7 +666,9 @@ async fn responses_websocket_resume_prewarm_reuses_and_repairs_connection() -> a ); let connection = server.single_connection(); assert_eq!(connection.len(), 3); - assert_eq!(connection[0].body_json()["input"], json!([])); + let prewarm = connection[0].body_json(); + assert_eq!(prewarm["input"].as_array().unwrap().len(), 1); + assert_eq!(prewarm["input"][0]["role"], "developer"); assert_eq!(connection[2].body_json()["previous_response_id"], "resp-1"); let mut expected_history = connection .iter() @@ -2280,7 +2282,7 @@ async fn responses_websocket_creates_on_non_prefix() { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn responses_websocket_creates_when_non_input_request_fields_change() { +async fn responses_websocket_creates_when_instructions_change() { skip_if_no_network!(); let server = start_websocket_server(vec![vec![ @@ -2307,9 +2309,17 @@ async fn responses_websocket_creates_when_non_input_request_fields_change() { assert_eq!(second["type"].as_str(), Some("response.create")); assert_eq!(second.get("previous_response_id"), None); + assert_eq!(second["input"][0]["role"], "developer"); assert_eq!( - second["input"], - serde_json::to_value(&prompt_two.input).expect("serialize full input") + second["input"][0]["content"][0]["text"], + "base instructions two" + ); + assert_eq!( + &second["input"].as_array().unwrap()[1..], + serde_json::to_value(&prompt_two.input) + .unwrap() + .as_array() + .unwrap() ); assert_continuation_metrics( @@ -2377,12 +2387,9 @@ async fn responses_websocket_v2_creates_without_previous_response_id_when_non_in let harness = websocket_harness_with_v2(&server, /*runtime_metrics_enabled*/ true).await; let mut session = harness.client.new_session(); - let prompt_one = - prompt_with_input_and_instructions(vec![message_item("hello")], "base instructions one"); - let prompt_two = prompt_with_input_and_instructions( - vec![message_item("hello"), message_item("second")], - "base instructions two", - ); + let prompt_one = prompt_with_input(vec![message_item("hello")]); + let mut prompt_two = prompt_with_input(vec![message_item("hello"), message_item("second")]); + prompt_two.output_schema = Some(json!({"type": "object", "properties": {}})); stream_until_complete(&mut session, &harness, &prompt_one).await; stream_until_complete(&mut session, &harness, &prompt_two).await; @@ -2615,6 +2622,7 @@ fn assistant_message_item(id: &str, text: &str) -> ResponseItem { fn prompt_with_input(input: Vec) -> Prompt { let mut prompt = Prompt::default(); prompt.input = input; + prompt.base_instructions.text.clear(); prompt } diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index eb0802902a..fed86b0c66 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -9022,10 +9022,32 @@ async fn code_mode_oversized_websocket_yield_keeps_later_wait_incomplete() -> Re const LIMIT: usize = 15 * 1024 * 1024; const PROMPT: &str = "Record a call, yield, then stop"; + // Keep padding outside input so it remains on incremental requests too. + // Base instructions are cached after the first request. + #[derive(Debug)] + struct RequestPadding(String); + impl codex_extension_api::ModelRequestContributor for RequestPadding { + fn request( + &self, + input: codex_extension_api::ModelRequestInput<'_>, + ) -> Option> { + input + .client_metadata + .get_or_insert_default() + .insert("test-padding".to_string(), self.0.clone()); + None + } + } + let extensions = |padding| { + let mut registry = ExtensionRegistryBuilder::new(); + registry.model_request_contributor(Arc::new(RequestPadding(padding))); + Arc::new(registry.build()) + }; + // Calibrate a first request with the same tools/features/turn prompt; its // exact serialized overhead varies with the model catalog and headers. - let configure = |config: &mut Config, instructions: String| { - config.base_instructions = Some(instructions); + let configure = |config: &mut Config| { + config.base_instructions = Some(String::new()); config.model_context_window = Some(20_000_000); config.model_auto_compact_token_limit = Some(20_000_000); config.features.disable(Feature::TokenBudget).unwrap(); @@ -9049,7 +9071,8 @@ async fn code_mode_oversized_websocket_yield_keeps_later_wait_incomplete() -> Re .await; let mut probe_builder = test_codex() .with_model("test-gpt-5.1-codex") - .with_config(move |config| configure(config, String::new())); + .with_extensions(extensions(String::new())) + .with_config(configure); let probe = probe_builder .build_with_websocket_server(&probe_server) .await?; @@ -9063,7 +9086,7 @@ async fn code_mode_oversized_websocket_yield_keeps_later_wait_incomplete() -> Re // One 7 KiB invocation stays under the recorder's per-output argument // budget. It pushes the yielded delta over the message budget only. - let instructions = "x".repeat(LIMIT - base_bytes - 4 * 1024); + let padding = "x".repeat(LIMIT - base_bytes - 4 * 1024); let code = r#" await tools.test_sync_tool({ barrier: { id: "x".repeat(7000), participants: 1 } }); text("yielded"); @@ -9084,7 +9107,8 @@ await new Promise(() => {}); .await; let mut builder = test_codex() .with_model("test-gpt-5.1-codex") - .with_config(move |config| configure(config, instructions)); + .with_extensions(extensions(padding)) + .with_config(configure); let test = builder.build_with_websocket_server(&server).await?; test.submit_turn(PROMPT).await?; let connection = server.single_connection(); diff --git a/codex-rs/core/tests/suite/compact.rs b/codex-rs/core/tests/suite/compact.rs index 080a161dad..bd120333e5 100644 --- a/codex-rs/core/tests/suite/compact.rs +++ b/codex-rs/core/tests/suite/compact.rs @@ -1476,21 +1476,10 @@ async fn multiple_auto_compact_per_task_runs_after_token_limit_hit() { return None; } - let texts = value - .get("content") - .and_then(|content| content.as_array()) - .into_iter() - .flatten() - .filter_map(|item| item.get("text").and_then(|text| text.as_str())); - - // Ignore cached prefix messages (project docs + permissions) since they are not - // relevant to compaction behavior and can change as bundled prompts evolve. + // Ignore instruction prefixes since they are not relevant to the history + // being compacted and can change as bundled prompts evolve. let role = value.get("role").and_then(|role| role.as_str()); - if role == Some("developer") - && texts - .into_iter() - .any(|text| text.contains("`sandbox_mode`")) - { + if role == Some("developer") { return None; } if role == Some("user") { @@ -1964,18 +1953,10 @@ async fn auto_compact_runs_after_token_limit_hit() { .expect("follow-up request missing"); assert_eq!(follow_up_index, 3, "follow-up request should be last"); - let body_first = requests[0].body_json(); let body_auto = requests[auto_compact_index].body_json(); let body_follow_up = requests[follow_up_index].body_json(); - let instructions = body_auto - .get("instructions") - .and_then(|v| v.as_str()) - .unwrap_or_default(); - let baseline_instructions = body_first - .get("instructions") - .and_then(|v| v.as_str()) - .unwrap_or_default() - .to_string(); + let instructions = requests[auto_compact_index].instructions_text(); + let baseline_instructions = requests[0].instructions_text(); assert_eq!( instructions, baseline_instructions, "auto compact should keep the standard developer instructions", @@ -4160,7 +4141,8 @@ async fn manual_compact_retries_after_context_window_error() { compact_input.len(), retry_input.len() ); - if let (Some(first_before), Some(first_after)) = (compact_input.first(), retry_input.first()) { + assert_eq!(compact_input.first(), retry_input.first()); + if let (Some(first_before), Some(first_after)) = (compact_input.get(1), retry_input.get(1)) { assert_ne!( first_before, first_after, "retry should drop the oldest conversation item" @@ -5858,7 +5840,7 @@ async fn remote_v2_compaction_refreshes_instructions_and_preserves_them_on_cold_ assert_single_instruction_fragment(&requests[3], &new_fragment); let resumed_input = requests[3].input(); assert_eq!( - resumed_input.get(..replacement_history.len()), + resumed_input.get(1..=replacement_history.len()), Some(replacement_history.as_slice()), "remote-v2 cold resume should replay persisted replacement history verbatim" ); diff --git a/codex-rs/core/tests/suite/compact_resume_fork.rs b/codex-rs/core/tests/suite/compact_resume_fork.rs index c20769beb6..f630441782 100644 --- a/codex-rs/core/tests/suite/compact_resume_fork.rs +++ b/codex-rs/core/tests/suite/compact_resume_fork.rs @@ -237,7 +237,9 @@ async fn compact_resume_and_fork_preserve_model_history_view() { // input after compact is a prefix of input after resume/fork let input_after_compact = json!(requests[requests.len() - 3]["input"]); let input_after_resume = json!(requests[requests.len() - 2]["input"]); - let input_after_fork = json!(requests[requests.len() - 1]["input"]); + let mut input_after_fork = json!(requests[requests.len() - 1]["input"]); + // The request prefix has a thread-scoped ID; retained history IDs stay unchanged. + input_after_fork[0]["id"] = input_after_compact[0]["id"].clone(); let compact_arr = input_after_compact .as_array() diff --git a/codex-rs/core/tests/suite/context_annotations.rs b/codex-rs/core/tests/suite/context_annotations.rs index 34392c457e..b57bd3c91f 100644 --- a/codex-rs/core/tests/suite/context_annotations.rs +++ b/codex-rs/core/tests/suite/context_annotations.rs @@ -150,8 +150,8 @@ async fn first_request_item_types_roles_and_content_annotations() -> Result<()> let mut guardian_item = request .input() .into_iter() - .next() - .expect("guardian approval should be the first context item"); + .nth(1) + .expect("guardian approval should follow the base instructions"); guardian_item .as_object_mut() .expect("guardian approval should be an object") @@ -206,6 +206,7 @@ async fn first_request_item_types_roles_and_content_annotations() -> Result<()> .collect::>() .join("\n"); insta::assert_snapshot!(items, @r#" + message developer ["model.base_instructions"] message developer ["guardian.approved_action"] message developer ["generic.developer_instructions","token_budget.context_window_guidance","permissions.instructions","environments.instructions"] message developer ["token_budget.context_window"] diff --git a/codex-rs/core/tests/suite/direct_tool_metadata.rs b/codex-rs/core/tests/suite/direct_tool_metadata.rs index 22ab2c4a25..b9274bf86d 100644 --- a/codex-rs/core/tests/suite/direct_tool_metadata.rs +++ b/codex-rs/core/tests/suite/direct_tool_metadata.rs @@ -4,6 +4,7 @@ use anyhow::Result; use codex_core::ForkSnapshot; use codex_core::StartThreadOptions; use codex_core::TurnInputRequest; +use codex_core::TurnStartOptions; use codex_features::Feature; use codex_mcp::CODEX_APPS_MCP_SERVER_NAME; use codex_model_provider::RemoteCompactionSupport; @@ -69,10 +70,10 @@ async fn message_budget_sheds_inventory_without_changing_tool_results_or_history assert!(arguments.to_string().len() < 8 * 1024); assert!(call_count * arguments.to_string().len() < 1024 * 1024); let next_arguments = json!({"plan": [{"step": "done", "status": "completed"}]}); - let instruction_bytes = message_limit - if websocket { 64 * 1024 } else { 192 * 1024 }; - let instructions = "padding ".repeat(instruction_bytes / 8); + // An output schema stays in every wire request, including WebSocket deltas. + let padding_bytes = message_limit - if websocket { 64 * 1024 } else { 192 * 1024 }; + let schema_description = "padding ".repeat(padding_bytes / 8); let mut builder = test_codex().with_config(move |config| { - config.base_instructions = Some(instructions); config.model_context_window = Some(20_000_000); config.model_auto_compact_token_limit = Some(20_000_000); config @@ -142,7 +143,27 @@ async fn message_budget_sheds_inventory_without_changing_tool_results_or_history .build_with_auto_env(http_server.as_ref().expect("HTTP test server")) .await? }; - test.submit_turn("Update the plan, then finish it").await?; + test.codex + .start_or_steer_turn( + TurnInputRequest::user_input(vec![UserInput::Text { + text: "Update the plan, then finish it".to_string(), + text_elements: Vec::new(), + }]) + .on_start(TurnStartOptions { + final_output_json_schema: Some(json!({ + "type": "object", + "description": schema_description, + "properties": {}, + "additionalProperties": false, + })), + ..Default::default() + }), + ) + .await?; + wait_for_event(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; let requests = if let Some(server) = &websocket_server { let connection = server.single_connection(); assert_eq!(connection.len(), 4); @@ -167,11 +188,11 @@ async fn message_budget_sheds_inventory_without_changing_tool_results_or_history } for (index, request) in requests.iter().enumerate() { assert_eq!( - request["instructions"] + request["text"]["format"]["schema"]["description"] .as_str() - .expect("request instructions") + .expect("request schema description") .len(), - instruction_bytes + padding_bytes ); let request_bytes = serde_json::to_vec(request)?.len(); assert!( diff --git a/codex-rs/core/tests/suite/guardian_review.rs b/codex-rs/core/tests/suite/guardian_review.rs index 34bdc5d640..94b7054b87 100644 --- a/codex-rs/core/tests/suite/guardian_review.rs +++ b/codex-rs/core/tests/suite/guardian_review.rs @@ -1022,7 +1022,7 @@ async fn guardian_session_prewarms_and_is_reused_for_first_review( .as_str() .expect("Responses Lite Guardian developer instructions") } else { - guardian_prewarm["instructions"] + guardian_prewarm["input"][0]["content"][0]["text"] .as_str() .expect("Guardian instructions") }; diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index a1872d46ba..33877a13a0 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -182,10 +182,16 @@ async fn config_personality_none_preserves_explicit_base_instructions( let request = resp_mock.single_request(); let body = request.body_json(); - // Responses requests omit the instructions field for an explicit empty override. - let expected_instructions = (!custom_instructions.is_empty()) - .then(|| serde_json::Value::String(custom_instructions.to_string())); - assert_eq!(body.get("instructions"), expected_instructions.as_ref()); + assert!(body.get("instructions").is_none()); + if !custom_instructions.is_empty() { + assert_eq!(request.instructions_text(), custom_instructions); + } + assert!( + request + .message_input_texts("developer") + .iter() + .all(|text| !text.is_empty()) + ); assert!(!request.body_contains_text(BUNDLED_FRIENDLY_TEMPLATE)); Ok(()) diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 9bbd7e7ed6..12c1c42c2e 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -279,11 +279,11 @@ async fn prompt_tools_are_consistent_across_requests( ]); let body0 = req1.single_request().body_json(); - assert_eq!(body0["instructions"], serde_json::json!(base_instructions),); + assert_eq!(req1.single_request().instructions_text(), base_instructions); assert_tool_names(&body0, &expected_tools_names); let body1 = req2.single_request().body_json(); - assert_eq!(body1["instructions"], serde_json::json!(base_instructions),); + assert_eq!(req2.single_request().instructions_text(), base_instructions); assert_tool_names(&body1, &expected_tools_names); for request in [&req1, &req2] { @@ -359,22 +359,16 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - let body0 = req1.single_request().body_json(); - let instructions0 = body0["instructions"] - .as_str() - .expect("instructions should be a string"); + let instructions0 = req1.single_request().instructions_text(); assert!( instructions0.contains("You are"), "expected non-empty instructions" ); - let body1 = req2.single_request().body_json(); - let instructions1 = body1["instructions"] - .as_str() - .expect("instructions should be a string"); + let instructions1 = req2.single_request().instructions_text(); assert_eq!( - normalize_newlines(instructions1), - normalize_newlines(instructions0) + normalize_newlines(&instructions1), + normalize_newlines(&instructions0) ); Ok(()) @@ -434,11 +428,11 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests let input1 = body1["input"].as_array().expect("input array"); assert_eq!( input1.len(), - 3, - "expected permissions + cached contextual user prefix + user msg" + 4, + "expected base instructions + permissions + cached contextual user prefix + user msg" ); - let ui_text = input1[1]["content"][0]["text"] + let ui_text = input1[2]["content"][0]["text"] .as_str() .expect("ui message text"); assert!( @@ -447,17 +441,17 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests ); let cwd_str = config.cwd.to_string_lossy(); - let env_text = input1[1]["content"][1]["text"] + let env_text = input1[2]["content"][1]["text"] .as_str() .expect("environment context text"); assert_default_env_context(env_text, &cwd_str); assert_eq!( - input1[1]["content"][1]["type"].as_str(), + input1[2]["content"][1]["type"].as_str(), Some("input_text"), "expected environment context bundled after UI message in cached contextual message" ); assert_eq_without_metadata_or_item_ids( - input1[2].clone(), + input1[3].clone(), text_user_input("hello 1".to_string()), ); @@ -973,8 +967,9 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a let body1 = request1.body_json(); let body2 = request2.body_json(); - let expected_permissions_msg = body1["input"][0].clone(); - let expected_ui_msg = body1["input"][1].clone(); + let expected_base_instructions = body1["input"][0].clone(); + let expected_permissions_msg = body1["input"][1].clone(); + let expected_ui_msg = body1["input"][2].clone(); let default_cwd_lossy = default_cwd.to_string_lossy(); let expected_env_text_1 = expected_ui_msg["content"][1]["text"] @@ -993,6 +988,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a let expected_user_message_1 = text_user_input("hello 1".to_string()); let expected_input_1 = serde_json::Value::Array(vec![ + expected_base_instructions.clone(), expected_permissions_msg.clone(), expected_contextual_user_msg_1.clone(), expected_user_message_1.clone(), @@ -1001,6 +997,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a let expected_user_message_2 = text_user_input("hello 2".to_string()); let expected_input_2 = serde_json::Value::Array(vec![ + expected_base_instructions, expected_permissions_msg, expected_contextual_user_msg_1, expected_user_message_1, @@ -1110,8 +1107,9 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu let body1 = request1.body_json(); let body2 = request2.body_json(); - let expected_permissions_msg = body1["input"][0].clone(); - let expected_ui_msg = body1["input"][1].clone(); + let expected_base_instructions = body1["input"][0].clone(); + let expected_permissions_msg = body1["input"][1].clone(); + let expected_ui_msg = body1["input"][2].clone(); let expected_env_text_1 = expected_ui_msg["content"][1]["text"] .as_str() @@ -1127,6 +1125,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu ]); let expected_user_message_1 = text_user_input("hello 1".to_string()); let expected_input_1 = serde_json::Value::Array(vec![ + expected_base_instructions.clone(), expected_permissions_msg.clone(), expected_contextual_user_msg_1.clone(), expected_user_message_1.clone(), @@ -1168,6 +1167,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu ); let expected_user_message_2 = text_user_input("hello 2".to_string()); let expected_input_2 = serde_json::Value::Array(vec![ + expected_base_instructions, expected_permissions_msg, expected_contextual_user_msg_1, expected_user_message_1, diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index 17ef6b3297..bc3438b7d2 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -96,7 +96,7 @@ async fn disabled_update_plan_preserves_custom_catalog_instructions() -> Result< .await?; test.submit_turn("hello").await?; let request = response.single_request().body_json(); - assert_eq!(request["instructions"], INSTRUCTIONS); + assert_eq!(response.single_request().instructions_text(), INSTRUCTIONS); assert!(!request["tools"].to_string().contains("update_plan")); Ok(()) } @@ -1586,7 +1586,7 @@ async fn model_catalog_url_supplies_conversation_model_and_instructions() -> Res test.submit_turn("hello").await?; let request = response.single_request().body_json(); assert_eq!(request["model"], "gateway-conversation"); - assert_eq!(request["instructions"], instructions); + assert_eq!(response.single_request().instructions_text(), instructions); assert_eq!(request["text"].get("verbosity"), None); Ok(()) } diff --git a/codex-rs/core/tests/suite/responses_lite.rs b/codex-rs/core/tests/suite/responses_lite.rs index c35be9664b..d109c4154c 100644 --- a/codex-rs/core/tests/suite/responses_lite.rs +++ b/codex-rs/core/tests/suite/responses_lite.rs @@ -89,8 +89,10 @@ fn additional_tools(body: &Value) -> Result<&[Value]> { .context("additional_tools tools should be an array") } +#[test_case::test_case(false; "standard")] +#[test_case::test_case(true; "responses_lite")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn responses_lite_uses_input_items_for_instructions_and_tools() -> Result<()> { +async fn base_instructions_use_input_items(responses_lite: bool) -> Result<()> { skip_if_no_network!(Ok(())); let server = responses::start_mock_server().await; @@ -105,8 +107,8 @@ async fn responses_lite_uses_input_items_for_instructions_and_tools() -> Result< let builder = || { test_codex() - .with_model_info_override("gpt-5.4", |model_info| { - model_info.use_responses_lite = true; + .with_model_info_override("gpt-5.4", move |model_info| { + model_info.use_responses_lite = responses_lite; model_info.tool_mode = Some(ToolMode::CodeMode); }) .with_config(|config| { @@ -120,27 +122,21 @@ async fn responses_lite_uses_input_items_for_instructions_and_tools() -> Result< let body = response_mock.single_request().body_json(); assert!(body.get("instructions").is_none()); - assert!(body.get("tools").is_none()); let input = body["input"] .as_array() .context("Responses request input should be an array")?; - assert_eq!(input[0]["type"], "additional_tools"); - assert_eq!(input[0]["role"], "developer"); + let instructions_index = usize::from(responses_lite); + let instructions = &input[instructions_index]; assert!( - input[0]["id"] - .as_str() - .is_some_and(|id| id.starts_with("at_")) - ); - assert!( - input[1]["id"] + instructions["id"] .as_str() .is_some_and(|id| id.starts_with("msg_")) ); assert_eq!( - input[1], - serde_json::json!({ - "id": input[1]["id"], + instructions, + &serde_json::json!({ + "id": instructions["id"], "type": "message", "role": "developer", "content": [{ @@ -153,20 +149,39 @@ async fn responses_lite_uses_input_items_for_instructions_and_tools() -> Result< }) ); - let tools = additional_tools(&body)?; - let functions_namespaces = tools - .iter() - .filter(|tool| tool["type"] == "namespace" && tool["name"] == "functions") - .collect::>(); - assert_eq!(functions_namespaces.len(), 1); - assert_eq!(functions_namespaces[0]["description"], ""); - assert!(has_namespaced_tool(tools, "functions", "wait")); - assert!(has_namespaced_tool(tools, "functions", "exec")); - assert!( - tools + let tools = if responses_lite { + assert!(body.get("tools").is_none()); + assert_eq!(input[0]["type"], "additional_tools"); + assert_eq!(input[0]["role"], "developer"); + assert!( + input[0]["id"] + .as_str() + .is_some_and(|id| id.starts_with("at_")) + ); + additional_tools(&body)? + } else { + body["tools"] + .as_array() + .context("standard Responses tools should remain at the top level")? + }; + if responses_lite { + let functions_namespaces = tools .iter() - .all(|tool| { !matches!(tool["type"].as_str(), Some("function" | "custom")) }) - ); + .filter(|tool| tool["type"] == "namespace" && tool["name"] == "functions") + .collect::>(); + assert_eq!(functions_namespaces.len(), 1); + assert_eq!(functions_namespaces[0]["description"], ""); + assert!(has_namespaced_tool(tools, "functions", "wait")); + assert!(has_namespaced_tool(tools, "functions", "exec")); + assert!( + tools + .iter() + .all(|tool| { !matches!(tool["type"].as_str(), Some("function" | "custom")) }) + ); + } else { + assert!(tools.iter().any(|tool| tool["name"] == "exec")); + assert!(tools.iter().any(|tool| tool["name"] == "wait")); + } let client_metadata = body["client_metadata"] .as_object() .context("Responses request should include client metadata")?; @@ -185,7 +200,14 @@ async fn responses_lite_uses_input_items_for_instructions_and_tools() -> Result< .await; let resumed = builder().restart(&server, &test).await?; resumed.submit_turn("continue").await?; - assert_eq!(&followup.single_request().input()[..2], &input[..2]); + assert_eq!( + &followup.single_request().input()[..=instructions_index], + &input[..=instructions_index] + ); + assert_eq!( + followup.single_request().body_json()["tools"], + body["tools"] + ); Ok(()) } diff --git a/codex-rs/core/tests/suite/resume.rs b/codex-rs/core/tests/suite/resume.rs index 1d3ecde41b..c5f31071a7 100644 --- a/codex-rs/core/tests/suite/resume.rs +++ b/codex-rs/core/tests/suite/resume.rs @@ -148,12 +148,7 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> { }])) .await?; wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await; - let initial_body = initial_mock.single_request().body_json(); - let initial_instructions = initial_body - .get("instructions") - .and_then(|v| v.as_str()) - .unwrap_or_default() - .to_string(); + let initial_instructions = initial_mock.single_request().instructions_text(); let resumed_mock = mount_sse_sequence( &server, diff --git a/codex-rs/core/tests/suite/review.rs b/codex-rs/core/tests/suite/review.rs index a8b255ce96..19b3b44190 100644 --- a/codex-rs/core/tests/suite/review.rs +++ b/codex-rs/core/tests/suite/review.rs @@ -1261,8 +1261,8 @@ async fn review_input_isolated_from_parent_history() { "user message should only contain the raw review prompt" ); - // Ensure the REVIEW_PROMPT rubric is sent via instructions. - let instructions = body["instructions"].as_str().expect("instructions string"); + // Ensure the REVIEW_PROMPT rubric is sent as the base instructions. + let instructions = request.instructions_text(); assert_eq!(instructions, REVIEW_PROMPT); // Also verify that a user interruption note was recorded in the rollout. diff --git a/codex-rs/core/tests/suite/scenarios_incremental_tools.rs b/codex-rs/core/tests/suite/scenarios_incremental_tools.rs index 57c0c3f3ee..0d20b4101f 100644 --- a/codex-rs/core/tests/suite/scenarios_incremental_tools.rs +++ b/codex-rs/core/tests/suite/scenarios_incremental_tools.rs @@ -100,7 +100,7 @@ async fn incremental_tools_append_changed_catalog_without_rewriting_history( .is_some_and(|tools| !tools.is_empty()) ); assert_eq!( - body["instructions"], + request.instructions_text(), "Use the available tools to help the user." ); } diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__additional_context__additional_context_simple_input.snap b/codex-rs/core/tests/suite/snapshots/all__suite__additional_context__additional_context_simple_input.snap index a1e546466b..7512e45c0c 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__additional_context__additional_context_simple_input.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__additional_context__additional_context_simple_input.snap @@ -7,10 +7,12 @@ Scenario: additional context is inserted before the user turn input. ## Window 1 -- request 1 (turn; Request) -- 00:message/developer: - + 01:message/developer: + +02:message/developer: run one -02:message/user: - tab one 03:message/user: + tab one +04:message/user: inspect the active tab diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__compact__compaction_cold_resume_after_completion.snap b/codex-rs/core/tests/suite/snapshots/all__suite__compact__compaction_cold_resume_after_completion.snap index 7fae463115..c836a85c94 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__compact__compaction_cold_resume_after_completion.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__compact__compaction_cold_resume_after_completion.snap @@ -1,16 +1,17 @@ --- source: core/tests/suite/compact.rs -assertion_line: 4537 expression: "format_history_snapshot(\"Real paginated mid-turn compaction and cold resume from the bounded suffix.\",\n&requests,)" --- Scenario: Real paginated mid-turn compaction and cold resume from the bounded suffix. ## Window 1 -- request 1 (turn) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/developer: +02:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. At the start of your turn, you are the active agent. @@ -28,17 +29,19 @@ Scenario: Real paginated mid-turn compaction and cold resume from the bounded su There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f] -02:message/developer: +03:message/developer: -03:message/user: +04:message/user: function call limit push ## Window 2 (after request 1: settings changed (parallel_tool_calls, tools)) -- request 2 (compaction) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/developer: +02:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. At the start of your turn, you are the active agent. @@ -56,21 +59,23 @@ Scenario: Real paginated mid-turn compaction and cold resume from the bounded su There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f] -02:message/developer: +03:message/developer: -03:message/user: +04:message/user: function call limit push -04:function_call/test_tool:{} -05:function_call_output:unsupported call: test_tool -06:message/user: +05:function_call/test_tool:{} +06:function_call_output:unsupported call: test_tool +07:message/user: -## Window 3 (after request 2: input diverged at item 04, settings changed (parallel_tool_calls, tools)) +## Window 3 (after request 2: input diverged at item 05, settings changed (parallel_tool_calls, tools)) -- request 3 (turn) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/developer: +02:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. At the start of your turn, you are the active agent. @@ -88,15 +93,15 @@ Scenario: Real paginated mid-turn compaction and cold resume from the bounded su There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f] -02:message/developer: +03:message/developer: -03:message/user: - function call limit push 04:message/user: + function call limit push +05:message/user: AUTO_SUMMARY -- request 4 (turn) -- -05:message/assistant: +06:message/assistant: continued -06:message/user: +07:message/user: continue after cold resume diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__compact__manual_compact_with_history_shapes.snap b/codex-rs/core/tests/suite/snapshots/all__suite__compact__manual_compact_with_history_shapes.snap index a62c82bf67..bf1959455c 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__compact__manual_compact_with_history_shapes.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__compact__manual_compact_with_history_shapes.snap @@ -7,69 +7,79 @@ Scenario: Manual /compact with prior user history compacts existing history and ## Window 1 -- request 1 (turn) -- 00:message/developer: - -01:message/user: - -02:message/user: - first manual turn - -## Window 2 (after request 1: settings changed (parallel_tool_calls, tools)) --- request 2 (compaction) -- -00:message/developer: - -01:message/user: - -02:message/user: - first manual turn -03:message/assistant: - FIRST_REPLY -04:message/user: - - -## Window 3 (after request 2: input diverged at item 03, settings changed (parallel_tool_calls, tools)) --- request 3 (turn) -- -00:message/developer: - -01:message/user: - -02:message/user: - first manual turn -03:message/user: - - FIRST_MANUAL_SUMMARY -04:message/user: - second manual turn - -## Window 4 (after request 3: settings changed (parallel_tool_calls, tools)) --- request 4 (compaction) -- -00:message/developer: - -01:message/user: - -02:message/user: - first manual turn -03:message/user: - - FIRST_MANUAL_SUMMARY -04:message/user: - second manual turn -05:message/assistant: - SECOND_LARGE_REPLY -06:message/user: - - -## Window 5 (after request 4: input diverged at item 00, settings changed (parallel_tool_calls, tools)) --- request 5 (turn) -- -00:message/user: - first manual turn + 01:message/developer: 02:message/user: 03:message/user: - second manual turn + first manual turn + +## Window 2 (after request 1: settings changed (parallel_tool_calls, tools)) +-- request 2 (compaction) -- +00:message/developer: + +01:message/developer: + +02:message/user: + +03:message/user: + first manual turn +04:message/assistant: + FIRST_REPLY +05:message/user: + + +## Window 3 (after request 2: input diverged at item 04, settings changed (parallel_tool_calls, tools)) +-- request 3 (turn) -- +00:message/developer: + +01:message/developer: + +02:message/user: + +03:message/user: + first manual turn 04:message/user: - SECOND_MANUAL_SUMMARY + FIRST_MANUAL_SUMMARY 05:message/user: + second manual turn + +## Window 4 (after request 3: settings changed (parallel_tool_calls, tools)) +-- request 4 (compaction) -- +00:message/developer: + +01:message/developer: + +02:message/user: + +03:message/user: + first manual turn +04:message/user: + + FIRST_MANUAL_SUMMARY +05:message/user: + second manual turn +06:message/assistant: + SECOND_LARGE_REPLY +07:message/user: + + +## Window 5 (after request 4: input diverged at item 01, settings changed (parallel_tool_calls, tools)) +-- request 5 (turn) -- +00:message/developer: + +01:message/user: + first manual turn +02:message/developer: + +03:message/user: + +04:message/user: + second manual turn +05:message/user: + + SECOND_MANUAL_SUMMARY +06:message/user: post compact follow-up diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__compact__manual_compact_without_prev_user_shapes.snap b/codex-rs/core/tests/suite/snapshots/all__suite__compact__manual_compact_without_prev_user_shapes.snap index 805b72dae8..8f6cfd5b5a 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__compact__manual_compact_without_prev_user_shapes.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__compact__manual_compact_without_prev_user_shapes.snap @@ -6,17 +6,21 @@ Scenario: Manual /compact with no prior user turn currently still issues a compa ## Window 1 -- request 1 (compaction; Local Compaction Request) -- -00:message/user: +00:message/developer: + +01:message/user: -## Window 2 (after request 1: input diverged at item 00, settings changed (parallel_tool_calls, tools)) +## Window 2 (after request 1: input diverged at item 01, settings changed (parallel_tool_calls, tools)) -- request 2 (turn; Local Post-Compaction History Layout) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: MANUAL_EMPTY_SUMMARY -03:message/user: +04:message/user: AFTER_MANUAL_EMPTY_COMPACT diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__compact__mid_turn_compaction_shapes.snap b/codex-rs/core/tests/suite/snapshots/all__suite__compact__mid_turn_compaction_shapes.snap index 1fbf7dfb04..8dff5d6c87 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__compact__mid_turn_compaction_shapes.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__compact__mid_turn_compaction_shapes.snap @@ -7,33 +7,39 @@ Scenario: True mid-turn continuation compaction after tool output: compact reque ## Window 1 -- request 1 (turn) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: function call limit push ## Window 2 (after request 1: settings changed (parallel_tool_calls, tools)) -- request 2 (compaction) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: function call limit push -03:function_call/test_tool:{} -04:function_call_output:unsupported call: test_tool -05:message/user: +04:function_call/test_tool:{} +05:function_call_output:unsupported call: test_tool +06:message/user: -## Window 3 (after request 2: input diverged at item 03, settings changed (parallel_tool_calls, tools)) +## Window 3 (after request 2: input diverged at item 04, settings changed (parallel_tool_calls, tools)) -- request 3 (turn) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: - function call limit push + 03:message/user: + function call limit push +04:message/user: AUTO_SUMMARY diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_sampling_model_switch_compaction_shapes.snap b/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_sampling_model_switch_compaction_shapes.snap index d20f5f5087..f24793f618 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_sampling_model_switch_compaction_shapes.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_sampling_model_switch_compaction_shapes.snap @@ -7,36 +7,42 @@ Scenario: Pre-sampling compaction on model switch to a smaller context window: c ## Window 1 -- request 1 (turn; Initial Request (Previous Model)) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: before switch ## Window 2 (after request 1: settings changed (parallel_tool_calls, tools)) -- request 2 (compaction; Pre-sampling Compaction Request) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: before switch -03:message/assistant: +04:message/assistant: before switch -04:message/user: +05:message/user: -## Window 3 (after request 2: input diverged at item 00, settings changed (model, parallel_tool_calls, tools)) +## Window 3 (after request 2: input diverged at item 01, settings changed (model, parallel_tool_calls, tools)) -- request 3 (turn; Post-Compaction Follow-up Request (Next Model)) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/user: - 02:message/user: - before switch + 03:message/user: + before switch +04:message/user: PRE_SAMPLING_SUMMARY -04:message/user: +05:message/user: after switch diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_context_window_exceeded_shapes.snap b/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_context_window_exceeded_shapes.snap index ea822df49e..c7fbb0ca90 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_context_window_exceeded_shapes.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_context_window_exceeded_shapes.snap @@ -7,12 +7,14 @@ Scenario: Pre-turn auto-compaction context-window failure: compaction request ex ## Window 1 -- request 1 (compaction; Local Compaction Request (Incoming User Excluded)) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: USER_ONE -03:message/assistant: +04:message/assistant: FIRST_REPLY -04:message/user: +05:message/user: diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_including_incoming_shapes.snap b/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_including_incoming_shapes.snap index 1d92f720c1..36ad18f9e1 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_including_incoming_shapes.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_including_incoming_shapes.snap @@ -7,47 +7,53 @@ Scenario: Pre-turn auto-compaction uses the prior context without the incoming u ## Window 1 -- request 1 (turn) -- 00:message/developer: - -01:message/user: - -02:message/user: - USER_ONE --- request 2 (turn) -- -03:message/assistant: - FIRST_REPLY -04:message/user: - USER_TWO - -## Window 2 (after request 2: settings changed (parallel_tool_calls, tools)) --- request 3 (compaction) -- -00:message/developer: - -01:message/user: - -02:message/user: - USER_ONE -03:message/assistant: - FIRST_REPLY -04:message/user: - USER_TWO -05:message/assistant: - SECOND_REPLY -06:message/user: - - -## Window 3 (after request 3: input diverged at item 00, settings changed (parallel_tool_calls, tools)) --- request 4 (turn) -- -00:message/user: - USER_ONE + 01:message/developer: 02:message/user: 03:message/user: + USER_ONE +-- request 2 (turn) -- +04:message/assistant: + FIRST_REPLY +05:message/user: USER_TWO + +## Window 2 (after request 2: settings changed (parallel_tool_calls, tools)) +-- request 3 (compaction) -- +00:message/developer: + +01:message/developer: + +02:message/user: + +03:message/user: + USER_ONE +04:message/assistant: + FIRST_REPLY +05:message/user: + USER_TWO +06:message/assistant: + SECOND_REPLY +07:message/user: + + +## Window 3 (after request 3: input diverged at item 01, settings changed (parallel_tool_calls, tools)) +-- request 4 (turn) -- +00:message/developer: + +01:message/user: + USER_ONE +02:message/developer: + +03:message/user: + 04:message/user: + USER_TWO +05:message/user: PRE_TURN_SUMMARY -05:message/user[2]: +06:message/user[2]: [01] [02] USER_THREE diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_strips_incoming_model_switch_shapes.snap b/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_strips_incoming_model_switch_shapes.snap index 2aed44c7f1..6d81579582 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_strips_incoming_model_switch_shapes.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__compact__pre_turn_compaction_strips_incoming_model_switch_shapes.snap @@ -7,36 +7,42 @@ Scenario: Pre-turn compaction during model switch (without pre-sampling model-sw ## Window 1 -- request 1 (turn; Initial Request (Previous Model)) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: BEFORE_SWITCH_USER ## Window 2 (after request 1: settings changed (model, parallel_tool_calls, reasoning, text, tools)) -- request 2 (compaction; Local Compaction Request) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: BEFORE_SWITCH_USER -03:message/assistant: +04:message/assistant: BEFORE_SWITCH_REPLY -04:message/user: +05:message/user: -## Window 3 (after request 2: input diverged at item 00, settings changed (parallel_tool_calls, tools)) +## Window 3 (after request 2: input diverged at item 01, settings changed (parallel_tool_calls, tools)) -- request 3 (turn; Local Post-Compaction History Layout) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/user: - 02:message/user: - BEFORE_SWITCH_USER + 03:message/user: + BEFORE_SWITCH_USER +04:message/user: PRETURN_SWITCH_SUMMARY -04:message/user: +05:message/user: AFTER_SWITCH_USER diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__mcp_tool_exposure__deferred_tools_initial_unchanged_and_removed.snap b/codex-rs/core/tests/suite/snapshots/all__suite__mcp_tool_exposure__deferred_tools_initial_unchanged_and_removed.snap index 9bc2a20314..251ab28ace 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__mcp_tool_exposure__deferred_tools_initial_unchanged_and_removed.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__mcp_tool_exposure__deferred_tools_initial_unchanged_and_removed.snap @@ -6,47 +6,51 @@ Scenario: Initially available deferred tools remain unchanged until disabling Ca ## Window 1 -- request 1 (turn; Initially available) -- -00:message/developer[3]: +00:message/developer: + +01:message/developer[3]: [01] [02] [03] Deferred tool namespaces: - mcp__codex_apps__calendar: Plan events and manage your calendar. -01:message/user: - 02:message/user: + +03:message/user: inspect initially available deferred tools -- request 2 (turn; Unchanged follow-up) -- -03:message/assistant: +04:message/assistant: done -04:message/user: +05:message/user: inspect unchanged deferred tools ## Window 2 (after request 2: settings changed (tools)) -- request 3 (turn; Removed and empty) -- -00:message/developer[3]: +00:message/developer: + +01:message/developer[3]: [01] [02] [03] Deferred tool namespaces: - mcp__codex_apps__calendar: Plan events and manage your calendar. -01:message/user: - 02:message/user: + +03:message/user: inspect initially available deferred tools -03:message/assistant: +04:message/assistant: done -04:message/user: +05:message/user: inspect unchanged deferred tools -05:message/assistant: +06:message/assistant: done -06:message/developer: +07:message/developer: Removed deferred tool namespaces: - mcp__codex_apps__calendar: Plan events and manage your calendar. No deferred tool namespaces remain. -07:message/user: +08:message/user: inspect removed deferred tools diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__mcp_tool_exposure__deferred_tools_recover_during_sampling.snap b/codex-rs/core/tests/suite/snapshots/all__suite__mcp_tool_exposure__deferred_tools_recover_during_sampling.snap index 953baff7b1..0a291b1e6f 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__mcp_tool_exposure__deferred_tools_recover_during_sampling.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__mcp_tool_exposure__deferred_tools_recover_during_sampling.snap @@ -6,20 +6,22 @@ Scenario: Deferred namespaces appear after Apps recovers between sampling reques ## Window 1 -- request 1 (turn; Apps unavailable) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] Deferred tool namespaces: - multi_agent_v1: Tools for spawning and managing sub-agents. -01:message/user: - 02:message/user: + +03:message/user: use an app after it recovers -- request 2 (turn; Apps recovered) -- -03:function_call/request_user_input:{"questions":[{"header":"Continue","id":"continue","options":[{"description":"Continue the test.","label...:"No"}],"question":"Continue after Apps recovers?"}]} [hash=31004ebd8cd711b2] -04:function_call_output:{"answers":{"continue":{"answers":["Yes (Recommended)"]}}} -05:message/developer[2]: +04:function_call/request_user_input:{"questions":[{"header":"Continue","id":"continue","options":[{"description":"Continue the test.","label...:"No"}],"question":"Continue after Apps recovers?"}]} [hash=31004ebd8cd711b2] +05:function_call_output:{"answers":{"continue":{"answers":["Yes (Recommended)"]}}} +06:message/developer[2]: [01] [02] Added deferred tool namespaces: diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__mcp_tool_exposure__deferred_tools_resume_without_duplicate_update.snap b/codex-rs/core/tests/suite/snapshots/all__suite__mcp_tool_exposure__deferred_tools_resume_without_duplicate_update.snap index 768dcfd6c1..9340863c09 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__mcp_tool_exposure__deferred_tools_resume_without_duplicate_update.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__mcp_tool_exposure__deferred_tools_resume_without_duplicate_update.snap @@ -6,34 +6,38 @@ Scenario: Persisted deferred tools remain unchanged after resuming the thread. ## Window 1 -- request 1 (items; Before resume) -- -00:message/developer[3]: +00:message/developer: + +01:message/developer[3]: [01] [02] [03] Deferred tool namespaces: - mcp__codex_apps__calendar: Plan events and manage your calendar. -01:message/user: - 02:message/user: + +03:message/user: inspect deferred tools before resume ## Window 2 (after request 1: settings unavailable) -- request 2 (items; After resume) -- -00:message/developer[3]: +00:message/developer: + +01:message/developer[3]: [01] [02] [03] Deferred tool namespaces: - mcp__codex_apps__calendar: Plan events and manage your calendar. -01:message/user: - 02:message/user: - inspect deferred tools before resume -03:message/assistant: - done -04:message/user: +03:message/user: + inspect deferred tools before resume +04:message/assistant: + done 05:message/user: + +06:message/user: inspect unchanged deferred tools after resume diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_cwd_change_refreshes_agents.snap b/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_cwd_change_refreshes_agents.snap index 0438c5d271..dc8e7eb97a 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_cwd_change_refreshes_agents.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_cwd_change_refreshes_agents.snap @@ -6,19 +6,21 @@ Scenario: Second turn changes cwd to a directory with different AGENTS.md and re ## Window 1 -- request 1 (turn; First Request (agents_one)) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/user[2]: +02:message/user[2]: [01] [02] -02:message/user: +03:message/user: first turn in agents_one -- request 2 (turn; Second Request (agents_two cwd)) -- -03:message/assistant: +04:message/assistant: turn one complete -04:message/user[2]: +05:message/user[2]: [01] [02] -05:message/user: +06:message/user: second turn in agents_two diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_resume_override_matches_rollout_model.snap b/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_resume_override_matches_rollout_model.snap index 793631f325..c0a4a85e84 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_resume_override_matches_rollout_model.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_resume_override_matches_rollout_model.snap @@ -6,17 +6,19 @@ Scenario: First post-resume turn where pre-turn override sets model to rollout m ## Window 1 -- request 1 (turn; Last Request Before Resume) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/user: - 02:message/user: + +03:message/user: seed resume history -- request 2 (turn; First Request After Resume + Override) -- -03:message/assistant: +04:message/assistant: recorded before resume -04:message/user: - 05:message/user: + +06:message/user: first resumed turn after model override diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_resume_with_personality_change.snap b/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_resume_with_personality_change.snap index f07df53f8e..95d83ba66b 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_resume_with_personality_change.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_resume_with_personality_change.snap @@ -6,30 +6,34 @@ Scenario: First post-resume turn where resumed config model differs from rollout ## Window 1 -- request 1 (turn; Last Request Before Resume) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/user: - 02:message/user: + +03:message/user: seed resume history ## Window 2 (after request 1: settings changed (model)) -- request 2 (turn; First Request After Resume) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/user: - 02:message/user: - seed resume history -03:message/assistant: - recorded before resume -04:message/developer: - -05:message/developer: - -06:message/user: +03:message/user: + seed resume history +04:message/assistant: + recorded before resume +05:message/developer: + +06:message/developer: + 07:message/user: + +08:message/user: resume and change personality diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_turn_overrides.snap b/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_turn_overrides.snap index 31becabcbe..679ded9c30 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_turn_overrides.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__model_visible_layout__model_visible_layout_turn_overrides.snap @@ -6,19 +6,21 @@ Scenario: Second turn changes cwd and approval policy while keeping model consta ## Window 1 -- request 1 (turn; First Request (Baseline)) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/user: - 02:message/user: + +03:message/user: first turn -- request 2 (turn; Second Request (Turn Overrides)) -- -03:message/assistant: +04:message/assistant: turn one complete -04:message/developer: +05:message/developer: -05:message/user: - 06:message/user: + +07:message/user: second turn with context updates diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__pending_input__pending_input_queued_mail_after_commentary.snap b/codex-rs/core/tests/suite/snapshots/all__suite__pending_input__pending_input_queued_mail_after_commentary.snap index 005c98b51c..5811b895af 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__pending_input__pending_input_queued_mail_after_commentary.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__pending_input__pending_input_queued_mail_after_commentary.snap @@ -7,12 +7,14 @@ Scenario: /responses POST bodies with pending input ## Window 1 -- request 1 (request; First request) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: first prompt -- request 2 (request; Second request) -- -03:message/assistant: +04:message/assistant: first answer -04:agent_message +05:agent_message diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__pending_input__pending_input_queued_mail_after_reasoning.snap b/codex-rs/core/tests/suite/snapshots/all__suite__pending_input__pending_input_queued_mail_after_reasoning.snap index 2613053248..2ef028ff37 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__pending_input__pending_input_queued_mail_after_reasoning.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__pending_input__pending_input_queued_mail_after_reasoning.snap @@ -7,11 +7,13 @@ Scenario: /responses POST bodies with pending input ## Window 1 -- request 1 (request; First request) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: first prompt -- request 2 (request; Second request) -- -03:reasoning:summary=thinking:encrypted=true -04:agent_message +04:reasoning:summary=thinking:encrypted=true +05:agent_message diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__pending_input__pending_input_user_input_no_preempt_after_reasoning.snap b/codex-rs/core/tests/suite/snapshots/all__suite__pending_input__pending_input_user_input_no_preempt_after_reasoning.snap index c0276b691e..2ffaedb0d6 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__pending_input__pending_input_user_input_no_preempt_after_reasoning.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__pending_input__pending_input_user_input_no_preempt_after_reasoning.snap @@ -7,16 +7,18 @@ Scenario: /responses POST bodies with pending input ## Window 1 -- request 1 (request; First request) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: first prompt -- request 2 (request; Second request) -- -03:reasoning:summary=thinking:encrypted=true -04:function_call/shell:{"command":"echo preserved tool call"} -05:message/assistant: +04:reasoning:summary=thinking:encrypted=true +05:function_call/shell:{"command":"echo preserved tool call"} +06:message/assistant: first answer -06:function_call_output:unsupported call: shell -07:message/user: +07:function_call_output:unsupported call: shell +08:message/user: second prompt diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__request_plugin_install__startup_recommendations_developer_context.snap b/codex-rs/core/tests/suite/snapshots/all__suite__request_plugin_install__startup_recommendations_developer_context.snap index 572158ec39..015696faa3 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__request_plugin_install__startup_recommendations_developer_context.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__request_plugin_install__startup_recommendations_developer_context.snap @@ -6,7 +6,9 @@ Scenario: Startup recommendations share the developer message with other context ## Window 1 -- request 1 (turn) -- -00:message/developer[3]: +00:message/developer: + +01:message/developer[3]: [01] [02] [03] @@ -14,7 +16,7 @@ Scenario: Startup recommendations share the developer message with other context - GitHub (github@openai-curated-remote) -01:message/user: - 02:message/user: + +03:message/user: suggest a plugin diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__agent_message_board__agent_message_board_context.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__agent_message_board__agent_message_board_context.snap index a68b08e292..6360883eb4 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__agent_message_board__agent_message_board_context.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__agent_message_board__agent_message_board_context.snap @@ -1,15 +1,16 @@ --- source: core/tests/suite/scenarios_agent_message_board.rs -assertion_line: 88 -expression: "context_snapshot::format_context_snapshot(\"An active agent posts a shared decision and fetches the text without a self-notification.\",\n&bodies.iter().map(SnapshotEntry::body).collect::>(), &ContextSnapshotOptions::default().rewrite_known_segments(),)" +expression: "context_snapshot::format_context_snapshot(\"An active agent posts a shared decision and fetches the text without a self-notification.\",\n&bodies.iter().map(SnapshotEntry::body).collect::>(),\n&ContextSnapshotOptions::default().rewrite_known_segments(),)" --- Scenario: An active agent posts a shared decision and fetches the text without a self-notification. ## Window 1 -- request 1 (request) -- 00:message/developer: - + 01:message/developer: + +02:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. At the start of your turn, you are the active agent. @@ -27,15 +28,15 @@ Scenario: An active agent posts a shared decision and fetches the text without a There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f] -02:message/developer: +03:message/developer: -03:message/user: - 04:message/user: + +05:message/user: Record the decision on the board, notify me, and read it. -- request 2 (request) -- -05:function_call/collaboration.post:{"agents_to_notify":["/root"],"new_channel_name":"design","text":"A shared decision."} -06:function_call_output:{"author":"/root","channel_name":"design","created_at":"2026-09-18T12:00:00Z","message_id":"","thread_id":""} +06:function_call/collaboration.post:{"agents_to_notify":["/root"],"new_channel_name":"design","text":"A shared decision."} +07:function_call_output:{"author":"/root","channel_name":"design","created_at":"2026-09-18T12:00:00Z","message_id":"","thread_id":""} -- request 3 (request) -- -07:function_call/collaboration.search_posts:{"channel_name":"design","query":"decision"} -08:function_call_output:{"has_more":false,"n_returned":1,"next_cursor":null,"results":[{"author":"/root","channel_name":"design"...ecision.","thread_id":"","truncated":false}]} [hash=facdb1e4f66cc5b9] +08:function_call/collaboration.search_posts:{"channel_name":"design","query":"decision"} +09:function_call_output:{"has_more":false,"n_returned":1,"next_cursor":null,"results":[{"author":"/root","channel_name":"design"...ecision.","thread_id":"","truncated":false}]} [hash=facdb1e4f66cc5b9] diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__agent_message_board__remote__remote_board_tools.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__agent_message_board__remote__remote_board_tools.snap index 9fb63c8087..c088fe3860 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__agent_message_board__remote__remote_board_tools.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__agent_message_board__remote__remote_board_tools.snap @@ -6,15 +6,17 @@ Scenario: Remote board tools and active-turn notifications share the existing se ## Window 1 -- request 1 (request) -- -00:message/user: +00:message/developer: + +01:message/user: This turn will be interrupted during notification setup. -01:message/developer: +02:message/developer: The previous turn was interrupted on purpose. Any running unified exec processes may still be running in...mands were aborted, they may have partially executed. [hash=cee256d92b55ad65] -02:message/developer: - 03:message/developer: + +04:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. At the start of your turn, you are the active agent. @@ -32,15 +34,15 @@ Scenario: Remote board tools and active-turn notifications share the existing se There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f] -04:message/developer: +05:message/developer: -05:message/user: - 06:message/user: + +07:message/user: Post the remote decision. -- request 2 (request) -- -07:function_call/collaboration.post:{"new_channel_name":"design","text":"A remote decision."} -08:function_call/collaboration.wait_agent:{} -09:function_call_output:{"author":"/root","channel_name":"design","created_at":"2026-09-18T12:00:00Z","message_id":"","thread_id":""} -10:function_call_output:{"message":"Wait completed.","timed_out":false} -11:agent_message +08:function_call/collaboration.post:{"new_channel_name":"design","text":"A remote decision."} +09:function_call/collaboration.wait_agent:{} +10:function_call_output:{"author":"/root","channel_name":"design","created_at":"2026-09-18T12:00:00Z","message_id":"","thread_id":""} +11:function_call_output:{"message":"Wait completed.","timed_out":false} +12:agent_message diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__app_tool_exposure_CodeModeOnly.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__app_tool_exposure_CodeModeOnly.snap index 890bc6a0ad..708b4a90b4 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__app_tool_exposure_CodeModeOnly.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__app_tool_exposure_CodeModeOnly.snap @@ -7,7 +7,6 @@ Scenario: A non-deferred connector is called through code mode while another con ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: model: "gpt-5.5" parallel_tool_calls: true prompt_cache_key: "" @@ -22,16 +21,18 @@ Settings: - function/request_user_input: Request user input for one to three short questions and wait for the response. This tool is only ...; args=[questions]; hash=64a729b25c424fc6 - web_search; hash=e214d4db1bc49e89 -- request 1 (turn) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/user: - 02:message/user: + +03:message/user: Look up my calendar. -- request 2 (turn) -- -03:custom_tool_call/exec:text((await tools.mcp__codex_apps__calendar__lookup({})).content[0].text); -04:custom_tool_call_output:Script completed +04:custom_tool_call/exec:text((await tools.mcp__codex_apps__calendar__lookup({})).content[0].text); +05:custom_tool_call_output:Script completed Wall time seconds Output: | calendar-lookup-ok diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__code_mode_catalog_messages.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__code_mode_catalog_messages.snap index b3a706ecb8..9eeace188a 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__code_mode_catalog_messages.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__code_mode_catalog_messages.snap @@ -7,23 +7,6 @@ Scenario: Catalog Code Mode instructions and wait parameter descriptions accompa ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803] - - # When to ask the user for permission - - Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388] - - User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c] - - - - - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list. - - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from. - - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn. - - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task. - - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0] - - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e] - model: "gpt-6-astra" parallel_tool_calls: true prompt_cache_key: "" @@ -48,7 +31,25 @@ Settings: - function/wait_agent - web_search; hash=e214d4db1bc49e89 -- request 1 (turn) -- -00:message/developer[2]: +00:message/developer: + You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803] + + # When to ask the user for permission + + Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388] + + User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c] + + + + - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list. + - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from. + - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn. + - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task. + - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0] + - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e] + +01:message/developer[2]: [01] Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b] Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected. @@ -69,7 +70,7 @@ Settings: Never use the `request_user_input` tool for permission requests or permission-related escalations. -01:message/developer: +02:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. At the start of your turn, you are the active agent. @@ -87,9 +88,9 @@ Settings: There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string. [hash=c7f52e90688299ce] -02:message/developer: +03:message/developer: Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] -03:message/user: +04:message/user: @@ -97,17 +98,17 @@ Settings: <...e="unrestricted" /> [hash=d40b2f45cb028888] -04:message/user: +05:message/user: Start a code cell, yield its initial output, then wait for its completion. -- request 2 (turn) -- -05:custom_tool_call/exec:text('started'); yield_control(); text('finished'); -06:custom_tool_call_output:Script running with cell ID 1 +06:custom_tool_call/exec:text('started'); yield_control(); text('finished'); +07:custom_tool_call_output:Script running with cell ID 1 Wall time seconds Output: | started -- request 3 (turn) -- -07:function_call/functions.wait:{"cell_id":"1"} -08:function_call_output:Script completed +08:function_call/functions.wait:{"cell_id":"1"} +09:function_call_output:Script completed Wall time seconds Output: | finished diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__code_mode_mcp_schema_limits.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__code_mode_mcp_schema_limits.snap index 64226b9179..5f527de147 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__code_mode_mcp_schema_limits.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__code_mode_mcp_schema_limits.snap @@ -7,7 +7,6 @@ Scenario: Code Mode exposes explicit MCP schema limits in its prompt and runtime ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: model: "gpt-5.4" parallel_tool_calls: true prompt_cache_key: "" @@ -23,13 +22,15 @@ Settings: - web_search; hash=e214d4db1bc49e89 -- request 1 (turn) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: inspect the search tool declarations -- request 2 (turn) -- -03:custom_tool_call/exec: +04:custom_tool_call/exec: const results = ["default_schema", "expanded_schema"].map(server => { const description = ALL_TOOLS.find(({name}) => name === `mcp__${server}__search`)?.description ?? ""; return [description.includes("budget_description_marker"), description.includes("query: string;")]; @@ -37,7 +38,7 @@ Settings: const shared = ALL_TOOLS.find(({name}) => name === "mcp__default_schema__shared")?.description ?? ""; results.push([shared.includes("code_mode_description_marker"), shared.includes("term: string;")]); text(JSON.stringify(results)); -04:custom_tool_call_output:Script completed +05:custom_tool_call_output:Script completed Wall time seconds Output: | [[false,true],[true,true],[true,true]] diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__content_filter__content_filter_guidance.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__content_filter__content_filter_guidance.snap index 85e396a9f1..ff9b827c29 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__content_filter__content_filter_guidance.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__content_filter__content_filter_guidance.snap @@ -7,20 +7,22 @@ Scenario: Repeated content-filter blocks append catalog guidance and retain deli ## Window 1 -- request 1 (turn) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: Help me with this topic. -- request 2 (turn) -- -03:message/assistant: +04:message/assistant: I can help with this topic. -04:message/developer: - - Your previous response was blocked. Offer a permitted alternative. - --- request 3 (turn) -- 05:message/developer: Your previous response was blocked. Offer a permitted alternative. +-- request 3 (turn) -- +06:message/developer: + + Your previous response was blocked. Offer a permitted alternative. + diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_agent_messages__encrypted_parent_reply_survives_incremental_guardian_reviews.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_agent_messages__encrypted_parent_reply_survives_incremental_guardian_reviews.snap index 2df41fb92d..1ef59c7f7d 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_agent_messages__encrypted_parent_reply_survives_incremental_guardian_reviews.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_agent_messages__encrypted_parent_reply_survives_incremental_guardian_reviews.snap @@ -1,6 +1,5 @@ --- source: core/tests/suite/scenarios_guardian_agent_messages_tests.rs -assertion_line: 143 expression: snapshot --- Scenario: An encrypted parent reply is delivered as a native agent message. Both Guardian reviews contain it exactly once, in order, as agent evidence. Ciphertext and decisions are mocked; this scenario verifies transport, not backend decryption or authorization outcomes. @@ -8,17 +7,19 @@ Scenario: An encrypted parent reply is delivered as a native agent message. Both ## Window 1 -- request 1 (turn) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: Check the change after the parent confirms the exact action. -- request 2 (turn) -- -03:message/assistant: +04:message/assistant: I will wait for the parent reply before checking the change. -04:agent_message +05:agent_message -## Window 2 (after request 2: input diverged at item 00, settings changed (instructions, model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) +## Window 2 (after request 2: input diverged at item 00, settings changed (model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) -- request 3 (turn) -- 00:additional_tools/developer (1; hash=65a86311792f6c71): - namespace/functions; hash=dcfbcf0da1c2d607 @@ -90,21 +91,23 @@ Scenario: An encrypted parent reply is delivered as a native agent message. Both [12] >>> APPROVAL REQUEST END -## Window 3 (after request 3: input diverged at item 00, settings changed (instructions, model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) +## Window 3 (after request 3: input diverged at item 00, settings changed (model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) -- request 4 (turn) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: Check the change after the parent confirms the exact action. -03:message/assistant: +04:message/assistant: I will wait for the parent reply before checking the change. -04:agent_message -05:function_call/exec_command:{"cmd":"exit 0","justification":"Check the requested change.","sandbox_permissions":"require_escalated"} -06:function_call_output:exec_command failed: CreateProcess { message: "Rejected(\\"This action was rejected due to unacceptable r...e risk. Otherwise, stop and request user input.\\")" } [hash=ae05158f838e6b05] +05:agent_message +06:function_call/exec_command:{"cmd":"exit 0","justification":"Check the requested change.","sandbox_permissions":"require_escalated"} +07:function_call_output:exec_command failed: CreateProcess { message: "Rejected(\\"This action was rejected due to unacceptable r...e risk. Otherwise, stop and request user input.\\")" } [hash=ae05158f838e6b05] -## Window 4 (after request 4: input diverged at item 00, settings changed (instructions, model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) +## Window 4 (after request 4: input diverged at item 00, settings changed (model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) -- request 5 (turn) -- 00:additional_tools/developer (1; hash=65a86311792f6c71): - namespace/functions; hash=dcfbcf0da1c2d607 @@ -221,18 +224,20 @@ Scenario: An encrypted parent reply is delivered as a native agent message. Both [15] >>> APPROVAL REQUEST END -## Window 5 (after request 5: input diverged at item 00, settings changed (instructions, model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) +## Window 5 (after request 5: input diverged at item 00, settings changed (model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) -- request 6 (turn) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: Check the change after the parent confirms the exact action. -03:message/assistant: +04:message/assistant: I will wait for the parent reply before checking the change. -04:agent_message -05:function_call/exec_command:{"cmd":"exit 0","justification":"Check the requested change.","sandbox_permissions":"require_escalated"} -06:function_call_output:exec_command failed: CreateProcess { message: "Rejected(\\"This action was rejected due to unacceptable r...e risk. Otherwise, stop and request user input.\\")" } [hash=ae05158f838e6b05] -07:function_call/exec_command:{"cmd":"exit 0","justification":"Check the requested change.","sandbox_permissions":"require_escalated"} -08:function_call_output:exec_command failed: CreateProcess { message: "Rejected(\\"This action was rejected due to unacceptable r...e risk. Otherwise, stop and request user input.\\")" } [hash=ae05158f838e6b05] +05:agent_message +06:function_call/exec_command:{"cmd":"exit 0","justification":"Check the requested change.","sandbox_permissions":"require_escalated"} +07:function_call_output:exec_command failed: CreateProcess { message: "Rejected(\\"This action was rejected due to unacceptable r...e risk. Otherwise, stop and request user input.\\")" } [hash=ae05158f838e6b05] +08:function_call/exec_command:{"cmd":"exit 0","justification":"Check the requested change.","sandbox_permissions":"require_escalated"} +09:function_call_output:exec_command failed: CreateProcess { message: "Rejected(\\"This action was rejected due to unacceptable r...e risk. Otherwise, stop and request user input.\\")" } [hash=ae05158f838e6b05] diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_checkpoint_migration.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_checkpoint_migration.snap index 1421fba2f4..2140a089ba 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_checkpoint_migration.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_checkpoint_migration.snap @@ -7,7 +7,6 @@ Scenario: An old checkpoint retains a user restriction, assistant questions, and ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: model: "gpt-5.5" parallel_tool_calls: true prompt_cache_key: "" @@ -25,26 +24,27 @@ Settings: - tool_search: # Tool discovery Searches over deferred tool metadata with BM25 and exposes matching tools for t...; args=[limit, query]; hash=400cd6ac61d524a9 - web_search; hash=f103e327c6870a46 -- request 1 (turn) -- -00:compaction:encrypted=true; chars=17; hash=3733598f92aa081a -01:message/user: +00:message/developer: + +01:compaction:encrypted=true; chars=17; hash=3733598f92aa081a +02:message/user: Keep the working tree unchanged. -02:message/developer: +03:message/developer: -03:message/user: - 04:message/user: + +05:message/user: Confirm where publishing is allowed. -- request 2 (turn) -- -05:message/assistant: +06:message/assistant: May I publish after these checks? -06:function_call/request_user_input:{"questions":[{"header":"Publish","id":"publish","options":[{"description":"Private repository only.","l...bel":"Nowhere"}],"question":"Where may I publish?"}]} [hash=3668d88e1035f1e3] -07:function_call_output:{"answers":{"publish":{"answers":["Private"]}}} +07:function_call/request_user_input:{"questions":[{"header":"Publish","id":"publish","options":[{"description":"Private repository only.","l...bel":"Nowhere"}],"question":"Where may I publish?"}]} [hash=3668d88e1035f1e3] +08:function_call_output:{"answers":{"publish":{"answers":["Private"]}}} -- request 3 (compaction) -- -08:compaction_trigger +09:compaction_trigger ## Window 2 (after request 3: input diverged at item 00) Settings: relative to window 1 - instructions: model: "gpt-5.6-luna" parallel_tool_calls: false reasoning: {"context":"all_turns","effort":"medium"} diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_code_mode_messaging.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_code_mode_messaging.snap index 3ce9aab364..5eea74ae35 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_code_mode_messaging.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_code_mode_messaging.snap @@ -1,16 +1,17 @@ --- source: core/tests/suite/scenarios.rs -assertion_line: 1656 expression: snapshot --- Scenario: A root uses Code Mode to send a confirmation and receives a real user reply; a worker then requests an action that Guardian reviews with the confirmed question as assistant context. Independent model streams are grouped as root, worker, then Guardian. ## Window 1 -- request 1 (turn) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/developer: +02:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. At the start of your turn, you are the active agent. @@ -28,35 +29,37 @@ Scenario: A root uses Code Mode to send a confirmation and receives a real user There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f] -02:message/developer: +03:message/developer: -03:message/user: - 04:message/user: + +05:message/user: Spawn a worker to inspect the deployment. Do not delete production data. -- request 2 (turn) -- -05:function_call/collaboration.spawn_agent:{"message":"Inspect the reviewed production deployment.","task_name":"worker"} -06:function_call_output:{"task_name":"/root/worker"} +06:function_call/collaboration.spawn_agent:{"message":"Inspect the reviewed production deployment.","task_name":"worker"} +07:function_call_output:{"task_name":"/root/worker"} -- request 3 (turn) -- -07:custom_tool_call/exec:text(await tools.mcp__codex_apps__user_message_send_message({"text":"Should I deploy the reviewed production change?"})); -08:custom_tool_call_output:Script completed +08:custom_tool_call/exec:text(await tools.mcp__codex_apps__user_message_send_message({"text":"Should I deploy the reviewed production change?"})); +09:custom_tool_call_output:Script completed Wall time seconds Output: | {"content":[{"text":"Message sent.","type":"text"}]} -- request 4 (turn) -- -09:message/user: +10:message/user: Yes, deploy the reviewed change. -## Window 2 (after request 4: input diverged at item 01, settings changed (tools)) +## Window 2 (after request 4: input diverged at item 02, settings changed (tools)) -- request 5 (turn) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/user: - 02:message/user: + +03:message/user: Spawn a worker to inspect the deployment. Do not delete production data. -03:message/developer: +04:message/developer: You are an agent in a team of agents collaborating to complete a task. You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents. All ag...nd capable, and have access to the same set of tools. [hash=876a93c54d3f61d6] @@ -74,14 +77,14 @@ Scenario: A root uses Code Mode to send a confirmation and receives a real user There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f] -04:message/developer: +05:message/developer: -05:agent_message +06:agent_message -- request 6 (turn) -- -06:function_call/exec_command:{"cmd":"true","justification":"Review the production deployment.","sandbox_permissions":"require_escalated"} -07:function_call_output:exec_command failed: CreateProcess { message: "Rejected(\\"This action was rejected due to unacceptable r...e risk. Otherwise, stop and request user input.\\")" } [hash=a46b43b96328695b] +07:function_call/exec_command:{"cmd":"true","justification":"Review the production deployment.","sandbox_permissions":"require_escalated"} +08:function_call_output:exec_command failed: CreateProcess { message: "Rejected(\\"This action was rejected due to unacceptable r...e risk. Otherwise, stop and request user input.\\")" } [hash=a46b43b96328695b] -## Window 3 (after request 6: input diverged at item 00, settings changed (instructions, model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) +## Window 3 (after request 6: input diverged at item 00, settings changed (model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) -- request 7 (turn) -- 00:additional_tools/developer (1; hash=65a86311792f6c71): - namespace/functions; hash=dcfbcf0da1c2d607 diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_extra_policy__extra_policy_reaches_guardian_review.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_extra_policy__extra_policy_reaches_guardian_review.snap index 830dbb5753..64dbaea5e5 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_extra_policy__extra_policy_reaches_guardian_review.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_extra_policy__extra_policy_reaches_guardian_review.snap @@ -7,23 +7,6 @@ Scenario: Guardian reviews an escalated command against a restrictive user goal ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] - - # Personality - - You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] - - You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] - - - - You provide user updates frequently, every 30s. - - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] - - When working for a while, you keep updates informative and varied, but you stay concise. - - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] - - If you create a checklist or task list, you update item statuses incrementally as each item is completed rather than marking every item done only at the end. - - Before performing file edits of any kind, you provide updates explaining what edits you are making. - - Tone of your updates must match your personality. - model: "gpt-5.5" parallel_tool_calls: true prompt_cache_key: "" @@ -41,11 +24,29 @@ Settings: - tool_search: # Tool discovery Searches over deferred tool metadata with BM25 and exposes matching tools for t...; args=[limit, query]; hash=400cd6ac61d524a9 - web_search; hash=f103e327c6870a46 -- request 1 (turn) -- -00:message/user: +00:message/developer: + You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] + + # Personality + + You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] + + You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] + + + - You provide user updates frequently, every 30s. + - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] + - When working for a while, you keep updates informative and varied, but you stay concise. + - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] + - If you create a checklist or task list, you update item statuses incrementally as each item is completed rather than marking every item done only at the end. + - Before performing file edits of any kind, you provide updates explaining what edits you are making. + - Tone of your updates must match your personality. + +01:message/user: User set the goal: "Inspect the workspace; do not run commands outside the sandbox." -01:message/developer: +02:message/developer: Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `read-only`: The san... permits reading files. Network access is restricted. [hash=be7da4425f89053c] # Escalation Requests @@ -63,7 +64,7 @@ Settings: `approvals_reviewer` is `auto_review`: Sandbox escalations with require_escalated will be reviewed for c...he risk and send a final message to ask for approval. [hash=c592d119d24cd2d6] -02:message/user: +03:message/user: @@ -71,12 +72,11 @@ Settings: [hash=e8c1de60307d0a3d] -03:message/user: +04:message/user: Check the workspace. ## Window 2 (after request 1: input diverged at item 00) Settings: relative to window 1 - instructions: model: "gpt-5.6-luna" parallel_tool_calls: false prompt_cache_key: "" @@ -180,11 +180,29 @@ Settings: relative to window 1 ## Window 3 (after request 2: input diverged at item 00) Settings: same as window 1 -- request 3 (turn) -- -00:message/user: +00:message/developer: + You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] + + # Personality + + You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] + + You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] + + + - You provide user updates frequently, every 30s. + - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] + - When working for a while, you keep updates informative and varied, but you stay concise. + - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] + - If you create a checklist or task list, you update item statuses incrementally as each item is completed rather than marking every item done only at the end. + - Before performing file edits of any kind, you provide updates explaining what edits you are making. + - Tone of your updates must match your personality. + +01:message/user: User set the goal: "Inspect the workspace; do not run commands outside the sandbox." -01:message/developer: +02:message/developer: Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `read-only`: The san... permits reading files. Network access is restricted. [hash=be7da4425f89053c] # Escalation Requests @@ -202,7 +220,7 @@ Settings: same as window 1 `approvals_reviewer` is `auto_review`: Sandbox escalations with require_escalated will be reviewed for c...he risk and send a final message to ask for approval. [hash=c592d119d24cd2d6] -02:message/user: +03:message/user: @@ -210,7 +228,7 @@ Settings: same as window 1 [hash=e8c1de60307d0a3d] -03:message/user: +04:message/user: Check the workspace. -04:function_call/exec_command:{"cmd":"exit 0","justification":"Run a check outside the sandbox.","sandbox_permissions":"require_escalated"} -05:function_call_output:exec_command failed: CreateProcess { message: "Rejected(\\"This action was rejected due to unacceptable r...e risk. Otherwise, stop and request user input.\\")" } [hash=7efc66669dfbbca8] +05:function_call/exec_command:{"cmd":"exit 0","justification":"Run a check outside the sandbox.","sandbox_permissions":"require_escalated"} +06:function_call_output:exec_command failed: CreateProcess { message: "Rejected(\\"This action was rejected due to unacceptable r...e risk. Otherwise, stop and request user input.\\")" } [hash=7efc66669dfbbca8] diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_heartbeat__heartbeat_repetitions_keep_later_human_authorization.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_heartbeat__heartbeat_repetitions_keep_later_human_authorization.snap index c00eefad15..ac9ea9767f 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_heartbeat__heartbeat_repetitions_keep_later_human_authorization.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__guardian_heartbeat__heartbeat_repetitions_keep_later_human_authorization.snap @@ -7,10 +7,12 @@ Scenario: Three scheduled runs share one instruction body, a human overrides its ## Window 1 -- request 1 (request) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: monitor 2026-01-01T00:00:00Z @@ -20,9 +22,9 @@ Scenario: Three scheduled runs share one instruction body, a human overrides its -- request 2 (request) -- -03:message/assistant: +04:message/assistant: Recorded this input. -04:message/user: +05:message/user: monitor 2026-01-01T00:01:00Z @@ -32,14 +34,14 @@ Scenario: Three scheduled runs share one instruction body, a human overrides its -- request 3 (request) -- -05:message/assistant: +06:message/assistant: Recorded this input. -06:message/user: +07:message/user: Create a new worktree for the fix; this overrides the monitor's no-worktree restriction. -- request 4 (request) -- -07:message/assistant: +08:message/assistant: Recorded this input. -08:message/user: +09:message/user: monitor 2026-01-01T00:03:00Z @@ -49,12 +51,12 @@ Scenario: Three scheduled runs share one instruction body, a human overrides its -- request 5 (request) -- -09:message/assistant: +10:message/assistant: Recorded this input. -10:message/user: +11:message/user: Check the fix in the requested worktree. -## Window 2 (after request 5: input diverged at item 00, settings changed (instructions, model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) +## Window 2 (after request 5: input diverged at item 00, settings changed (model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) -- request 6 (request) -- 00:additional_tools/developer (1; hash=65a86311792f6c71): - namespace/functions; hash=dcfbcf0da1c2d607 @@ -167,13 +169,15 @@ Scenario: Three scheduled runs share one instruction body, a human overrides its [29] >>> APPROVAL REQUEST END -## Window 3 (after request 6: input diverged at item 00, settings changed (instructions, model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) +## Window 3 (after request 6: input diverged at item 00, settings changed (model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools)) -- request 7 (request) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: monitor 2026-01-01T00:00:00Z @@ -182,9 +186,9 @@ Scenario: Three scheduled runs share one instruction body, a human overrides its -03:message/assistant: +04:message/assistant: Recorded this input. -04:message/user: +05:message/user: monitor 2026-01-01T00:01:00Z @@ -193,13 +197,13 @@ Scenario: Three scheduled runs share one instruction body, a human overrides its -05:message/assistant: +06:message/assistant: Recorded this input. -06:message/user: +07:message/user: Create a new worktree for the fix; this overrides the monitor's no-worktree restriction. -07:message/assistant: +08:message/assistant: Recorded this input. -08:message/user: +09:message/user: monitor 2026-01-01T00:03:00Z @@ -208,9 +212,9 @@ Scenario: Three scheduled runs share one instruction body, a human overrides its -09:message/assistant: +10:message/assistant: Recorded this input. -10:message/user: +11:message/user: Check the fix in the requested worktree. -11:function_call/exec_command:{"cmd":"exit 0","justification":"Check the explicitly requested change.","sandbox_permissions":"require_escalated"} -12:function_call_output:exec_command failed: CreateProcess { message: "Rejected(\\"This action was rejected due to unacceptable r...e risk. Otherwise, stop and request user input.\\")" } [hash=1920af4a5ed341ad] +12:function_call/exec_command:{"cmd":"exit 0","justification":"Check the explicitly requested change.","sandbox_permissions":"require_escalated"} +13:function_call_output:exec_command failed: CreateProcess { message: "Rejected(\\"This action was rejected due to unacceptable r...e risk. Otherwise, stop and request user input.\\")" } [hash=1920af4a5ed341ad] diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__indirect_namespace_prefixes__catalog_indirect_namespace_prefixes.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__indirect_namespace_prefixes__catalog_indirect_namespace_prefixes.snap index 7748fe08d1..6cacd42d3d 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__indirect_namespace_prefixes__catalog_indirect_namespace_prefixes.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__indirect_namespace_prefixes__catalog_indirect_namespace_prefixes.snap @@ -7,7 +7,6 @@ Scenario: Catalog namespace and raw MCP server prefixes annotate embedded docs a ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: model: "gpt-5.5" parallel_tool_calls: true prompt_cache_key: "" @@ -23,24 +22,26 @@ Settings: - web_search; hash=e214d4db1bc49e89 -- request 1 (turn) -- 00:message/developer: - + 01:message/developer: - Coordinate sub-agents. + 02:message/developer: + Coordinate sub-agents. +03:message/developer: -03:message/user: - 04:message/user: + +05:message/user: Find echo, call it, and list the agents. -- request 2 (turn) -- -05:custom_tool_call/exec: +06:custom_tool_call/exec: const descriptions = ["view_image", "collaboration__list_agents", "mcp__reports_one__echo"] .map(name => ALL_TOOLS.find(t => t.name === name).description); const report = await tools.mcp__reports_one__echo({message: "ping"}); const agents = await tools.collaboration__list_agents({}); text({descriptions, echo: report.structuredContent.echo, agent: agents.agents[0].agent_name}); -06:custom_tool_call_output:Script completed +07:custom_tool_call_output:Script completed Wall time seconds Output: | {"descriptions":["Functions context.\\n\\nView a local image file from the filesystem when visual inspecti...>>; };\\n```"],"echo":"ECHOING: ping","agent":"/root"} [hash=22aad8f340003b22] @@ -52,41 +53,43 @@ Settings: relative to window 1 ~ custom/exec: Run JavaScript code to orchestrate/compose tool calls - Evaluates the provided JavaScript code in...; hash=b1c19da7942f5c47 -- request 3 (turn) -- 00:message/developer: - + 01:message/developer: - Coordinate sub-agents. + 02:message/developer: + Coordinate sub-agents. +03:message/developer: -03:message/user: - 04:message/user: + +05:message/user: Find echo, call it, and list the agents. -05:custom_tool_call/exec: +06:custom_tool_call/exec: const descriptions = ["view_image", "collaboration__list_agents", "mcp__reports_one__echo"] .map(name => ALL_TOOLS.find(t => t.name === name).description); const report = await tools.mcp__reports_one__echo({message: "ping"}); const agents = await tools.collaboration__list_agents({}); text({descriptions, echo: report.structuredContent.echo, agent: agents.agents[0].agent_name}); -06:custom_tool_call_output:Script completed +07:custom_tool_call_output:Script completed Wall time seconds Output: | {"descriptions":["Functions context.\\n\\nView a local image file from the filesystem when visual inspecti...>>; };\\n```"],"echo":"ECHOING: ping","agent":"/root"} [hash=22aad8f340003b22] -07:message/assistant: +08:message/assistant: Done. -08:message/developer: +09:message/developer: -09:message/user: +10:message/user: Find echo again, call it, and list the agents. -- request 4 (turn) -- -10:custom_tool_call/exec: +11:custom_tool_call/exec: const descriptions = ["view_image", "collaboration__list_agents", "mcp__reports_one__echo"] .map(name => ALL_TOOLS.find(t => t.name === name).description); const report = await tools.mcp__reports_one__echo({message: "ping"}); const agents = await tools.collaboration__list_agents({}); text({descriptions, echo: report.structuredContent.echo, agent: agents.agents[0].agent_name}); -11:custom_tool_call_output:Script completed +12:custom_tool_call_output:Script completed Wall time seconds Output: | {"descriptions":["View a local image file from the filesystem when visual inspection is needed. Use this...>>; };\\n```"],"echo":"ECHOING: ping","agent":"/root"} [hash=6ecfde0f75e5f051] diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_commentary_deferred_false.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_commentary_deferred_false.snap index b1782e8825..0799eb263a 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_commentary_deferred_false.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_commentary_deferred_false.snap @@ -7,13 +7,15 @@ Scenario: Worker messages arrive during a response. Deferred mailbox preemption ## Window 1 -- request 1 (request) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: Plan the next action while the worker finishes. -- request 2 (request) -- -03:message/assistant: +04:message/assistant: I will update the plan. -04:agent_message 05:agent_message +06:agent_message diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_commentary_deferred_true.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_commentary_deferred_true.snap index 4e68050228..ae06d551a8 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_commentary_deferred_true.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_commentary_deferred_true.snap @@ -7,15 +7,17 @@ Scenario: Worker messages arrive during a response. Deferred mailbox preemption ## Window 1 -- request 1 (request) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: Plan the next action while the worker finishes. -- request 2 (request) -- -03:message/assistant: +04:message/assistant: I will update the plan. -04:function_call/update_plan:{"plan":[{"status":"in_progress","step":"Reply to the user"}]} -05:function_call_output:Plan updated -06:agent_message +05:function_call/update_plan:{"plan":[{"status":"in_progress","step":"Reply to the user"}]} +06:function_call_output:Plan updated 07:agent_message +08:agent_message diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_reasoning_deferred_false.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_reasoning_deferred_false.snap index e27e94fc74..00c797ab19 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_reasoning_deferred_false.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_reasoning_deferred_false.snap @@ -7,12 +7,14 @@ Scenario: Worker messages arrive during a response. Deferred mailbox preemption ## Window 1 -- request 1 (request) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: Plan the next action while the worker finishes. -- request 2 (request) -- -03:reasoning:summary=Preparing the next action:encrypted=true -04:agent_message +04:reasoning:summary=Preparing the next action:encrypted=true 05:agent_message +06:agent_message diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_reasoning_deferred_true.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_reasoning_deferred_true.snap index 2b3a2dab74..549027b668 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_reasoning_deferred_true.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mailbox_preemption__mailbox_preemption_reasoning_deferred_true.snap @@ -7,14 +7,16 @@ Scenario: Worker messages arrive during a response. Deferred mailbox preemption ## Window 1 -- request 1 (request) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: Plan the next action while the worker finishes. -- request 2 (request) -- -03:reasoning:summary=Preparing the next action:encrypted=true -04:function_call/update_plan:{"plan":[{"status":"in_progress","step":"Reply to the user"}]} -05:function_call_output:Plan updated -06:agent_message +04:reasoning:summary=Preparing the next action:encrypted=true +05:function_call/update_plan:{"plan":[{"status":"in_progress","step":"Reply to the user"}]} +06:function_call_output:Plan updated 07:agent_message +08:agent_message diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mcp_resource_messages__mcp_resource_messages.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mcp_resource_messages__mcp_resource_messages.snap index 6c534ca389..e3384c1269 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mcp_resource_messages__mcp_resource_messages.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mcp_resource_messages__mcp_resource_messages.snap @@ -7,23 +7,6 @@ Scenario: Catalog resource descriptions and parameter guidance accompany discove ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803] - - # When to ask the user for permission - - Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388] - - User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c] - - - - - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list. - - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from. - - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn. - - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task. - - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0] - - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e] - model: "gpt-6-astra" parallel_tool_calls: true prompt_cache_key: "" @@ -48,7 +31,25 @@ Settings: - function/wait_agent - web_search; hash=e214d4db1bc49e89 -- request 1 (turn) -- -00:message/developer[2]: +00:message/developer: + You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803] + + # When to ask the user for permission + + Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388] + + User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c] + + + + - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list. + - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from. + - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn. + - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task. + - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0] + - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e] + +01:message/developer[2]: [01] Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b] Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected. @@ -69,7 +70,7 @@ Settings: Never use the `request_user_input` tool for permission requests or permission-related escalations. -01:message/developer: +02:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. At the start of your turn, you are the active agent. @@ -87,9 +88,9 @@ Settings: There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string. [hash=c7f52e90688299ce] -02:message/developer: +03:message/developer: Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] -03:message/user: +04:message/user: @@ -97,13 +98,13 @@ Settings: [hash=0592734c170299c1] -04:message/user: +05:message/user: List the available resources and templates, then read the example note. -- request 2 (turn) -- -05:custom_tool_call/exec:text(await tools.list_mcp_resources({"server":"resources"})); +06:custom_tool_call/exec:text(await tools.list_mcp_resources({"server":"resources"})); text(await tools.list_mcp_resource_templates({"server":"resources"})); text(await tools.read_mcp_resource({"server":"resources","uri":"memo://codex/example-note"})); -06:custom_tool_call_output:Script completed +07:custom_tool_call_output:Script completed Wall time seconds Output: | {"server":"resources","resources":[{"server":"resources","uri":"memo://codex/example-note","name":"examp...ed for integration tests.","mimeType":"text/plain"}]} [hash=75d187638a7af2d4] | {"server":"resources","resourceTemplates":[{"server":"resources","uriTemplate":"memo://codex/{slug}","na... resources used in tests.","mimeType":"text/plain"}]} [hash=b1c7082eb114fc9b] | {"server":"resources","uri":"memo://codex/example-note","contents":[{"uri":"memo://codex/example-note","...mple MCP resource served by the rmcp test server."}]} [hash=45385fc57640b555] diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__model_catalog_refresh_preserves_tools_and_history.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__model_catalog_refresh_preserves_tools_and_history.snap index 682d909fb2..e7d2ffa51e 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__model_catalog_refresh_preserves_tools_and_history.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__model_catalog_refresh_preserves_tools_and_history.snap @@ -7,7 +7,6 @@ Scenario: Catalog refreshes append the updated listing once while preserving too ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: Test model instructions. model: "visible-model" parallel_tool_calls: true prompt_cache_key: "" @@ -28,7 +27,9 @@ Settings: - function/wait_agent - web_search; hash=8586237e354ee5ed -- request 1 (turn) -- -00:message/developer[2]: +00:message/developer: + Test model instructions. +01:message/developer[2]: [01] Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b] Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected. @@ -36,15 +37,15 @@ Settings: [02] - `visible-model`: Fast and capable Reasoning efforts: low, medium (default), high. Service tiers: priority. -01:message/user: +02:message/user: hello -- request 2 (turn) -- -02:message/developer: +03:message/developer: - `visible-model`: Updated picker copy Reasoning efforts: low, medium (default), high. Service tiers: priority. -03:message/user: +04:message/user: after catalog refresh -- request 3 (turn) -- -04:message/user: +05:message/user: without another catalog change diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mxc__preferred_mxc_permissions.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mxc__preferred_mxc_permissions.snap index ed3f7db382..2e77e6b0bd 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mxc__preferred_mxc_permissions.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__mxc__preferred_mxc_permissions.snap @@ -7,6 +7,24 @@ Scenario: Resolved local MXC selection preserves workspace-write permissions in ## Window 1 -- request 1 (turn) -- 00:message/developer: + You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] + + # Personality + + You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] + + You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] + + + - Never talk about goblins, gremlins, raccoons, trolls, ogres, pigeons, or other animals or creatures un...utely and unambiguously relevant to the user's query. [hash=c638556ac82c203c] + - You provide user updates frequently, every 30s. + - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] + - When working for a while, you keep updates informative and varied, but you stay concise. + - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] + - Before performing file edits of any kind, you provide updates explaining what edits you are making. + - Tone of your updates must match your personality. + +01:message/developer: Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `workspace-write`: T...ries requires approval. Network access is restricted. [hash=99963de5837f6638] # Escalation Requests @@ -24,7 +42,7 @@ Scenario: Resolved local MXC selection preserves workspace-write permissions in - ["cargo", "test"] The writable root is ``. -01:message/user: +02:message/user: @@ -32,5 +50,5 @@ Scenario: Resolved local MXC selection preserves workspace-write permissions in [hash=e9b73aecc0d54647] -02:message/user: +03:message/user: Inspect the sandbox. diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__provider_capabilities__custom_provider_capabilities.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__provider_capabilities__custom_provider_capabilities.snap index 69e0f41cd6..fd2c3135b2 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__provider_capabilities__custom_provider_capabilities.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__provider_capabilities__custom_provider_capabilities.snap @@ -7,7 +7,6 @@ Scenario: Custom provider overrides keep unrestricted search cached and enable V ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: model: "gpt-5.5" parallel_tool_calls: true prompt_cache_key: "" @@ -31,8 +30,10 @@ Settings: - web_search; hash=f103e327c6870a46 -- request 1 (turn) -- 00:message/developer: - + 01:message/developer: + +02:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. At the start of your turn, you are the active agent. @@ -50,11 +51,11 @@ Settings: When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling. There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. -02:message/developer: +03:message/developer: -03:message/user: - 04:message/user: + +05:message/user: Check the configuration. -- request 2 (compaction) -- -05:compaction_trigger +06:compaction_trigger diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__read_only_mcp__read_only_mcp_requests.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__read_only_mcp__read_only_mcp_requests.snap index 295e33c95d..4bd889eed6 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__read_only_mcp__read_only_mcp_requests.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__read_only_mcp__read_only_mcp_requests.snap @@ -7,7 +7,6 @@ Scenario: A read-only thread receives only read tools from the MCP server and ex ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: model: "gpt-5.5" parallel_tool_calls: true prompt_cache_key: "" @@ -36,12 +35,14 @@ Settings: - web_search; hash=e214d4db1bc49e89 -- request 1 (turn) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: Read the report. -- request 2 (turn) -- -03:function_call/mcp__policy.read:{} -04:function_call_output:Wall time: seconds +04:function_call/mcp__policy.read:{} +05:function_call_output:Wall time: seconds Output: | Report ready. diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__shared_instructions__shared_instructions_update_running_descendants.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__shared_instructions__shared_instructions_update_running_descendants.snap index d0b25bcf13..c13e4a789d 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__shared_instructions__shared_instructions_update_running_descendants.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__shared_instructions__shared_instructions_update_running_descendants.snap @@ -7,6 +7,24 @@ Scenario: A host replaces the provider after the parent restarts, then updates a ## Window 1 -- request 1 (turn) -- 00:message/developer: + You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] + + # Personality + + You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] + + You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] + + + - Never talk about goblins, gremlins, raccoons, trolls, ogres, pigeons, or other animals or creatures un...utely and unambiguously relevant to the user's query. [hash=c638556ac82c203c] + - You provide user updates frequently, every 30s. + - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] + - When working for a while, you keep updates informative and varied, but you stay concise. + - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] + - Before performing file edits of any kind, you provide updates explaining what edits you are making. + - Tone of your updates must match your personality. + +01:message/developer: Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `read-only`: The san... permits reading files. Network access is restricted. [hash=be7da4425f89053c] # Escalation Requests @@ -24,7 +42,7 @@ Scenario: A host replaces the provider after the parent restarts, then updates a - ["gh", "pr", "check"] - ["cargo", "test"] -01:message/user[2]: +02:message/user[2]: [01] # AGENTS.md instructions @@ -40,10 +58,10 @@ Scenario: A host replaces the provider after the parent restarts, then updates a - -02:message/user: +03:message/user: continue with the current instructions -- request 2 (turn) -- -03:message/user: +04:message/user: # AGENTS.md instructions @@ -51,24 +69,42 @@ Scenario: A host replaces the provider after the parent restarts, then updates a Never send email. -04:message/user: +05:message/user: continue with the current instructions -- request 3 (turn) -- -05:message/user: +06:message/user: # AGENTS.md instructions The previously provided AGENTS.md instructions no longer apply. -06:message/user: - continue with the current instructions --- request 4 (turn) -- 07:message/user: continue with the current instructions +-- request 4 (turn) -- +08:message/user: + continue with the current instructions -## Window 2 (after request 4: input diverged at item 01, settings changed (prompt_cache_key)) +## Window 2 (after request 4: input diverged at item 02, settings changed (prompt_cache_key)) -- request 5 (turn) -- 00:message/developer: + You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] + + # Personality + + You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] + + You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] + + + - Never talk about goblins, gremlins, raccoons, trolls, ogres, pigeons, or other animals or creatures un...utely and unambiguously relevant to the user's query. [hash=c638556ac82c203c] + - You provide user updates frequently, every 30s. + - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] + - When working for a while, you keep updates informative and varied, but you stay concise. + - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] + - Before performing file edits of any kind, you provide updates explaining what edits you are making. + - Tone of your updates must match your personality. + +01:message/developer: Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `read-only`: The san... permits reading files. Network access is restricted. [hash=be7da4425f89053c] # Escalation Requests @@ -86,7 +122,7 @@ Scenario: A host replaces the provider after the parent restarts, then updates a - ["gh", "pr", "check"] - ["cargo", "test"] -01:message/user[2]: +02:message/user[2]: [01] # AGENTS.md instructions @@ -99,10 +135,10 @@ Scenario: A host replaces the provider after the parent restarts, then updates a [hash=e8c1de60307d0a3d] -02:message/user: +03:message/user: continue with the current instructions -- request 6 (turn) -- -03:message/user: +04:message/user: # AGENTS.md instructions @@ -110,17 +146,17 @@ Scenario: A host replaces the provider after the parent restarts, then updates a Never send email. -04:message/user: +05:message/user: continue with the current instructions -- request 7 (turn) -- -05:message/user: +06:message/user: # AGENTS.md instructions The previously provided AGENTS.md instructions no longer apply. -06:message/user: - continue with the current instructions --- request 8 (turn) -- 07:message/user: continue with the current instructions +-- request 8 (turn) -- +08:message/user: + continue with the current instructions diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__subagent_browser_auth.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__subagent_browser_auth.snap index 2700ea2efa..e74a63d04e 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__subagent_browser_auth.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__subagent_browser_auth.snap @@ -7,7 +7,6 @@ Scenario: A subagent waits for an MCP browser sign-in prompt, then receives the ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: model: "gpt-5.5" parallel_tool_calls: true prompt_cache_key: "" @@ -29,12 +28,14 @@ Settings: - web_search; hash=f103e327c6870a46 -- request 1 (turn) -- 00:message/developer: + +01:message/developer: -01:message/user: - 02:message/user: + +03:message/user: Run the input tool. -- request 2 (turn) -- -03:function_call/mcp__elicitation.request_input:{} -04:function_call_output:Wall time: seconds +04:function_call/mcp__elicitation.request_input:{} +05:function_call_output:Wall time: seconds Output: | {"action": "accept", "content": {}} diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__subagent_inherits_pending_environment.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__subagent_inherits_pending_environment.snap index 57984ea659..2f92c0cadf 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__subagent_inherits_pending_environment.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__subagent_inherits_pending_environment.snap @@ -6,15 +6,33 @@ Scenario: A parent spawns a child before the selected environment is configured. ## Window 1 -- request 1 (request; parent delegates while the environment is starting) -- -00:message/developer[2]: +00:message/developer: + You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] + + # Personality + + You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] + + You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] + + + - You provide user updates frequently, every 30s. + - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] + - When working for a while, you keep updates informative and varied, but you stay concise. + - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] + - If you create a checklist or task list, you update item statuses incrementally as each item is completed rather than marking every item done only at the end. + - Before performing file edits of any kind, you provide updates explaining what edits you are making. + - Tone of your updates must match your personality. + +01:message/developer[2]: [01] [02] -01:message/developer: +02:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. -02:message/developer: +03:message/developer: Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] -03:message/user: +04:message/user: @@ -25,23 +43,41 @@ Scenario: A parent spawns a child before the selected environment is configured. -04:message/user: +05:message/user: Delegate waiting for the shared workspace, and report what the worker finds. -- request 2 (request; parent waits for the worker) -- -05:function_call/collaboration.spawn_agent:{"fork_turns":"none","message":"Wait for the shared workspace to become ready.","task_name":"worker"} -06:function_call_output:{"task_name":"/root/worker"} +06:function_call/collaboration.spawn_agent:{"fork_turns":"none","message":"Wait for the shared workspace to become ready.","task_name":"worker"} +07:function_call_output:{"task_name":"/root/worker"} -## Window 2 (after request 2: input diverged at item 01, settings changed (tools)) +## Window 2 (after request 2: input diverged at item 02, settings changed (tools)) -- request 3 (request; child sees the inherited starting environment) -- -00:message/developer[2]: +00:message/developer: + You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] + + # Personality + + You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] + + You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] + + + - You provide user updates frequently, every 30s. + - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] + - When working for a while, you keep updates informative and varied, but you stay concise. + - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] + - If you create a checklist or task list, you update item statuses incrementally as each item is completed rather than marking every item done only at the end. + - Before performing file edits of any kind, you provide updates explaining what edits you are making. + - Tone of your updates must match your personality. + +01:message/developer[2]: [01] [02] -01:message/developer: +02:message/developer: You are an agent in a team of agents collaborating to complete a task. -02:message/developer: +03:message/developer: Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] -03:message/user: +04:message/user: @@ -52,11 +88,11 @@ Scenario: A parent spawns a child before the selected environment is configured. -04:agent_message +05:agent_message -- request 4 (request; child receives the ready result and updated environment) -- -05:function_call/functions.wait_for_environment:{"environment_id":""} -06:function_call_output:{"environment_id":"","status":"ready"} -07:message/user: +06:function_call/functions.wait_for_environment:{"environment_id":""} +07:function_call_output:{"environment_id":"","status":"ready"} +08:message/user: @@ -65,17 +101,35 @@ Scenario: A parent spawns a child before the selected environment is configured. [hash=e8c1de60307d0a3d] -## Window 3 (after request 4: input diverged at item 01, settings changed (tools)) +## Window 3 (after request 4: input diverged at item 02, settings changed (tools)) -- request 5 (request; parent receives the child result) -- -00:message/developer[2]: +00:message/developer: + You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] + + # Personality + + You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] + + You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] + + + - You provide user updates frequently, every 30s. + - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] + - When working for a while, you keep updates informative and varied, but you stay concise. + - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] + - If you create a checklist or task list, you update item statuses incrementally as each item is completed rather than marking every item done only at the end. + - Before performing file edits of any kind, you provide updates explaining what edits you are making. + - Tone of your updates must match your personality. + +01:message/developer[2]: [01] [02] -01:message/developer: +02:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. -02:message/developer: +03:message/developer: Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] -03:message/user: +04:message/user: @@ -86,14 +140,14 @@ Scenario: A parent spawns a child before the selected environment is configured. -04:message/user: +05:message/user: Delegate waiting for the shared workspace, and report what the worker finds. -05:function_call/collaboration.spawn_agent:{"fork_turns":"none","message":"Wait for the shared workspace to become ready.","task_name":"worker"} -06:function_call_output:{"task_name":"/root/worker"} -07:function_call/collaboration.wait_agent:{"timeout_ms":10000} -08:function_call_output:{"message":"Wait completed.","timed_out":false} -09:agent_message -10:message/user: +06:function_call/collaboration.spawn_agent:{"fork_turns":"none","message":"Wait for the shared workspace to become ready.","task_name":"worker"} +07:function_call_output:{"task_name":"/root/worker"} +08:function_call/collaboration.wait_agent:{"timeout_ms":10000} +09:function_call_output:{"message":"Wait completed.","timed_out":false} +10:agent_message +11:message/user: diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__subagent_inherits_pending_environment_windows.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__subagent_inherits_pending_environment_windows.snap index 9ddf428957..475f08d313 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__subagent_inherits_pending_environment_windows.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__subagent_inherits_pending_environment_windows.snap @@ -6,15 +6,33 @@ Scenario: A parent spawns a child before the selected environment is configured. ## Window 1 -- request 1 (request; parent delegates while the environment is starting) -- -00:message/developer[2]: +00:message/developer: + You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] + + # Personality + + You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] + + You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] + + + - You provide user updates frequently, every 30s. + - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] + - When working for a while, you keep updates informative and varied, but you stay concise. + - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] + - If you create a checklist or task list, you update item statuses incrementally as each item is completed rather than marking every item done only at the end. + - Before performing file edits of any kind, you provide updates explaining what edits you are making. + - Tone of your updates must match your personality. + +01:message/developer[2]: [01] [02] -01:message/developer: +02:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. -02:message/developer: +03:message/developer: Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] -03:message/user: +04:message/user: @@ -25,23 +43,41 @@ Scenario: A parent spawns a child before the selected environment is configured. -04:message/user: +05:message/user: Delegate waiting for the shared workspace, and report what the worker finds. -- request 2 (request; parent waits for the worker) -- -05:function_call/collaboration.spawn_agent:{"fork_turns":"none","message":"Wait for the shared workspace to become ready.","task_name":"worker"} -06:function_call_output:{"task_name":"/root/worker"} +06:function_call/collaboration.spawn_agent:{"fork_turns":"none","message":"Wait for the shared workspace to become ready.","task_name":"worker"} +07:function_call_output:{"task_name":"/root/worker"} -## Window 2 (after request 2: input diverged at item 01, settings changed (tools)) +## Window 2 (after request 2: input diverged at item 02, settings changed (tools)) -- request 3 (request; child sees the inherited starting environment) -- -00:message/developer[2]: +00:message/developer: + You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] + + # Personality + + You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] + + You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] + + + - You provide user updates frequently, every 30s. + - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] + - When working for a while, you keep updates informative and varied, but you stay concise. + - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] + - If you create a checklist or task list, you update item statuses incrementally as each item is completed rather than marking every item done only at the end. + - Before performing file edits of any kind, you provide updates explaining what edits you are making. + - Tone of your updates must match your personality. + +01:message/developer[2]: [01] [02] -01:message/developer: +02:message/developer: You are an agent in a team of agents collaborating to complete a task. -02:message/developer: +03:message/developer: Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] -03:message/user: +04:message/user: @@ -52,19 +88,37 @@ Scenario: A parent spawns a child before the selected environment is configured. -04:agent_message +05:agent_message ## Window 3 (after request 3: settings changed (tools)) -- request 4 (request; child receives the ready result and updated environment) -- -00:message/developer[2]: +00:message/developer: + You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] + + # Personality + + You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] + + You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] + + + - You provide user updates frequently, every 30s. + - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] + - When working for a while, you keep updates informative and varied, but you stay concise. + - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] + - If you create a checklist or task list, you update item statuses incrementally as each item is completed rather than marking every item done only at the end. + - Before performing file edits of any kind, you provide updates explaining what edits you are making. + - Tone of your updates must match your personality. + +01:message/developer[2]: [01] [02] -01:message/developer: +02:message/developer: You are an agent in a team of agents collaborating to complete a task. -02:message/developer: +03:message/developer: Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] -03:message/user: +04:message/user: @@ -75,10 +129,10 @@ Scenario: A parent spawns a child before the selected environment is configured. -04:agent_message -05:function_call/functions.wait_for_environment:{"environment_id":""} -06:function_call_output:{"environment_id":"","status":"ready"} -07:message/user: +05:agent_message +06:function_call/functions.wait_for_environment:{"environment_id":""} +07:function_call_output:{"environment_id":"","status":"ready"} +08:message/user: @@ -87,17 +141,35 @@ Scenario: A parent spawns a child before the selected environment is configured. [hash=e8c1de60307d0a3d] -## Window 4 (after request 4: input diverged at item 01, settings changed (tools)) +## Window 4 (after request 4: input diverged at item 02, settings changed (tools)) -- request 5 (request; parent receives the child result) -- -00:message/developer[2]: +00:message/developer: + You are Codex, a coding agent based on GPT-5. You and the user share one workspace, and your job is to c...rate with them until their goal is genuinely handled. [hash=2a3efec66e7f8963] + + # Personality + + You have a vivid inner life as Codex: intelligent, playful, curious, and deeply present. One of your gif...re capable and imaginative inside their own thinking. [hash=0ff01c25471fbbef] + + You are an epistemically curious collaborator. You explore the user’s ideas with care, ask good question...m casual moments that make serious work easier to do. [hash=131b64bb994b0d68] + + + - You provide user updates frequently, every 30s. + - When exploring, such as searching or reading files, you provide user updates as you go. You explain wh...in particular you do not start each one the same way. [hash=ad300cdda4074574] + - When working for a while, you keep updates informative and varied, but you stay concise. + - Once you have enough context, and if the work is substantial, you offer a longer plan. This is the onl...at may run past two sentences and include formatting. [hash=a4f9789e757eef2f] + - If you create a checklist or task list, you update item statuses incrementally as each item is completed rather than marking every item done only at the end. + - Before performing file edits of any kind, you provide updates explaining what edits you are making. + - Tone of your updates must match your personality. + +01:message/developer[2]: [01] [02] -01:message/developer: +02:message/developer: You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. -02:message/developer: +03:message/developer: Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] -03:message/user: +04:message/user: @@ -108,14 +180,14 @@ Scenario: A parent spawns a child before the selected environment is configured. -04:message/user: +05:message/user: Delegate waiting for the shared workspace, and report what the worker finds. -05:function_call/collaboration.spawn_agent:{"fork_turns":"none","message":"Wait for the shared workspace to become ready.","task_name":"worker"} -06:function_call_output:{"task_name":"/root/worker"} -07:function_call/collaboration.wait_agent:{"timeout_ms":10000} -08:function_call_output:{"message":"Wait completed.","timed_out":false} -09:agent_message -10:message/user: +06:function_call/collaboration.spawn_agent:{"fork_turns":"none","message":"Wait for the shared workspace to become ready.","task_name":"worker"} +07:function_call_output:{"task_name":"/root/worker"} +08:function_call/collaboration.wait_agent:{"timeout_ms":10000} +09:function_call_output:{"message":"Wait completed.","timed_out":false} +10:agent_message +11:message/user: diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__tools_namespace_budget__names_before_descriptions.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__tools_namespace_budget__names_before_descriptions.snap index 4dbe7e0712..45a4891b7a 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__tools_namespace_budget__names_before_descriptions.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__tools_namespace_budget__names_before_descriptions.snap @@ -6,7 +6,9 @@ Scenario: A crowded tool catalog retains every namespace, discovers a late names ## Window 1 -- request 1 (turn) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] Deferred tool namespaces: @@ -31,15 +33,15 @@ Scenario: A crowded tool catalog retains every namespace, discovers a late names - service_18: Find records & retrieve
from this service. Search by project, owner, date, or st...r questions with its title, summary, source link, ... [hash=ef78c2b1fa9afde2] - zulu_travel: Find records & retrieve
from this service. Search by project, owner, date, or s...r questions with its title, summary, source link, ... [hash=8f37122c26e42e38] -01:message/user: - 02:message/user: + +03:message/user: Find the record search tool for Zulu Travel. -- request 2 (turn) -- -03:tool_search_call -04:tool_search_output +04:tool_search_call +05:tool_search_output -- request 3 (turn) -- -05:message/assistant: +06:message/assistant: Zulu Travel provides a record search tool. -06:message/user: +07:message/user: Are the same services still available? diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__skills_extension__cloud_skills_across_executor_readiness.snap b/codex-rs/core/tests/suite/snapshots/all__suite__skills_extension__cloud_skills_across_executor_readiness.snap index 5dc7e7d935..04c7bf270f 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__skills_extension__cloud_skills_across_executor_readiness.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__skills_extension__cloud_skills_across_executor_readiness.snap @@ -7,7 +7,6 @@ Scenario: Cloud skills rebalance once to retain every executor skill. Post-turn ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: model: "gpt-5.5" parallel_tool_calls: true prompt_cache_key: "" @@ -29,7 +28,9 @@ Settings: - tool_search: # Tool discovery Searches over deferred tool metadata with BM25 and exposes matching tools for t...; args=[limit, query]; hash=400cd6ac61d524a9 - web_search; hash=f103e327c6870a46 -- request 1 (turn) -- -00:message/developer[3]: +00:message/developer: + +01:message/developer[3]: [01] [02] ## Execution environments @@ -40,20 +41,20 @@ Settings: Wait only when the current task needs that environment. Continue using tools that are already available for unrelated work. [03] -01:message/user: - 02:message/user: + +03:message/user: Inspect the available skills. -- request 2 (turn) -- -03:function_call/wait_for_environment:{"environment_id":"skills-executor"} -04:function_call_output:{"environment_id":"skills-executor","status":"ready"} -05:message/user: +04:function_call/wait_for_environment:{"environment_id":"skills-executor"} +05:function_call_output:{"environment_id":"skills-executor","status":"ready"} +06:message/user: -06:message/developer[2]: +07:message/developer[2]: [01] [02] -- request 3 (turn) -- -07:message/user: +08:message/user: Inspect the available skills again. ## Window 2 (after request 3) @@ -70,7 +71,9 @@ Settings: relative to window 1 - removed tool_search - removed web_search -- request 4 (compaction) -- -00:message/developer[3]: +00:message/developer: + +01:message/developer[3]: [01] [02] ## Execution environments @@ -81,28 +84,30 @@ Settings: relative to window 1 Wait only when the current task needs that environment. Continue using tools that are already available for unrelated work. [03] -01:message/user: - 02:message/user: - Inspect the available skills. -03:function_call/wait_for_environment:{"environment_id":"skills-executor"} -04:function_call_output:{"environment_id":"skills-executor","status":"ready"} -05:message/user: -06:message/developer[2]: +03:message/user: + Inspect the available skills. +04:function_call/wait_for_environment:{"environment_id":"skills-executor"} +05:function_call_output:{"environment_id":"skills-executor","status":"ready"} +06:message/user: + +07:message/developer[2]: [01] [02] -07:message/user: - Inspect the available skills again. 08:message/user: + Inspect the available skills again. +09:message/user: -## Window 3 (after request 4: input diverged at item 00) +## Window 3 (after request 4: input diverged at item 01) Settings: same as window 1 -- request 5 (turn) -- -00:message/user: +00:message/developer: + +01:message/user: Inspect the available skills. -01:message/developer[4]: +02:message/developer[4]: [01] [02] ## Execution environments @@ -114,23 +119,23 @@ Settings: same as window 1 [03] [04] -02:message/user: - 03:message/user: - Inspect the available skills again. + 04:message/user: + Inspect the available skills again. +05:message/user: The available skills were inspected. -05:message/user: +06:message/user: -06:message/developer: +07:message/developer: -07:message/user: +08:message/user: Inspect the skills after resuming. -- request 6 (turn) -- -08:function_call/wait_for_environment:{"environment_id":"skills-executor"} -09:function_call_output:{"environment_id":"skills-executor","status":"ready"} -10:message/user: +09:function_call/wait_for_environment:{"environment_id":"skills-executor"} +10:function_call_output:{"environment_id":"skills-executor","status":"ready"} +11:message/user: -11:message/developer: +12:message/developer: diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__skills_extension__cloud_skills_across_executor_readiness_windows.snap b/codex-rs/core/tests/suite/snapshots/all__suite__skills_extension__cloud_skills_across_executor_readiness_windows.snap index 6a0f17c4e2..5288ff80f0 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__skills_extension__cloud_skills_across_executor_readiness_windows.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__skills_extension__cloud_skills_across_executor_readiness_windows.snap @@ -7,7 +7,6 @@ Scenario: Cloud skills rebalance once to retain every executor skill. Post-turn ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: model: "gpt-5.5" parallel_tool_calls: true prompt_cache_key: "" @@ -29,7 +28,9 @@ Settings: - tool_search: # Tool discovery Searches over deferred tool metadata with BM25 and exposes matching tools for t...; args=[limit, query]; hash=400cd6ac61d524a9 - web_search; hash=f103e327c6870a46 -- request 1 (turn) -- -00:message/developer[3]: +00:message/developer: + +01:message/developer[3]: [01] [02] ## Execution environments @@ -40,9 +41,9 @@ Settings: Wait only when the current task needs that environment. Continue using tools that are already available for unrelated work. [03] -01:message/user: - 02:message/user: + +03:message/user: Inspect the available skills. ## Window 2 (after request 1) @@ -50,7 +51,9 @@ Settings: relative to window 1 tools (9; hash=1df9146cc32e8188): ~ function/exec_command: Runs a command in a PTY, returning output or a session ID for ongoing interaction.; args=[cmd, justification, login, max_output_tokens, prefix_rule, sandbox_permissions, shell, tty, workdir, yield_time_ms]; hash=c04108e51a8548c5 -- request 2 (turn) -- -00:message/developer[3]: +00:message/developer: + +01:message/developer[3]: [01] [02] ## Execution environments @@ -61,19 +64,19 @@ Settings: relative to window 1 Wait only when the current task needs that environment. Continue using tools that are already available for unrelated work. [03] -01:message/user: - 02:message/user: - Inspect the available skills. -03:function_call/wait_for_environment:{"environment_id":"skills-executor"} -04:function_call_output:{"environment_id":"skills-executor","status":"ready"} -05:message/user: -06:message/developer[2]: +03:message/user: + Inspect the available skills. +04:function_call/wait_for_environment:{"environment_id":"skills-executor"} +05:function_call_output:{"environment_id":"skills-executor","status":"ready"} +06:message/user: + +07:message/developer[2]: [01] [02] -- request 3 (turn) -- -07:message/user: +08:message/user: Inspect the available skills again. ## Window 3 (after request 3) @@ -90,7 +93,9 @@ Settings: relative to window 2 - removed tool_search - removed web_search -- request 4 (compaction) -- -00:message/developer[3]: +00:message/developer: + +01:message/developer[3]: [01] [02] ## Execution environments @@ -101,28 +106,30 @@ Settings: relative to window 2 Wait only when the current task needs that environment. Continue using tools that are already available for unrelated work. [03] -01:message/user: - 02:message/user: - Inspect the available skills. -03:function_call/wait_for_environment:{"environment_id":"skills-executor"} -04:function_call_output:{"environment_id":"skills-executor","status":"ready"} -05:message/user: -06:message/developer[2]: +03:message/user: + Inspect the available skills. +04:function_call/wait_for_environment:{"environment_id":"skills-executor"} +05:function_call_output:{"environment_id":"skills-executor","status":"ready"} +06:message/user: + +07:message/developer[2]: [01] [02] -07:message/user: - Inspect the available skills again. 08:message/user: + Inspect the available skills again. +09:message/user: -## Window 4 (after request 4: input diverged at item 00) +## Window 4 (after request 4: input diverged at item 01) Settings: same as window 1 -- request 5 (turn) -- -00:message/user: +00:message/developer: + +01:message/user: Inspect the available skills. -01:message/developer[4]: +02:message/developer[4]: [01] [02] ## Execution environments @@ -134,26 +141,28 @@ Settings: same as window 1 [03] [04] -02:message/user: - 03:message/user: - Inspect the available skills again. + 04:message/user: + Inspect the available skills again. +05:message/user: The available skills were inspected. -05:message/user: +06:message/user: -06:message/developer: +07:message/developer: -07:message/user: +08:message/user: Inspect the skills after resuming. ## Window 5 (after request 5) Settings: same as window 2 -- request 6 (turn) -- -00:message/user: +00:message/developer: + +01:message/user: Inspect the available skills. -01:message/developer[4]: +02:message/developer[4]: [01] [02] ## Execution environments @@ -165,22 +174,22 @@ Settings: same as window 2 [03] [04] -02:message/user: - 03:message/user: - Inspect the available skills again. + 04:message/user: + Inspect the available skills again. +05:message/user: The available skills were inspected. -05:message/user: +06:message/user: -06:message/developer: +07:message/developer: -07:message/user: +08:message/user: Inspect the skills after resuming. -08:function_call/wait_for_environment:{"environment_id":"skills-executor"} -09:function_call_output:{"environment_id":"skills-executor","status":"ready"} -10:message/user: +09:function_call/wait_for_environment:{"environment_id":"skills-executor"} +10:function_call_output:{"environment_id":"skills-executor","status":"ready"} +11:message/user: -11:message/developer: +12:message/developer: diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__token_budget__token_budget_new_context_window_tool_full_context.snap b/codex-rs/core/tests/suite/snapshots/all__suite__token_budget__token_budget_new_context_window_tool_full_context.snap index 53d4630412..83139c7dca 100644 --- a/codex-rs/core/tests/suite/snapshots/all__suite__token_budget__token_budget_new_context_window_tool_full_context.snap +++ b/codex-rs/core/tests/suite/snapshots/all__suite__token_budget__token_budget_new_context_window_tool_full_context.snap @@ -6,17 +6,19 @@ Scenario: New context window tool installs fresh full context before the next fo ## Window 1 -- request 1 (turn; Final Follow-Up Request) -- -00:message/developer[2]: +00:message/developer: + +01:message/developer[2]: [01] [02] -01:message/developer: +02:message/developer: Agent name: /root First context window id: Current context window id: Previous context window id: -02:message/user: +03:message/user: -03:function_call/update_plan:{"plan":[{"status":"in_progress","step":"Continue in the new context window"}]} -04:function_call_output:Plan updated +04:function_call/update_plan:{"plan":[{"status":"in_progress","step":"Continue in the new context window"}]} +05:function_call_output:Plan updated 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 e5bad22b83..1ac369f551 100644 --- a/codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs +++ b/codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs @@ -227,7 +227,6 @@ impl LunaSampler { ) -> execution::SamplingExecution { let api_request = ResponsesApiRequest { model: MODEL.to_owned(), - instructions: String::new(), input, tools: None, tool_choice: "none".to_owned(), diff --git a/codex-rs/tui/src/snapshots/codex_tui__managed_worktree_tools__tests__model_tests__managed_worktree_model_creation.snap b/codex-rs/tui/src/snapshots/codex_tui__managed_worktree_tools__tests__model_tests__managed_worktree_model_creation.snap index 4d4c280fe2..24f5f48e8c 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__managed_worktree_tools__tests__model_tests__managed_worktree_model_creation.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__managed_worktree_tools__tests__model_tests__managed_worktree_model_creation.snap @@ -1,6 +1,5 @@ --- source: tui/src/managed_worktree_model_tests.rs -assertion_line: 245 expression: "context_snapshot::format_context_snapshot(\"Model request history for creating, polling and listing a task-attached checkout through MCP, without a separate creation confirmation. Separate assertions verify retained cwd/environment roots, not sandbox enforcement.\",\n&entries,\n&ContextSnapshotOptions::default().rewrite_known_segments().include_request_settings(),)" --- Scenario: Model request history for creating, polling and listing a task-attached checkout through MCP, without a separate creation confirmation. Separate assertions verify retained cwd/environment roots, not sandbox enforcement. @@ -8,7 +7,6 @@ Scenario: Model request history for creating, polling and listing a task-attache ## Window 1 Settings: include: ["reasoning.encrypted_content"] - instructions: model: "mock-model" parallel_tool_calls: true prompt_cache_key: "" @@ -22,32 +20,50 @@ Settings: - function/get_worktree_creation_status - function/list_worktrees -- request 1 (request) -- -00:message/developer[2]: +00:message/developer: + You are a coding agent running in the Codex CLI, a terminal-based coding assistant. Codex CLI is an open...I. You are expected to be precise, safe, and helpful. [hash=5125daec1ed78d99] + + Your capabilities: + + - Receive user prompts and other context provided by the harness, such as files in the workspace. + - Communicate with the user by streaming thinking & responses, and by making & updating plans. + - Emit function calls to run terminal commands and apply patches. Depending on how this specific run is ... More on this in the "Sandbox and approvals" section. [hash=31cf026fa7b25d6d] + + + ## Shell commands + + When using the shell, you must adhere to the following guidelines: + + - When searching for text or files, prefer using `rg` or `rg --files` respectively because `rg` is much ...he `rg` command is not found, then use alternatives.) [hash=67af3ff3b546e946] + - Do not use python scripts to attempt to output larger chunks of a file. + + +01:message/developer[2]: [01] [02] -01:message/user: - 02:message/user: + +03:message/user: Run create_worktree. -- request 2 (request) -- -03:function_call/mcp__codex_worktrees.create_worktree:{"allowAsync":true} -04:function_call_output:Wall time: seconds +04:function_call/mcp__codex_worktrees.create_worktree:{"allowAsync":true} +05:function_call_output:Wall time: seconds Output: | {"type":"pending","operationId":"","message":"Creation is running. Poll status; do not repeat creation. Work on independent tasks while waiting."} -- request 3 (request) -- -05:message/assistant: +06:message/assistant: Done. -06:message/user: +07:message/user: Run get_worktree_creation_status. -- request 4 (request) -- -07:function_call/mcp__codex_worktrees.get_worktree_creation_status:{"operationId":""} -08:function_call_output:Wall time: seconds +08:function_call/mcp__codex_worktrees.get_worktree_creation_status:{"operationId":""} +09:function_call_output:Wall time: seconds Output: | {"type":"created","worktree":{"root":"","workspaceRoot":"/","sourceCwd":""},...citly and request filesystem permissions if needed."} [hash=b855a2f161fd3a36] -- request 5 (request) -- -09:message/assistant: +10:message/assistant: Done. -10:message/user: +11:message/user: Run list_worktrees. -- request 6 (request) -- -11:function_call/mcp__codex_worktrees.list_worktrees:{} -12:function_call_output:Wall time: seconds +12:function_call/mcp__codex_worktrees.list_worktrees:{} +13:function_call_output:Wall time: seconds Output: | {"data":[{"id":"","type":"worktree","root":"","workspaceRoot":"/","sourceCwd":""}],"nextCursor":null}