From dbf73ee80d104bf74a7bef4d049f382b4f1a949e Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 16 Jun 2026 18:43:08 -0700 Subject: [PATCH 1/6] core: remove redundant TurnContext fields --- codex-rs/core/src/compact.rs | 2 +- codex-rs/core/src/context_manager/updates.rs | 8 +- codex-rs/core/src/mcp_tool_call.rs | 10 ++- codex-rs/core/src/mcp_tool_call_tests.rs | 18 ++-- codex-rs/core/src/session/mod.rs | 34 +++++-- codex-rs/core/src/session/review.rs | 26 ++---- .../src/session/rollout_reconstruction.rs | 4 +- codex-rs/core/src/session/tests.rs | 32 ++----- .../core/src/session/tests/guardian_tests.rs | 20 ++--- codex-rs/core/src/session/token_budget.rs | 2 +- codex-rs/core/src/session/turn.rs | 10 ++- codex-rs/core/src/session/turn_context.rs | 58 ++---------- codex-rs/core/src/tasks/compact.rs | 1 + codex-rs/core/src/tasks/mod.rs | 2 +- codex-rs/core/src/tasks/user_shell.rs | 12 ++- codex-rs/core/src/tools/code_mode/mod.rs | 4 +- codex-rs/core/src/tools/events.rs | 7 +- .../core/src/tools/handlers/apply_patch.rs | 6 +- .../src/tools/handlers/extension_tools.rs | 4 +- codex-rs/core/src/tools/handlers/mcp.rs | 2 +- .../list_mcp_resource_templates.rs | 3 +- .../mcp_resource/list_mcp_resources.rs | 3 +- .../mcp_resource/read_mcp_resource.rs | 3 +- .../src/tools/handlers/multi_agents_common.rs | 2 - .../src/tools/handlers/multi_agents_tests.rs | 10 +-- codex-rs/core/src/tools/handlers/shell.rs | 11 ++- .../src/tools/handlers/shell/shell_command.rs | 5 +- .../core/src/tools/handlers/shell_tests.rs | 2 +- .../handlers/unified_exec/exec_command.rs | 4 +- .../handlers/unified_exec/write_stdin.rs | 2 +- .../core/src/tools/handlers/view_image.rs | 2 +- codex-rs/core/src/tools/mod.rs | 15 ++++ codex-rs/core/src/tools/orchestrator.rs | 6 +- .../tools/runtimes/shell/unix_escalation.rs | 8 +- codex-rs/core/src/tools/spec_plan.rs | 46 ++++++---- codex-rs/core/src/tools/spec_plan_tests.rs | 20 ----- codex-rs/core/src/unified_exec/mod_tests.rs | 2 +- .../core/src/unified_exec/process_manager.rs | 16 +++- codex-rs/core/src/user_shell_command.rs | 5 +- turn-context-field-lifetimes.md | 89 +++++++++++++++++++ 40 files changed, 298 insertions(+), 218 deletions(-) create mode 100644 turn-context-field-lifetimes.md diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index 52b2ef4ef501..3ff34c99a927 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -209,7 +209,7 @@ async fn run_compact_task_inner_impl( let mut history = sess.clone_history().await; history.record_items( &[initial_input_for_turn.into()], - turn_context.truncation_policy, + turn_context.model_info.truncation_policy.into(), ); let max_retries = turn_context.provider.info().stream_max_retries(); diff --git a/codex-rs/core/src/context_manager/updates.rs b/codex-rs/core/src/context_manager/updates.rs index 0ce02240b007..4c69aae95b2e 100644 --- a/codex-rs/core/src/context_manager/updates.rs +++ b/codex-rs/core/src/context_manager/updates.rs @@ -63,8 +63,12 @@ fn build_permissions_update_item( exec_policy, #[allow(deprecated)] &next.cwd, - next.features.enabled(Feature::ExecPermissionApprovals), - next.features.enabled(Feature::RequestPermissionsTool), + next.config + .features + .enabled(Feature::ExecPermissionApprovals), + next.config + .features + .enabled(Feature::RequestPermissionsTool), ) .render(), ) diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index 708d26654686..e8cbb56c5513 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -626,7 +626,11 @@ async fn maybe_request_codex_apps_auth_elicitation( return result; } - if !turn_context.features.enabled(Feature::AuthElicitation) { + if !turn_context + .config + .features + .enabled(Feature::AuthElicitation) + { return result; } @@ -723,10 +727,10 @@ async fn augment_mcp_tool_request_meta_with_sandbox_state( let sandbox_state = serde_json::to_value(SandboxState { permission_profile: Some(turn_context.permission_profile()), sandbox_policy: turn_context.sandbox_policy(), - codex_linux_sandbox_exe: turn_context.codex_linux_sandbox_exe.clone(), + codex_linux_sandbox_exe: turn_context.config.codex_linux_sandbox_exe.clone(), #[allow(deprecated)] sandbox_cwd: turn_context.cwd.to_path_buf(), - use_legacy_landlock: turn_context.features.use_legacy_landlock(), + use_legacy_landlock: turn_context.config.features.use_legacy_landlock(), })?; match meta.as_mut() { diff --git a/codex-rs/core/src/mcp_tool_call_tests.rs b/codex-rs/core/src/mcp_tool_call_tests.rs index 9a8a9174a12c..56d1ff9b6cd2 100644 --- a/codex-rs/core/src/mcp_tool_call_tests.rs +++ b/codex-rs/core/src/mcp_tool_call_tests.rs @@ -1316,15 +1316,14 @@ async fn codex_apps_auth_elicitation_non_host_owned_server_returns_original_resu let (session, mut turn_context, rx_event) = make_session_and_context_with_rx().await; let mut features = Features::with_defaults(); features.enable(Feature::AuthElicitation); - Arc::get_mut(&mut turn_context) - .expect("single turn context ref") - .features = ManagedFeatures::from(features); + let turn_context = Arc::get_mut(&mut turn_context).expect("single turn context ref"); + Arc::make_mut(&mut turn_context.config).features = ManagedFeatures::from(features); let result = codex_apps_auth_failure_result(); let metadata = codex_apps_auth_failure_metadata(); let returned = maybe_request_codex_apps_auth_elicitation( &session, - &turn_context, + turn_context, "call_123", CODEX_APPS_MCP_SERVER_NAME, Some(&metadata), @@ -1343,7 +1342,7 @@ async fn codex_apps_auth_elicitation_disallowed_by_policy_returns_original_resul let mut features = Features::with_defaults(); features.enable(Feature::AuthElicitation); let turn_context = Arc::get_mut(&mut turn_context).expect("single turn context ref"); - turn_context.features = ManagedFeatures::from(features); + Arc::make_mut(&mut turn_context.config).features = ManagedFeatures::from(features); turn_context .approval_policy .set(AskForApproval::Never) @@ -1372,7 +1371,7 @@ async fn codex_apps_auth_elicitation_granular_mcp_disabled_returns_original_resu let mut features = Features::with_defaults(); features.enable(Feature::AuthElicitation); let turn_context = Arc::get_mut(&mut turn_context).expect("single turn context ref"); - turn_context.features = ManagedFeatures::from(features); + Arc::make_mut(&mut turn_context.config).features = ManagedFeatures::from(features); turn_context .approval_policy .set(AskForApproval::Granular(GranularApprovalConfig { @@ -1407,9 +1406,10 @@ async fn codex_apps_auth_elicitation_feature_enabled_requests_elicitation() { *session.active_turn.lock().await = Some(ActiveTurn::default()); let mut features = Features::with_defaults(); features.enable(Feature::AuthElicitation); - Arc::get_mut(&mut turn_context) - .expect("single turn context ref") - .features = ManagedFeatures::from(features); + { + let turn_context = Arc::get_mut(&mut turn_context).expect("single turn context ref"); + Arc::make_mut(&mut turn_context.config).features = ManagedFeatures::from(features); + } let result = codex_apps_auth_failure_result(); let metadata = codex_apps_auth_failure_metadata(); diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index d632609c0255..d61de71bc572 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -149,7 +149,6 @@ use codex_thread_store::ReadThreadParams; use codex_thread_store::ResumeThreadParams; use codex_thread_store::ThreadPersistenceMetadata; use codex_thread_store::ThreadStore; -use codex_utils_output_truncation::TruncationPolicy; use codex_utils_path_uri::PathUri; use futures::future::BoxFuture; use futures::future::Shared; @@ -198,7 +197,6 @@ use codex_config::ConfigLayerSource; use codex_config::ConfigLayerStackOrdering; use codex_config::types::McpServerConfig; use codex_model_provider_info::ModelProviderInfo; -use codex_protocol::config_types::ShellEnvironmentPolicy; use codex_protocol::error::CodexErr; use codex_protocol::error::Result as CodexResult; #[cfg(test)] @@ -1330,7 +1328,11 @@ impl Session { } = self .reconstruct_history_from_rollout(turn_context, rollout_items) .await; - if turn_context.features.enabled(Feature::ResizeAllImages) { + if turn_context + .config + .features + .enabled(Feature::ResizeAllImages) + { // Keep the recorded rollout unchanged. Prepare its reconstructed history before // installing it, so legacy images are processed once for this resume or fork and // will be processed again if the rollout is reconstructed in a future session. @@ -2630,7 +2632,11 @@ impl Session { turn_context: &TurnContext, items: &'a [ResponseItem], ) -> Cow<'a, [ResponseItem]> { - if !turn_context.features.enabled(Feature::ResizeAllImages) { + if !turn_context + .config + .features + .enabled(Feature::ResizeAllImages) + { return Cow::Borrowed(items); } @@ -2644,7 +2650,11 @@ impl Session { turn_context: &TurnContext, input: Vec, ) -> ResponseItem { - let local_image_preparation = if turn_context.features.enabled(Feature::ResizeAllImages) { + let local_image_preparation = if turn_context + .config + .features + .enabled(Feature::ResizeAllImages) + { LocalImagePreparation::Defer } else { LocalImagePreparation::Process @@ -2664,7 +2674,10 @@ impl Session { let items = items.as_ref(); { let mut state = self.state.lock().await; - state.record_items(items.iter(), turn_context.truncation_policy); + state.record_items( + items.iter(), + turn_context.model_info.truncation_policy.into(), + ); } self.persist_rollout_response_items(items).await; self.send_raw_response_items(turn_context, items).await; @@ -2683,7 +2696,10 @@ impl Session { let items = items.as_ref(); { let mut state = self.state.lock().await; - state.record_items(items.iter(), turn_context.truncation_policy); + state.record_items( + items.iter(), + turn_context.model_info.truncation_policy.into(), + ); } self.persist_rollout_items(&[RolloutItem::InterAgentCommunication(communication)]) .await; @@ -2885,9 +2901,11 @@ impl Session { #[allow(deprecated)] &turn_context.cwd, turn_context + .config .features .enabled(Feature::ExecPermissionApprovals), turn_context + .config .features .enabled(Feature::RequestPermissionsTool), ) @@ -3031,7 +3049,7 @@ impl Session { contextual_user_sections.push(user_instructions.to_string()); } // This is full-context metadata. Steady-state context diffs should not re-emit it. - if turn_context.features.enabled(Feature::TokenBudget) + if turn_context.config.features.enabled(Feature::TokenBudget) && let Some(model_context_window) = turn_context.model_context_window() { developer_sections.push( diff --git a/codex-rs/core/src/session/review.rs b/codex-rs/core/src/session/review.rs index 746265971ed3..0e60a92f5b57 100644 --- a/codex-rs/core/src/session/review.rs +++ b/codex-rs/core/src/session/review.rs @@ -1,6 +1,5 @@ use super::*; use codex_core_skills::HostLoadedSkills; -use codex_protocol::openai_models::ToolMode; use std::sync::atomic::AtomicBool; /// Spawn a review thread using the given prompt. @@ -47,15 +46,13 @@ pub(super) async fn spawn_review_thread( let mut per_turn_config = (*config).clone(); per_turn_config.model = Some(model.clone()); per_turn_config.features = review_features.clone(); - let tool_mode = model_info.tool_mode.unwrap_or_else(|| { - if per_turn_config.features.enabled(Feature::CodeModeOnly) { - ToolMode::CodeModeOnly - } else if per_turn_config.features.enabled(Feature::CodeMode) { - ToolMode::CodeMode - } else { - ToolMode::Direct - } - }); + per_turn_config.permissions.shell_environment_policy = parent_turn_context + .config + .permissions + .shell_environment_policy + .clone(); + per_turn_config.codex_linux_sandbox_exe = + parent_turn_context.config.codex_linux_sandbox_exe.clone(); if let Err(err) = per_turn_config.web_search_mode.set(review_web_search_mode) { let fallback_value = per_turn_config.web_search_mode.value(); tracing::warn!( @@ -113,20 +110,15 @@ pub(super) async fn spawn_review_thread( config: per_turn_config, auth_manager: auth_manager_for_context, model_info: model_info.clone(), - comp_hash: model_info.comp_hash.clone(), - tool_mode, session_telemetry: session_telemetry_for_context, provider: provider_for_context, reasoning_effort, reasoning_summary, session_source, parent_thread_id: parent_turn_context.parent_thread_id, - thread_source: parent_turn_context.thread_source.clone(), environments: parent_turn_context.environments.clone(), available_models, unified_exec_shell_mode, - features: review_features, - ghost_snapshot: parent_turn_context.ghost_snapshot.clone(), current_date: parent_turn_context.current_date.clone(), timezone: parent_turn_context.timezone.clone(), app_server_client_name: parent_turn_context.app_server_client_name.clone(), @@ -140,14 +132,10 @@ pub(super) async fn spawn_review_thread( permission_profile: parent_turn_context.permission_profile(), network: parent_turn_context.network.clone(), windows_sandbox_level: parent_turn_context.windows_sandbox_level, - shell_environment_policy: parent_turn_context.shell_environment_policy.clone(), #[allow(deprecated)] cwd: parent_turn_context.cwd.clone(), final_output_json_schema: None, - codex_self_exe: parent_turn_context.codex_self_exe.clone(), - codex_linux_sandbox_exe: parent_turn_context.codex_linux_sandbox_exe.clone(), dynamic_tools: parent_turn_context.dynamic_tools.clone(), - truncation_policy: model_info.truncation_policy.into(), turn_metadata_state, extension_data, turn_skills: TurnSkillsContext::new(parent_turn_context.turn_skills.outcome.clone()), diff --git a/codex-rs/core/src/session/rollout_reconstruction.rs b/codex-rs/core/src/session/rollout_reconstruction.rs index 34e7807a9a5a..ab4838b2ffad 100644 --- a/codex-rs/core/src/session/rollout_reconstruction.rs +++ b/codex-rs/core/src/session/rollout_reconstruction.rs @@ -271,14 +271,14 @@ impl Session { RolloutItem::ResponseItem(response_item) => { history.record_items( std::iter::once(response_item), - turn_context.truncation_policy, + turn_context.model_info.truncation_policy.into(), ); } RolloutItem::InterAgentCommunication(communication) => { let response_item = communication.to_model_input_item(); history.record_items( std::iter::once(&response_item), - turn_context.truncation_policy, + turn_context.model_info.truncation_policy.into(), ); } RolloutItem::Compacted(compacted) => { diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index b7ddb90b1ca5..473adaa835e2 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -3530,7 +3530,7 @@ async fn includes_timed_out_message() { }; let (_, turn_context) = make_session_and_context().await; - let out = format_exec_output_str(&exec, turn_context.truncation_policy); + let out = format_exec_output_str(&exec, turn_context.model_info.truncation_policy.into()); assert_eq!( out, @@ -3569,10 +3569,6 @@ async fn turn_context_with_model_updates_model_fields() { updated.config.model_reasoning_effort, Some(ReasoningEffortConfig::Medium) ); - assert_eq!( - updated.truncation_policy, - expected_model_info.truncation_policy.into() - ); } #[test] @@ -8118,18 +8114,6 @@ fn file_system_policy_with_unreadable_glob(turn_context: &TurnContext) -> FileSy policy } -#[tokio::test] -async fn turn_context_item_uses_turn_context_comp_hash_snapshot() { - let (_session, mut turn_context) = make_session_and_context().await; - turn_context.comp_hash = Some("turn-context-hash".to_string()); - turn_context.model_info.comp_hash = Some("model-info-hash".to_string()); - - assert_eq!( - turn_context.to_turn_context_item().comp_hash.as_deref(), - Some("turn-context-hash") - ); -} - #[tokio::test] async fn turn_context_item_stores_local_cwd() { let (_session, mut turn_context) = make_session_and_context().await; @@ -9706,7 +9690,7 @@ async fn sample_rollout( } live_history.record_items( initial_context.iter(), - reconstruction_turn.truncation_policy, + reconstruction_turn.model_info.truncation_policy.into(), ); let user1 = ResponseItem::Message { @@ -9720,7 +9704,7 @@ async fn sample_rollout( }; live_history.record_items( std::iter::once(&user1), - reconstruction_turn.truncation_policy, + reconstruction_turn.model_info.truncation_policy.into(), ); rollout_items.push(RolloutItem::ResponseItem(user1.clone())); @@ -9735,7 +9719,7 @@ async fn sample_rollout( }; live_history.record_items( std::iter::once(&assistant1), - reconstruction_turn.truncation_policy, + reconstruction_turn.model_info.truncation_policy.into(), ); rollout_items.push(RolloutItem::ResponseItem(assistant1.clone())); @@ -9763,7 +9747,7 @@ async fn sample_rollout( }; live_history.record_items( std::iter::once(&user2), - reconstruction_turn.truncation_policy, + reconstruction_turn.model_info.truncation_policy.into(), ); rollout_items.push(RolloutItem::ResponseItem(user2.clone())); @@ -9778,7 +9762,7 @@ async fn sample_rollout( }; live_history.record_items( std::iter::once(&assistant2), - reconstruction_turn.truncation_policy, + reconstruction_turn.model_info.truncation_policy.into(), ); rollout_items.push(RolloutItem::ResponseItem(assistant2.clone())); @@ -9806,7 +9790,7 @@ async fn sample_rollout( }; live_history.record_items( std::iter::once(&user3), - reconstruction_turn.truncation_policy, + reconstruction_turn.model_info.truncation_policy.into(), ); rollout_items.push(RolloutItem::ResponseItem(user3)); @@ -9821,7 +9805,7 @@ async fn sample_rollout( }; live_history.record_items( std::iter::once(&assistant3), - reconstruction_turn.truncation_policy, + reconstruction_turn.model_info.truncation_policy.into(), ); rollout_items.push(RolloutItem::ResponseItem(assistant3)); diff --git a/codex-rs/core/src/session/tests/guardian_tests.rs b/codex-rs/core/src/session/tests/guardian_tests.rs index e958b39e1f7d..43cd91a99d3f 100644 --- a/codex-rs/core/src/session/tests/guardian_tests.rs +++ b/codex-rs/core/src/session/tests/guardian_tests.rs @@ -95,11 +95,11 @@ async fn request_permissions_routes_to_guardian_when_reviewer_is_enabled() { .approval_policy .set(AskForApproval::OnRequest) .expect("test setup should allow updating approval policy"); - turn_context_raw + let mut config = (*turn_context_raw.config).clone(); + config .features .enable(Feature::GuardianApproval) .expect("test setup should allow enabling guardian approvals"); - let mut config = (*turn_context_raw.config).clone(); config.approvals_reviewer = ApprovalsReviewer::AutoReview; config.model_provider.base_url = Some(format!("{}/v1", server.uri())); let config = Arc::new(config); @@ -183,11 +183,11 @@ async fn request_permissions_guardian_review_stops_when_cancelled() { .approval_policy .set(AskForApproval::OnRequest) .expect("test setup should allow updating approval policy"); - turn_context_raw + let mut config = (*turn_context_raw.config).clone(); + config .features .enable(Feature::GuardianApproval) .expect("test setup should allow enabling guardian approvals"); - let mut config = (*turn_context_raw.config).clone(); config.approvals_reviewer = ApprovalsReviewer::AutoReview; config.model_provider.base_url = Some(format!("{}/v1", server.uri())); let config = Arc::new(config); @@ -292,21 +292,21 @@ async fn guardian_allows_shell_command_additional_permissions_requests_past_poli .await; let (mut session, mut turn_context_raw) = make_session_and_context().await; - turn_context_raw.codex_linux_sandbox_exe = codex_linux_sandbox_exe_or_skip!(); turn_context_raw .approval_policy .set(AskForApproval::OnRequest) .expect("test setup should allow updating approval policy"); - turn_context_raw - .features - .enable(Feature::GuardianApproval) - .expect("test setup should allow enabling guardian approvals"); session .features .enable(Feature::ExecPermissionApprovals) .expect("test setup should allow enabling request permissions"); turn_context_raw.permission_profile = codex_protocol::models::PermissionProfile::Disabled; let mut config = (*turn_context_raw.config).clone(); + config.codex_linux_sandbox_exe = codex_linux_sandbox_exe_or_skip!(); + config + .features + .enable(Feature::GuardianApproval) + .expect("test setup should allow enabling guardian approvals"); config.model_provider.base_url = Some(format!("{}/v1", server.uri())); let config = Arc::new(config); let models_manager = models_manager_with_provider( @@ -467,7 +467,7 @@ async fn guardian_allows_unified_exec_additional_permissions_requests_past_polic .approval_policy .set(AskForApproval::OnRequest) .expect("test setup should allow updating approval policy"); - turn_context_raw + Arc::make_mut(&mut turn_context_raw.config) .features .enable(Feature::GuardianApproval) .expect("test setup should allow enabling guardian approvals"); diff --git a/codex-rs/core/src/session/token_budget.rs b/codex-rs/core/src/session/token_budget.rs index b8f9f82fbc0c..4548ed3198a7 100644 --- a/codex-rs/core/src/session/token_budget.rs +++ b/codex-rs/core/src/session/token_budget.rs @@ -11,7 +11,7 @@ pub(super) async fn maybe_record_token_budget_remaining_context( tokens_before_sampling: i64, tokens_after_sampling: i64, ) { - if !turn_context.features.enabled(Feature::TokenBudget) { + if !turn_context.config.features.enabled(Feature::TokenBudget) { return; } let Some(model_context_window) = turn_context.model_context_window() else { diff --git a/codex-rs/core/src/session/turn.rs b/codex-rs/core/src/session/turn.rs index 4b052cb659bf..43ee23c9938c 100644 --- a/codex-rs/core/src/session/turn.rs +++ b/codex-rs/core/src/session/turn.rs @@ -177,7 +177,7 @@ pub(crate) async fn run_turn( .await; sess.set_previous_turn_settings(Some(PreviousTurnSettings { model: turn_context.model_info.slug.clone(), - comp_hash: turn_context.comp_hash.clone(), + comp_hash: turn_context.model_info.comp_hash.clone(), realtime_active: Some(turn_context.realtime_active), })) .await; @@ -840,7 +840,7 @@ async fn maybe_run_previous_model_inline_compact( }; let should_compact_for_comp_hash_change = comp_hash_changed( previous_turn_settings.comp_hash.as_deref(), - turn_context.comp_hash.as_deref(), + turn_context.model_info.comp_hash.as_deref(), ); let previous_model_turn_context = Arc::new( turn_context @@ -913,7 +913,11 @@ async fn run_auto_compact( phase: CompactionPhase, ) -> CodexResult<()> { if should_use_remote_compact_task(turn_context.provider.info()) { - if turn_context.features.enabled(Feature::RemoteCompactionV2) { + if turn_context + .config + .features + .enabled(Feature::RemoteCompactionV2) + { emit_compact_metric( &sess.services.session_telemetry, "remote_v2", diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index 526a16baccf3..78c2da768a45 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -1,7 +1,6 @@ use super::*; use crate::SkillLoadOutcome; use crate::agents_md::LoadedAgentsMd; -use crate::config::GhostSnapshotConfig; use crate::environment_selection::TurnEnvironmentSnapshot; use crate::shell_snapshot::ShellSnapshotFile; use codex_core_skills::HostLoadedSkills; @@ -12,9 +11,7 @@ use codex_protocol::SessionId; use codex_protocol::ThreadId; use codex_protocol::models::AdditionalPermissionProfile; use codex_protocol::openai_models::ModelInfo; -use codex_protocol::openai_models::ToolMode; use codex_protocol::protocol::MultiAgentVersion; -use codex_protocol::protocol::ThreadSource; use codex_protocol::protocol::TurnEnvironmentSelection; use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile; use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy; @@ -111,15 +108,12 @@ pub struct TurnContext { pub config: Arc, pub(crate) auth_manager: Option>, pub(crate) model_info: ModelInfo, - pub(crate) comp_hash: Option, - pub(crate) tool_mode: ToolMode, pub(crate) session_telemetry: SessionTelemetry, pub(crate) provider: SharedModelProvider, pub(crate) reasoning_effort: Option, pub(crate) reasoning_summary: ReasoningSummaryConfig, pub(crate) session_source: SessionSource, pub(crate) parent_thread_id: Option, - pub(crate) thread_source: Option, pub(crate) environments: TurnEnvironmentSnapshot, /// The session's absolute working directory. All relative paths provided /// by the model as well as sandbox policies are resolved against this path @@ -139,15 +133,9 @@ pub struct TurnContext { pub(crate) permission_profile: PermissionProfile, pub(crate) network: Option, pub(crate) windows_sandbox_level: WindowsSandboxLevel, - pub(crate) shell_environment_policy: ShellEnvironmentPolicy, pub(crate) available_models: Vec, pub(crate) unified_exec_shell_mode: UnifiedExecShellMode, - pub features: ManagedFeatures, - pub(crate) ghost_snapshot: GhostSnapshotConfig, pub(crate) final_output_json_schema: Option, - pub(crate) codex_self_exe: Option, - pub(crate) codex_linux_sandbox_exe: Option, - pub(crate) truncation_policy: TruncationPolicy, pub(crate) dynamic_tools: Vec, pub(crate) turn_metadata_state: Arc, pub(crate) extension_data: Arc, @@ -214,7 +202,9 @@ impl TurnContext { .auth_manager .as_deref() .is_some_and(AuthManager::current_auth_uses_codex_backend); - self.features.apps_enabled_for_auth(uses_codex_backend) + self.config + .features + .apps_enabled_for_auth(uses_codex_backend) } pub(crate) fn tool_environment_mode(&self) -> ToolEnvironmentMode { @@ -231,16 +221,6 @@ impl TurnContext { let model_info = models_manager .get_model_info(model.as_str(), &config.to_models_manager_config()) .await; - let tool_mode = model_info.tool_mode.unwrap_or_else(|| { - if config.features.enabled(Feature::CodeModeOnly) { - ToolMode::CodeModeOnly - } else if config.features.enabled(Feature::CodeMode) { - ToolMode::CodeMode - } else { - ToolMode::Direct - } - }); - let truncation_policy = model_info.truncation_policy.into(); let supported_reasoning_levels = model_info .supported_reasoning_levels .iter() @@ -269,7 +249,6 @@ impl TurnContext { Some(reasoning_effort.clone()), /*developer_instructions*/ None, ); - let features = self.features.clone(); let available_models = models_manager .list_models(RefreshStrategy::OnlineIfUncached) .await; @@ -281,8 +260,6 @@ impl TurnContext { config: Arc::new(config), auth_manager: self.auth_manager.clone(), model_info: model_info.clone(), - comp_hash: model_info.comp_hash.clone(), - tool_mode, session_telemetry: self .session_telemetry .clone() @@ -292,7 +269,6 @@ impl TurnContext { reasoning_summary: self.reasoning_summary, session_source: self.session_source.clone(), parent_thread_id: self.parent_thread_id, - thread_source: self.thread_source.clone(), environments: self.environments.clone(), #[allow(deprecated)] cwd: self.cwd.clone(), @@ -309,15 +285,9 @@ impl TurnContext { permission_profile: self.permission_profile.clone(), network: self.network.clone(), windows_sandbox_level: self.windows_sandbox_level, - shell_environment_policy: self.shell_environment_policy.clone(), available_models, unified_exec_shell_mode: self.unified_exec_shell_mode.clone(), - features, - ghost_snapshot: self.ghost_snapshot.clone(), final_output_json_schema: self.final_output_json_schema.clone(), - codex_self_exe: self.codex_self_exe.clone(), - codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.clone(), - truncation_policy, dynamic_tools: self.dynamic_tools.clone(), turn_metadata_state: self.turn_metadata_state.clone(), extension_data: Arc::clone(&self.extension_data), @@ -368,7 +338,7 @@ impl TurnContext { .config .permissions .windows_sandbox_private_desktop, - use_legacy_landlock: self.features.use_legacy_landlock(), + use_legacy_landlock: self.config.features.use_legacy_landlock(), } } @@ -410,7 +380,7 @@ impl TurnContext { network: self.turn_context_network_item(), file_system_sandbox_policy: self.non_legacy_file_system_sandbox_policy(), model: self.model_info.slug.clone(), - comp_hash: self.comp_hash.clone(), + comp_hash: self.model_info.comp_hash.clone(), personality: self.personality, collaboration_mode: Some(self.collaboration_mode.clone()), multi_agent_version: Some(self.multi_agent_version), @@ -549,15 +519,6 @@ impl Session { ); let mut per_turn_config = per_turn_config; - let tool_mode = model_info.tool_mode.unwrap_or_else(|| { - if per_turn_config.features.enabled(Feature::CodeModeOnly) { - ToolMode::CodeModeOnly - } else if per_turn_config.features.enabled(Feature::CodeMode) { - ToolMode::CodeMode - } else { - ToolMode::Direct - } - }); per_turn_config.service_tier = get_service_tier( per_turn_config.service_tier, per_turn_config.features.enabled(Feature::FastMode), @@ -586,15 +547,12 @@ impl Session { config: per_turn_config.clone(), auth_manager: auth_manager_for_context, model_info: model_info.clone(), - comp_hash: model_info.comp_hash.clone(), - tool_mode, session_telemetry: session_telemetry_for_context, provider: provider_for_context, reasoning_effort, reasoning_summary, session_source, parent_thread_id: session_configuration.parent_thread_id, - thread_source: session_configuration.thread_source.clone(), environments, #[allow(deprecated)] cwd, @@ -614,15 +572,9 @@ impl Session { permission_profile: session_configuration.permission_profile(), network, windows_sandbox_level: session_configuration.windows_sandbox_level, - shell_environment_policy: per_turn_config.permissions.shell_environment_policy.clone(), available_models, unified_exec_shell_mode, - features: per_turn_config.features.clone(), - ghost_snapshot: per_turn_config.ghost_snapshot.clone(), final_output_json_schema: None, - codex_self_exe: per_turn_config.codex_self_exe.clone(), - codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(), - truncation_policy: model_info.truncation_policy.into(), dynamic_tools: session_configuration.dynamic_tools.clone(), turn_metadata_state, extension_data, diff --git a/codex-rs/core/src/tasks/compact.rs b/codex-rs/core/src/tasks/compact.rs index adc98e413a8f..1b8127e67713 100644 --- a/codex-rs/core/src/tasks/compact.rs +++ b/codex-rs/core/src/tasks/compact.rs @@ -31,6 +31,7 @@ impl SessionTask for CompactTask { let session = session.clone_session(); let _ = if crate::compact::should_use_remote_compact_task(ctx.provider.info()) { if ctx + .config .features .enabled(codex_features::Feature::RemoteCompactionV2) { diff --git a/codex-rs/core/src/tasks/mod.rs b/codex-rs/core/src/tasks/mod.rs index b2f337ec42f2..3ff0e2d5f7ae 100644 --- a/codex-rs/core/src/tasks/mod.rs +++ b/codex-rs/core/src/tasks/mod.rs @@ -722,7 +722,7 @@ impl Session { } emit_turn_memory_metric( &self.services.session_telemetry, - turn_context.features.enabled(Feature::MemoryTool), + turn_context.config.features.enabled(Feature::MemoryTool), turn_context.config.memories.use_memories, turn_had_memory_citation, ); diff --git a/codex-rs/core/src/tasks/user_shell.rs b/codex-rs/core/src/tasks/user_shell.rs index 4c3049351729..37835c36a10f 100644 --- a/codex-rs/core/src/tasks/user_shell.rs +++ b/codex-rs/core/src/tasks/user_shell.rs @@ -157,7 +157,7 @@ pub(crate) async fn execute_user_shell_command( }; let shell_snapshot_location = turn_environment.shell_snapshot(&cwd); let mut exec_env_map = create_env( - &turn_context.shell_environment_policy, + &turn_context.config.permissions.shell_environment_policy, Some(session.thread_id), ); if exec_env_map.contains_key(PROXY_ACTIVE_ENV_KEY) { @@ -167,7 +167,11 @@ pub(crate) async fn execute_user_shell_command( &display_command, environment_shell, shell_snapshot_location.as_ref(), - &turn_context.shell_environment_policy.r#set, + &turn_context + .config + .permissions + .shell_environment_policy + .r#set, &mut exec_env_map, ); @@ -294,7 +298,7 @@ pub(crate) async fn execute_user_shell_command( duration: output.duration, formatted_output: format_exec_output_str( &output, - turn_context.truncation_policy, + turn_context.model_info.truncation_policy.into(), ), status: if output.exit_code == 0 { ExecCommandStatus::Completed @@ -339,7 +343,7 @@ pub(crate) async fn execute_user_shell_command( duration: exec_output.duration, formatted_output: format_exec_output_str( &exec_output, - turn_context.truncation_policy, + turn_context.model_info.truncation_policy.into(), ), status: ExecCommandStatus::Failed, }), diff --git a/codex-rs/core/src/tools/code_mode/mod.rs b/codex-rs/core/src/tools/code_mode/mod.rs index 92bb5a8ad634..09918033e667 100644 --- a/codex-rs/core/src/tools/code_mode/mod.rs +++ b/codex-rs/core/src/tools/code_mode/mod.rs @@ -26,6 +26,7 @@ use crate::tools::ToolRouter; use crate::tools::context::FunctionToolOutput; use crate::tools::context::SharedTurnDiffTracker; use crate::tools::context::ToolPayload; +use crate::tools::effective_tool_mode; use crate::tools::parallel::ToolCallRuntime; use crate::tools::router::ToolCall; use crate::tools::router::ToolCallSource; @@ -116,7 +117,8 @@ impl CodeModeService { router: Arc, tracker: SharedTurnDiffTracker, ) -> Option { - if !matches!(turn.tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly) + let tool_mode = effective_tool_mode(turn); + if !matches!(tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly) || self.session.is_none() { return None; diff --git a/codex-rs/core/src/tools/events.rs b/codex-rs/core/src/tools/events.rs index 2751780d5858..c30cee84ca19 100644 --- a/codex-rs/core/src/tools/events.rs +++ b/codex-rs/core/src/tools/events.rs @@ -346,7 +346,7 @@ impl ToolEmitter { output: &ExecToolCallOutput, ctx: ToolEventCtx<'_>, ) -> String { - super::format_exec_output_for_model(output, ctx.turn.truncation_policy) + super::format_exec_output_for_model(output, ctx.turn.model_info.truncation_policy.into()) } pub async fn finish( @@ -495,7 +495,10 @@ async fn emit_exec_stage( aggregated_output: output.aggregated_output.text.clone(), exit_code: output.exit_code, duration: output.duration, - formatted_output: format_exec_output_str(&output, ctx.turn.truncation_policy), + formatted_output: format_exec_output_str( + &output, + ctx.turn.model_info.truncation_policy.into(), + ), status: if output.exit_code == 0 { ExecCommandStatus::Completed } else { diff --git a/codex-rs/core/src/tools/handlers/apply_patch.rs b/codex-rs/core/src/tools/handlers/apply_patch.rs index 2ce9119e3580..9004eb9922d1 100644 --- a/codex-rs/core/src/tools/handlers/apply_patch.rs +++ b/codex-rs/core/src/tools/handlers/apply_patch.rs @@ -82,7 +82,11 @@ impl ToolArgumentDiffConsumer for ApplyPatchArgumentDiffConsumer { call_id: String, diff: &str, ) -> Option { - if !turn.features.enabled(Feature::ApplyPatchStreamingEvents) { + if !turn + .config + .features + .enabled(Feature::ApplyPatchStreamingEvents) + { return None; } diff --git a/codex-rs/core/src/tools/handlers/extension_tools.rs b/codex-rs/core/src/tools/handlers/extension_tools.rs index b59bfaec02db..ae79ba405d5b 100644 --- a/codex-rs/core/src/tools/handlers/extension_tools.rs +++ b/codex-rs/core/src/tools/handlers/extension_tools.rs @@ -143,7 +143,7 @@ async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall { call_id: invocation.call_id.clone(), tool_name: invocation.tool_name.clone(), model: invocation.turn.model_info.slug.clone(), - truncation_policy: invocation.turn.truncation_policy, + truncation_policy: invocation.turn.model_info.truncation_policy.into(), conversation_history, turn_item_emitter: Arc::new(CoreTurnItemEmitter { session: Arc::downgrade(&invocation.session), @@ -315,7 +315,7 @@ mod tests { let weak_turn = Arc::downgrade(&turn); let turn_id = turn.sub_id.clone(); let model = turn.model_info.slug.clone(); - let truncation_policy = turn.truncation_policy; + let truncation_policy = turn.model_info.truncation_policy.into(); let expected_sandbox_cwds = turn .environments .turn_environments diff --git a/codex-rs/core/src/tools/handlers/mcp.rs b/codex-rs/core/src/tools/handlers/mcp.rs index efb32aab945d..016a0344cda5 100644 --- a/codex-rs/core/src/tools/handlers/mcp.rs +++ b/codex-rs/core/src/tools/handlers/mcp.rs @@ -156,7 +156,7 @@ impl McpHandler { tool_input: result.tool_input, wall_time: started.elapsed(), original_image_detail_supported: can_request_original_image_detail(&turn.model_info), - truncation_policy: turn.truncation_policy, + truncation_policy: turn.model_info.truncation_policy.into(), })) } } diff --git a/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs b/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs index 80b183dababc..2af409cf200c 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs @@ -115,9 +115,10 @@ impl ListMcpResourceTemplatesHandler { } } .await; + let truncation_policy = turn.model_info.truncation_policy.into(); match payload_result { - Ok(payload) => match serialize_function_output(payload, turn.truncation_policy) { + Ok(payload) => match serialize_function_output(payload, truncation_policy) { Ok(output) => { let content = function_call_output_content_items_to_text(&output.body) .unwrap_or_default(); diff --git a/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resources.rs b/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resources.rs index a77d617b942b..1a6a6e2014a4 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resources.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resources.rs @@ -113,9 +113,10 @@ impl ListMcpResourcesHandler { } } .await; + let truncation_policy = turn.model_info.truncation_policy.into(); match payload_result { - Ok(payload) => match serialize_function_output(payload, turn.truncation_policy) { + Ok(payload) => match serialize_function_output(payload, truncation_policy) { Ok(output) => { let content = function_call_output_content_items_to_text(&output.body) .unwrap_or_default(); diff --git a/codex-rs/core/src/tools/handlers/mcp_resource/read_mcp_resource.rs b/codex-rs/core/src/tools/handlers/mcp_resource/read_mcp_resource.rs index 9ca15718a0ce..f1ee6b462cf2 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource/read_mcp_resource.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource/read_mcp_resource.rs @@ -96,9 +96,10 @@ impl ReadMcpResourceHandler { }) } .await; + let truncation_policy = turn.model_info.truncation_policy.into(); match payload_result { - Ok(payload) => match serialize_function_output(payload, turn.truncation_policy) { + Ok(payload) => match serialize_function_output(payload, truncation_policy) { Ok(output) => { let content = function_call_output_content_items_to_text(&output.body) .unwrap_or_default(); diff --git a/codex-rs/core/src/tools/handlers/multi_agents_common.rs b/codex-rs/core/src/tools/handlers/multi_agents_common.rs index 595889215241..6ce21c8f2aa0 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_common.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_common.rs @@ -263,8 +263,6 @@ pub(crate) fn apply_spawn_agent_runtime_overrides( FunctionCallError::RespondToModel(format!("approval_policy is invalid: {err}")) })?; config.approvals_reviewer = turn.config.approvals_reviewer; - config.permissions.shell_environment_policy = turn.shell_environment_policy.clone(); - config.codex_linux_sandbox_exe = turn.codex_linux_sandbox_exe.clone(); #[allow(deprecated)] let turn_cwd = turn.cwd.clone(); config.cwd = turn_cwd; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index ebb452c1de08..c9e0158c9d0d 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -4468,16 +4468,18 @@ async fn build_agent_spawn_config_uses_turn_context_values() { }; turn.developer_instructions = Some("dev".to_string()); turn.compact_prompt = Some("compact".to_string()); - turn.shell_environment_policy = ShellEnvironmentPolicy { + let mut config = (*turn.config).clone(); + config.permissions.shell_environment_policy = ShellEnvironmentPolicy { use_profile: true, ..ShellEnvironmentPolicy::default() }; + config.codex_linux_sandbox_exe = Some(PathBuf::from("/bin/echo")); + turn.config = Arc::new(config); let temp_dir = tempfile::tempdir().expect("temp dir"); #[allow(deprecated)] { turn.cwd = temp_dir.abs(); } - turn.codex_linux_sandbox_exe = Some(PathBuf::from("/bin/echo")); #[allow(deprecated)] let turn_cwd = turn.cwd.clone(); let sandbox_policy = pick_allowed_sandbox_policy( @@ -4507,8 +4509,6 @@ async fn build_agent_spawn_config_uses_turn_context_values() { expected.model_reasoning_summary = Some(turn.reasoning_summary); expected.developer_instructions = turn.developer_instructions.clone(); expected.compact_prompt = turn.compact_prompt.clone(); - expected.permissions.shell_environment_policy = turn.shell_environment_policy.clone(); - expected.codex_linux_sandbox_exe = turn.codex_linux_sandbox_exe.clone(); #[allow(deprecated)] { expected.cwd = turn.cwd.clone(); @@ -4545,8 +4545,6 @@ async fn build_agent_resume_config_clears_base_instructions() { expected.model_reasoning_summary = Some(turn.reasoning_summary); expected.developer_instructions = turn.developer_instructions.clone(); expected.compact_prompt = turn.compact_prompt.clone(); - expected.permissions.shell_environment_policy = turn.shell_environment_policy.clone(); - expected.codex_linux_sandbox_exe = turn.codex_linux_sandbox_exe.clone(); #[allow(deprecated)] { expected.cwd = turn.cwd.clone(); diff --git a/codex-rs/core/src/tools/handlers/shell.rs b/codex-rs/core/src/tools/handlers/shell.rs index 91f2c64d3c3a..3e1f160e9c5c 100644 --- a/codex-rs/core/src/tools/handlers/shell.rs +++ b/codex-rs/core/src/tools/handlers/shell.rs @@ -80,7 +80,12 @@ async fn run_exec_like(args: RunExecLikeArgs) -> Result Result ToolMode { + turn_context.model_info.tool_mode.unwrap_or_else(|| { + if turn_context.config.features.enabled(Feature::CodeModeOnly) { + ToolMode::CodeModeOnly + } else if turn_context.config.features.enabled(Feature::CodeMode) { + ToolMode::CodeMode + } else { + ToolMode::Direct + } + }) +} + /// Format the combined exec output for sending back to the model. /// Includes exit code and duration metadata; truncates large bodies safely. pub fn format_exec_output_for_model( diff --git a/codex-rs/core/src/tools/orchestrator.rs b/codex-rs/core/src/tools/orchestrator.rs index 7e97e4ad5144..14c6ad74dc23 100644 --- a/codex-rs/core/src/tools/orchestrator.rs +++ b/codex-rs/core/src/tools/orchestrator.rs @@ -237,7 +237,7 @@ impl ToolOrchestrator { }; // Platform-specific flag gating is handled by SandboxManager::select_initial. - let use_legacy_landlock = turn_ctx.features.use_legacy_landlock(); + let use_legacy_landlock = turn_ctx.config.features.use_legacy_landlock(); #[allow(deprecated)] let sandbox_cwd = tool.sandbox_cwd(req).unwrap_or(&turn_ctx.cwd); let sandbox_policy_cwd = PathUri::from_abs_path(sandbox_cwd); @@ -249,7 +249,7 @@ impl ToolOrchestrator { manager: &self.sandbox, sandbox_cwd: &sandbox_policy_cwd, workspace_roots: workspace_roots.as_slice(), - codex_linux_sandbox_exe: turn_ctx.codex_linux_sandbox_exe.as_ref(), + codex_linux_sandbox_exe: turn_ctx.config.codex_linux_sandbox_exe.as_ref(), use_legacy_landlock, windows_sandbox_level: turn_ctx.windows_sandbox_level, windows_sandbox_private_desktop: turn_ctx @@ -413,7 +413,7 @@ impl ToolOrchestrator { let retry_codex_linux_sandbox_exe = if unsandboxed_allowed { None } else { - turn_ctx.codex_linux_sandbox_exe.as_ref() + turn_ctx.config.codex_linux_sandbox_exe.as_ref() }; let retry_attempt = SandboxAttempt { sandbox: retry_sandbox, diff --git a/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs b/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs index ea53e9a55c89..f2df49708923 100644 --- a/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs +++ b/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs @@ -185,8 +185,8 @@ pub(super) async fn try_run_zsh_fork( arg0, sandbox_policy_cwd, windows_sandbox_workspace_roots, - codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(), - use_legacy_landlock: ctx.turn.features.use_legacy_landlock(), + codex_linux_sandbox_exe: ctx.turn.config.codex_linux_sandbox_exe.clone(), + use_legacy_landlock: ctx.turn.config.features.use_legacy_landlock(), }; let main_execve_wrapper_exe = ctx .session @@ -286,8 +286,8 @@ pub(crate) async fn prepare_unified_exec_zsh_fork( arg0: exec_request.arg0.clone(), sandbox_policy_cwd: exec_request.windows_sandbox_policy_cwd.clone(), windows_sandbox_workspace_roots: exec_request.windows_sandbox_workspace_roots.clone(), - codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(), - use_legacy_landlock: ctx.turn.features.use_legacy_landlock(), + codex_linux_sandbox_exe: ctx.turn.config.codex_linux_sandbox_exe.clone(), + use_legacy_landlock: ctx.turn.config.features.use_legacy_landlock(), }; let escalation_policy = CoreShellActionProvider { policy: Arc::clone(&exec_policy), diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index 9269aa379bc6..7680e5d4ffaf 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -3,6 +3,7 @@ use crate::agent::next_thread_spawn_depth; use crate::session::turn_context::TurnContext; use crate::tools::code_mode::execute_spec::create_code_mode_tool; use crate::tools::context::ToolInvocation; +use crate::tools::effective_tool_mode; use crate::tools::handlers::ApplyPatchHandler; use crate::tools::handlers::CodeModeExecuteHandler; use crate::tools::handlers::CodeModeWaitHandler; @@ -243,10 +244,9 @@ fn spec_for_model_request( tool_name: &ToolName, spec: ToolSpec, ) -> ToolSpec { - if matches!( - turn_context.tool_mode, - ToolMode::CodeMode | ToolMode::CodeModeOnly - ) && exposure != ToolExposure::DirectModelOnly + let tool_mode = effective_tool_mode(turn_context); + if matches!(tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly) + && exposure != ToolExposure::DirectModelOnly && !is_excluded_from_code_mode(turn_context, tool_name) && codex_code_mode::is_code_mode_nested_tool(spec.name()) { @@ -298,7 +298,7 @@ pub(crate) fn search_tool_enabled(turn_context: &TurnContext) -> bool { } pub(crate) fn tool_suggest_enabled(turn_context: &TurnContext) -> bool { - let features = turn_context.features.get(); + let features = turn_context.config.features.get(); features.enabled(Feature::ToolSuggest) && features.enabled(Feature::Apps) && features.enabled(Feature::Plugins) @@ -324,7 +324,12 @@ fn collab_tools_enabled(turn_context: &TurnContext) -> bool { } fn agent_jobs_tools_enabled(turn_context: &TurnContext) -> bool { - turn_context.features.get().enabled(Feature::SpawnCsv) && collab_tools_enabled(turn_context) + turn_context + .config + .features + .get() + .enabled(Feature::SpawnCsv) + && collab_tools_enabled(turn_context) } fn agent_jobs_worker_tools_enabled(turn_context: &TurnContext) -> bool { @@ -339,6 +344,7 @@ fn agent_jobs_worker_tools_enabled(turn_context: &TurnContext) -> bool { fn image_generation_tool_enabled(turn_context: &TurnContext) -> bool { image_generation_runtime_enabled(turn_context) && turn_context + .config .features .get() .enabled(Feature::ImageGeneration) @@ -365,7 +371,11 @@ fn standalone_image_generation_model_visible(turn_context: &TurnContext) -> bool return true; } - turn_context.features.get().enabled(Feature::ImageGenExt) + turn_context + .config + .features + .get() + .enabled(Feature::ImageGenExt) } fn standalone_image_generation_available( @@ -412,7 +422,8 @@ fn is_hidden_by_code_mode_only( tool_name: &ToolName, exposure: ToolExposure, ) -> bool { - turn_context.tool_mode == ToolMode::CodeModeOnly + let tool_mode = effective_tool_mode(turn_context); + tool_mode == ToolMode::CodeModeOnly && exposure != ToolExposure::DirectModelOnly && codex_code_mode::is_code_mode_nested_tool(&codex_tools::code_mode_name_for_tool_name( tool_name, @@ -433,10 +444,8 @@ fn build_code_mode_executors( turn_context: &TurnContext, executors: &[Arc], ) -> Vec> { - if !matches!( - turn_context.tool_mode, - ToolMode::CodeMode | ToolMode::CodeModeOnly - ) { + let tool_mode = effective_tool_mode(turn_context); + if !matches!(tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly) { return vec![]; } @@ -482,7 +491,7 @@ fn build_code_mode_executors( create_code_mode_tool( &enabled_tools, &namespace_descriptions, - turn_context.tool_mode == ToolMode::CodeModeOnly, + tool_mode == ToolMode::CodeModeOnly, deferred_tools_available, ), code_mode_nested_tool_specs, @@ -577,6 +586,7 @@ fn standalone_web_search_enabled(turn_context: &TurnContext) -> bool { namespace_tools_enabled(turn_context) && (turn_context.model_info.use_responses_lite || turn_context + .config .features .get() .enabled(Feature::StandaloneWebSearch)) @@ -584,7 +594,7 @@ fn standalone_web_search_enabled(turn_context: &TurnContext) -> bool { fn add_shell_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) { let turn_context = context.turn_context; - let features = turn_context.features.get(); + let features = turn_context.config.features.get(); let environment_mode = turn_context.tool_environment_mode(); if !environment_mode.has_environment() { return; @@ -643,7 +653,7 @@ fn add_mcp_resource_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) { let turn_context = context.turn_context; - let features = turn_context.features.get(); + let features = turn_context.config.features.get(); let environment_mode = turn_context.tool_environment_mode(); planned_tools.add(PlanHandler); @@ -915,10 +925,8 @@ fn append_extension_tool_executors( .iter() .map(|executor| executor.tool_name()) .collect::>(); - if matches!( - turn_context.tool_mode, - ToolMode::CodeMode | ToolMode::CodeModeOnly - ) { + let tool_mode = effective_tool_mode(turn_context); + if matches!(tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly) { reserved_tool_names.insert(ToolName::plain(codex_code_mode::PUBLIC_TOOL_NAME)); reserved_tool_names.insert(ToolName::plain(codex_code_mode::WAIT_TOOL_NAME)); } diff --git a/codex-rs/core/src/tools/spec_plan_tests.rs b/codex-rs/core/src/tools/spec_plan_tests.rs index 8a324dcc8ab9..43bcf03a6ccd 100644 --- a/codex-rs/core/src/tools/spec_plan_tests.rs +++ b/codex-rs/core/src/tools/spec_plan_tests.rs @@ -197,16 +197,6 @@ async fn probe(configure_turn: impl FnOnce(&mut TurnContext)) -> ToolPlanProbe { } fn set_feature(turn: &mut TurnContext, feature: Feature, enabled: bool) { - if enabled { - turn.features - .enable(feature) - .expect("test feature should be enableable"); - } else { - turn.features - .disable(feature) - .expect("test feature should be disableable"); - } - let mut config = (*turn.config).clone(); if enabled { config @@ -221,15 +211,6 @@ fn set_feature(turn: &mut TurnContext, feature: Feature, enabled: bool) { } turn.multi_agent_version = config.multi_agent_version_from_features(); turn.config = Arc::new(config); - turn.tool_mode = turn.model_info.tool_mode.unwrap_or_else(|| { - if turn.config.features.enabled(Feature::CodeModeOnly) { - ToolMode::CodeModeOnly - } else if turn.config.features.enabled(Feature::CodeMode) { - ToolMode::CodeMode - } else { - ToolMode::Direct - } - }); } fn set_features(turn: &mut TurnContext, features: &[Feature]) { @@ -1196,7 +1177,6 @@ async fn tool_mode_selector_overrides_feature_flags() { let direct = probe(|turn| { set_features(turn, &[Feature::CodeMode, Feature::CodeModeOnly]); turn.model_info.tool_mode = Some(ToolMode::Direct); - turn.tool_mode = ToolMode::Direct; }) .await; direct.assert_visible_lacks(&[ diff --git a/codex-rs/core/src/unified_exec/mod_tests.rs b/codex-rs/core/src/unified_exec/mod_tests.rs index ad39d62b9663..dce521f6f472 100644 --- a/codex-rs/core/src/unified_exec/mod_tests.rs +++ b/codex-rs/core/src/unified_exec/mod_tests.rs @@ -190,7 +190,7 @@ async fn exec_command_with_tty( chunk_id: generate_chunk_id(), wall_time, raw_output: collected, - truncation_policy: turn.truncation_policy, + truncation_policy: turn.model_info.truncation_policy.into(), max_output_tokens: None, process_id: response_process_id, exit_code, diff --git a/codex-rs/core/src/unified_exec/process_manager.rs b/codex-rs/core/src/unified_exec/process_manager.rs index 702a339e2edb..e683df963c47 100644 --- a/codex-rs/core/src/unified_exec/process_manager.rs +++ b/codex-rs/core/src/unified_exec/process_manager.rs @@ -602,7 +602,7 @@ impl UnifiedExecProcessManager { chunk_id, wall_time, raw_output: collected, - truncation_policy: context.turn.truncation_policy, + truncation_policy: context.turn.model_info.truncation_policy.into(), max_output_tokens: request.max_output_tokens, process_id: response_process_id, exit_code, @@ -1030,7 +1030,7 @@ impl UnifiedExecProcessManager { context: &UnifiedExecContext, ) -> Result<(UnifiedExecProcess, Option), UnifiedExecError> { let local_policy_env = create_env( - &context.turn.shell_environment_policy, + &context.turn.config.permissions.shell_environment_policy, /*thread_id*/ None, ); let mut env = local_policy_env.clone(); @@ -1040,7 +1040,9 @@ impl UnifiedExecProcessManager { ); let env = apply_unified_exec_env(env); let exec_server_env_config = ExecServerEnvConfig { - policy: exec_env_policy_from_shell_policy(&context.turn.shell_environment_policy), + policy: exec_env_policy_from_shell_policy( + &context.turn.config.permissions.shell_environment_policy, + ), local_policy_env, }; let mut orchestrator = ToolOrchestrator::new(); @@ -1072,7 +1074,13 @@ impl UnifiedExecProcessManager { turn_environment: request.turn_environment.clone(), env, exec_server_env_config: Some(exec_server_env_config), - explicit_env_overrides: context.turn.shell_environment_policy.r#set.clone(), + explicit_env_overrides: context + .turn + .config + .permissions + .shell_environment_policy + .r#set + .clone(), network: request.network.clone(), tty: request.tty, sandbox_permissions: request.sandbox_permissions, diff --git a/codex-rs/core/src/user_shell_command.rs b/codex-rs/core/src/user_shell_command.rs index cf034faca00e..e64cf64cdfe4 100644 --- a/codex-rs/core/src/user_shell_command.rs +++ b/codex-rs/core/src/user_shell_command.rs @@ -11,7 +11,10 @@ fn user_shell_command_fragment( exec_output: &ExecToolCallOutput, turn_context: &TurnContext, ) -> UserShellCommand { - let output = format_exec_output_str(exec_output, turn_context.truncation_policy); + let output = format_exec_output_str( + exec_output, + turn_context.model_info.truncation_policy.into(), + ); UserShellCommand::new(command, exec_output.exit_code, exec_output.duration, output) } diff --git a/turn-context-field-lifetimes.md b/turn-context-field-lifetimes.md new file mode 100644 index 000000000000..36776578d6aa --- /dev/null +++ b/turn-context-field-lifetimes.md @@ -0,0 +1,89 @@ +# `TurnContext` field lifetimes + +`TurnContext` is not actually turn-scoped as a whole. It contains four different kinds of state: + +- snapshots of mutable thread settings; +- session-wide live handles; +- values resolved specifically for one turn; +- genuinely mutable turn state. + +Physically, every field lives as long as the longest `Arc`. Normally that is turn completion, but unified-exec watchers retain it until the process exits, and the reusable Guardian trunk can retain the originating context for effectively the rest of the session. + +Relevant ownership points: + +- [`RunningTask` retains the context](codex-rs/core/src/state/turn.rs#L72). +- [The spawned task captures another `Arc`](codex-rs/core/src/tasks/mod.rs#L374). +- [Unified-exec exit watchers retain the context](codex-rs/core/src/unified_exec/async_watcher.rs#L107). +- [Guardian subagent forwarding captures the parent context](codex-rs/core/src/codex_delegate.rs#L142). + +## Terminology + +- **Durable**: copied into `TurnContextItem`, persisted to the rollout, and retained as the in-memory reference context for later turns. +- **Next turn**: copied into `PreviousTurnSettings` for decisions made during a subsequent turn. +- **MCP**: copied into the session-held MCP manager or its runtime context. +- **Child/review**: copied into a child-agent configuration, review context, or alternate-model context. +- **Live use**: read later through a retained `Arc`, without a separate longer-lived projection. + +## Field inventory + +| Field | Real semantic lifetime and origin | Captured and used later | +|---|---|---| +| `sub_id` | Genuine turn/task identifier | Durable; copied into metadata, MCP manager, events, approvals, and child/review machinery | +| `trace_id` | Snapshot of the tracing span when the context is created | Copied into regular, compact, remote-compact, and user-shell task metadata; not durable | +| `realtime_active` | Runtime snapshot taken when the context is built; not live conversation state | Durable and Next turn; copied to review context | +| `config` | Newly allocated per-turn `Config`, mostly copied from thread configuration but rewritten with selected cwd, workspace roots, permission state, reasoning, service tier, and related values | Parts are Durable; full config is cloned into child agents, review, and alternate-model compaction | +| `auth_manager` | Live session-wide shared service handle, not a snapshot | Used later by `apps_enabled()` and copied into review contexts; otherwise redundant with session/provider access | +| `model_info` | Per-turn lookup of model catalog metadata for the selected thread model | Model slug is Durable and Next turn; full object drives sampling/tools and is copied or replaced in child/review contexts | +| `session_telemetry` | Session telemetry handle cloned and rebound to this turn's model and slug | Used through turn finish and copied/rebound for review and alternate-model contexts | +| `provider` | Recreated per turn from thread provider configuration plus live auth | Used for sampling and compaction; provider info is copied into child config and review | +| `reasoning_effort` | Snapshot of thread collaboration settings, with model compatibility adjustment in `with_model` | Durable; copied into model requests, child config, review, and compaction | +| `reasoning_summary` | Thread setting or model-default-derived turn value | Used for model, compact, and child requests; notably not persisted from this field—the rollout compatibility field is hardcoded to `Auto` | +| `session_source` | Immutable thread identity copied into every turn | Copied into metadata and review; child threads derive a new `SessionSource::SubAgent` | +| `parent_thread_id` | Immutable thread relationship copied into every turn | Copied into request metadata, review, and agent-tree handling | +| `environments` | Immutable turn snapshot of thread environment selections | Used throughout tools; selections and cwd escape into MCP and child sessions; review copies it. Underlying `Environment` Arcs and shell-snapshot futures are shared across turns | +| `cwd` | Turn-derived legacy projection: primary selected-environment cwd, otherwise thread fallback cwd | Durable, MCP, metadata, child, and review capture. This is explicitly deprecated and is the largest compatibility duplicate | +| `current_date` | Genuine creation-time turn snapshot | Durable; used in model environment context and copied to review | +| `timezone` | Genuine creation-time turn snapshot | Durable; used in model environment context and copied to review | +| `app_server_client_name` | Thread/session setting snapshot | Used later for hook payloads and plugin-install/tool exposure behavior; copied to review | +| `developer_instructions` | Thread configuration snapshot | Materialized into model history; copied into child config and `with_model`; deliberately cleared for review | +| `compact_prompt` | Thread configuration snapshot | Used only when compacting; copied into child and review contexts | +| `user_instructions` | Rendered snapshot of thread-loaded `AGENTS.md` instructions | Materialized into model history; preserved by `with_model`, cleared for review. Delegated children reload from the parent session rather than this field | +| `collaboration_mode` | Mutable thread setting frozen for this turn | Durable; used throughout the task and copied to review and alternate-model contexts | +| `multi_agent_version` | Session-latched value selected once and then copied into turns | Durable; used for tools and lifecycle. Review forces it to `Disabled` | +| `personality` | Mutable thread setting frozen for this turn | Durable; copied into prompts, compaction, and review | +| `approval_policy` | Mutable thread setting frozen for this turn | Durable; also copied into MCP, child config, review, and approval paths | +| `permission_profile` | Materialized per-turn permission snapshot derived from thread state and workspace roots | Durable; copied into MCP, metadata sandbox tag, child config, review, and execution paths | +| `network` | Turn-selected handle to the live session proxy. The choice of `Some` or `None` is turn-specific, but internals remain shared and live | Used by shell and unified exec and copied to review. Only `is_some()` enters metadata. `TurnContextItem.network` comes from config requirements, not this field | +| `windows_sandbox_level` | Mutable thread setting frozen for this turn | Used by execution and metadata sandbox tagging; copied to review. Child config inherits it indirectly through cloned `config` | +| `available_models` | Snapshot of the models-manager cache at turn creation | Used later only to build multi-agent/model-selection tool specs; review obtains a new list | +| `unified_exec_shell_mode` | Derived from session features, user shell, and runtime paths | Used later by unified-exec tool specs and runtime; recalculated for review | +| `final_output_json_schema` | Genuine request/turn-specific value | Copied into each model `Prompt`; not durable; review clears it | +| `dynamic_tools` | Thread/session configuration copied into every turn | Used by tool-router construction; copied into review | +| `turn_metadata_state` | Genuine mutable turn state | Shared by `with_model`; read for every model and MCP request. Its Git enrichment task clones this state independently and can briefly outlive `TurnContext` | +| `extension_data` | Genuine mutable turn extension store | Explicitly cloned into `RunningTask` and `SessionTaskContext`; shared by alternate-model contexts | +| `turn_skills` | Per-turn resolved skills outcome plus a genuinely turn-local implicit-invocation dedupe set | Outcome is also inserted into extension data. `with_model` shares both; review shares the outcome but creates a fresh dedupe set | +| `turn_timing_state` | Genuine mutable turn/task timing state | Sampling and tool guards retain it independently; read through completion or abort; shared by `with_model` | +| `server_model_warning_emitted` | Per-context response-stream dedupe flag | Used during streaming. `with_model` makes a distinct atomic initialized from the old value; review resets it | +| `model_verification_emitted` | Per-context response-stream dedupe flag | Streaming-only; copied by value into `with_model` and reset for review | + +## Independent longer-lived projections + +- [`TurnContextItem`](codex-rs/core/src/session/turn_context.rs#L409) is the durable rollout and reference-context projection. +- [`PreviousTurnSettings`](codex-rs/core/src/session/turn.rs#L175) retains model, compatibility hash, and realtime state for the next turn. +- [MCP refresh](codex-rs/core/src/session/mcp.rs#L320) captures selected cwd/environment, approval policy, turn ID, and permission profile into a session-held manager. +- [`TurnMetadataState`](codex-rs/core/src/turn_metadata.rs#L85) captures identity, cwd, policy, and metadata into independently mutable turn state. +- [Child-agent config construction](codex-rs/core/src/tools/handlers/multi_agents_common.rs#L220) copies model, provider, reasoning, instructions, approval, shell, cwd, permissions, and sandbox paths. +- [Review context construction](codex-rs/core/src/session/review.rs#L109) copies most of the parent context into another `TurnContext`. +- [`with_model`](codex-rs/core/src/session/turn_context.rs#L290) preserves nearly the entire context while replacing model-dependent fields. + +## Architectural conclusions + +- The genuinely turn-owned core is small: IDs and tracing, metadata, timing, extension state, skill dedupe, stream dedupe, final schema, and turn-resolved model/environment state. +- Most of the struct is a flattened thread-settings snapshot. +- `auth_manager` and probably `provider` are session services masquerading as turn state. +- Feature flags, shell policy, Linux sandbox executable, compatibility hash, truncation policy, and tool mode now come directly from `config` or `model_info` instead of duplicate `TurnContext` fields. +- `cwd` is duplicated legacy state and should disappear once consumers consistently use the selected turn environment. +- Mutable turn state such as metadata, timing, extension data, and stream dedupe would be clearer in a separate runtime-state object rather than mixed into an otherwise immutable snapshot. +- Background consumers should capture explicit narrow payloads rather than `Arc`. + +The most concerning lifetime leak is Guardian: the reusable trunk can retain and later consult the parent context from the turn that originally created it. That is not only memory retention; it creates a stale-authority boundary around cwd, approvals, permissions, and related settings. From 5c6bcbb3c57c97362d864764ea0ca209ba1e387b Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 16 Jun 2026 18:45:52 -0700 Subject: [PATCH 2/6] core: remove unused prompt personality --- codex-rs/core/src/client_common.rs | 5 ----- codex-rs/core/src/compact.rs | 1 - codex-rs/core/src/compact_remote.rs | 1 - codex-rs/core/src/compact_remote_v2.rs | 1 - codex-rs/core/src/session/turn.rs | 1 - 5 files changed, 9 deletions(-) diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs index 624501db462c..e0c2ff03c302 100644 --- a/codex-rs/core/src/client_common.rs +++ b/codex-rs/core/src/client_common.rs @@ -1,5 +1,4 @@ pub use codex_api::ResponseEvent; -use codex_config::types::Personality; use codex_protocol::error::Result; use codex_protocol::models::BaseInstructions; use codex_protocol::models::ContentItem; @@ -29,9 +28,6 @@ pub struct Prompt { pub base_instructions: BaseInstructions, - /// Optionally specify the personality of the model. - pub personality: Option, - /// Optional the output schema for the model's response. pub output_schema: Option, @@ -46,7 +42,6 @@ impl Default for Prompt { tools: Vec::new(), parallel_tool_calls: false, base_instructions: BaseInstructions::default(), - personality: None, output_schema: None, output_schema_strict: true, } diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index 3ff34c99a927..741c589a801c 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -234,7 +234,6 @@ async fn run_compact_task_inner_impl( let prompt = Prompt { input: turn_input, base_instructions: sess.get_base_instructions().await, - personality: turn_context.personality, ..Default::default() }; let attempt_result = drain_to_completed( diff --git a/codex-rs/core/src/compact_remote.rs b/codex-rs/core/src/compact_remote.rs index eca14af80e3b..08ca7efcdfe8 100644 --- a/codex-rs/core/src/compact_remote.rs +++ b/codex-rs/core/src/compact_remote.rs @@ -228,7 +228,6 @@ async fn run_remote_compact_task_inner_impl( tools: tool_router.model_visible_specs(), parallel_tool_calls: turn_context.model_info.supports_parallel_tool_calls, base_instructions, - personality: turn_context.personality, output_schema: None, output_schema_strict: true, }; diff --git a/codex-rs/core/src/compact_remote_v2.rs b/codex-rs/core/src/compact_remote_v2.rs index d13aa9406e38..4b4071a0d97f 100644 --- a/codex-rs/core/src/compact_remote_v2.rs +++ b/codex-rs/core/src/compact_remote_v2.rs @@ -237,7 +237,6 @@ async fn run_remote_compact_task_inner_impl( tools: tool_router.model_visible_specs(), parallel_tool_calls: turn_context.model_info.supports_parallel_tool_calls, base_instructions, - personality: turn_context.personality, output_schema: None, output_schema_strict: true, }; diff --git a/codex-rs/core/src/session/turn.rs b/codex-rs/core/src/session/turn.rs index 43ee23c9938c..7da157cf7bf4 100644 --- a/codex-rs/core/src/session/turn.rs +++ b/codex-rs/core/src/session/turn.rs @@ -1029,7 +1029,6 @@ pub(crate) fn build_prompt( tools: router.model_visible_specs(), parallel_tool_calls: turn_context.model_info.supports_parallel_tool_calls, base_instructions, - personality: turn_context.personality, output_schema: turn_context.final_output_json_schema.clone(), output_schema_strict: !crate::guardian::is_guardian_reviewer_source( &turn_context.session_source, From d575f089367db45e6ed46691f022f61d12d89f1b Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 16 Jun 2026 19:28:50 -0700 Subject: [PATCH 3/6] core: remove redundant compact prompt field --- codex-rs/core/src/compact.rs | 7 ++++++- codex-rs/core/src/session/review.rs | 2 +- codex-rs/core/src/session/turn_context.rs | 9 --------- codex-rs/core/src/tasks/compact.rs | 7 ++++++- codex-rs/core/src/tools/handlers/multi_agents_common.rs | 1 - codex-rs/core/src/tools/handlers/multi_agents_tests.rs | 4 +--- turn-context-field-lifetimes.md | 3 +-- 7 files changed, 15 insertions(+), 18 deletions(-) diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index 741c589a801c..9d79138e87ce 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -77,7 +77,12 @@ pub(crate) async fn run_inline_auto_compact_task( reason: CompactionReason, phase: CompactionPhase, ) -> CodexResult<()> { - let prompt = turn_context.compact_prompt().to_string(); + let prompt = turn_context + .config + .compact_prompt + .as_deref() + .unwrap_or(SUMMARIZATION_PROMPT) + .to_string(); let input = vec![UserInput::Text { text: prompt, // Compaction prompt is synthesized; no UI element ranges to preserve. diff --git a/codex-rs/core/src/session/review.rs b/codex-rs/core/src/session/review.rs index 0e60a92f5b57..0296d9e44aa1 100644 --- a/codex-rs/core/src/session/review.rs +++ b/codex-rs/core/src/session/review.rs @@ -53,6 +53,7 @@ pub(super) async fn spawn_review_thread( .clone(); per_turn_config.codex_linux_sandbox_exe = parent_turn_context.config.codex_linux_sandbox_exe.clone(); + per_turn_config.compact_prompt = parent_turn_context.config.compact_prompt.clone(); if let Err(err) = per_turn_config.web_search_mode.set(review_web_search_mode) { let fallback_value = per_turn_config.web_search_mode.value(); tracing::warn!( @@ -124,7 +125,6 @@ pub(super) async fn spawn_review_thread( app_server_client_name: parent_turn_context.app_server_client_name.clone(), developer_instructions: None, user_instructions: None, - compact_prompt: parent_turn_context.compact_prompt.clone(), collaboration_mode: parent_turn_context.collaboration_mode.clone(), multi_agent_version: MultiAgentVersion::Disabled, personality: parent_turn_context.personality, diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index 78c2da768a45..eade9f2d73df 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -124,7 +124,6 @@ pub struct TurnContext { pub(crate) timezone: Option, pub(crate) app_server_client_name: Option, pub(crate) developer_instructions: Option, - pub(crate) compact_prompt: Option, pub(crate) user_instructions: Option, pub(crate) collaboration_mode: CollaborationMode, pub(crate) multi_agent_version: MultiAgentVersion, @@ -276,7 +275,6 @@ impl TurnContext { timezone: self.timezone.clone(), app_server_client_name: self.app_server_client_name.clone(), developer_instructions: self.developer_instructions.clone(), - compact_prompt: self.compact_prompt.clone(), user_instructions: self.user_instructions.clone(), collaboration_mode, multi_agent_version: self.multi_agent_version, @@ -358,12 +356,6 @@ impl TurnContext { .then_some(file_system_sandbox_policy) } - pub(crate) fn compact_prompt(&self) -> &str { - self.compact_prompt - .as_deref() - .unwrap_or(compact::SUMMARIZATION_PROMPT) - } - pub(crate) fn to_turn_context_item(&self) -> TurnContextItem { let workspace_roots = self.config.effective_workspace_roots(); #[allow(deprecated)] @@ -560,7 +552,6 @@ impl Session { timezone: Some(timezone), app_server_client_name: session_configuration.app_server_client_name.clone(), developer_instructions: session_configuration.developer_instructions.clone(), - compact_prompt: session_configuration.compact_prompt.clone(), user_instructions: session_configuration .loaded_agents_md .as_ref() diff --git a/codex-rs/core/src/tasks/compact.rs b/codex-rs/core/src/tasks/compact.rs index 1b8127e67713..dd9d6cb079e7 100644 --- a/codex-rs/core/src/tasks/compact.rs +++ b/codex-rs/core/src/tasks/compact.rs @@ -56,7 +56,12 @@ impl SessionTask for CompactTask { /*manual*/ true, ); let input = vec![UserInput::Text { - text: ctx.compact_prompt().to_string(), + text: ctx + .config + .compact_prompt + .as_deref() + .unwrap_or(crate::compact::SUMMARIZATION_PROMPT) + .to_string(), // Compaction prompt is synthesized; no UI element ranges to preserve. text_elements: Vec::new(), }]; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_common.rs b/codex-rs/core/src/tools/handlers/multi_agents_common.rs index 6ce21c8f2aa0..8f91ce5b8ee2 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_common.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_common.rs @@ -228,7 +228,6 @@ fn build_agent_shared_config(turn: &TurnContext) -> Result`. From 0cc8263fbd09ee7a64bca543ee2afc07f564df9d Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 16 Jun 2026 19:33:49 -0700 Subject: [PATCH 4/6] codex: fix CI failure on PR #28638 --- turn-context-field-lifetimes.md | 76 ++++++++++++++++----------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/turn-context-field-lifetimes.md b/turn-context-field-lifetimes.md index 9b902d0cdf14..f2ca3c069ab8 100644 --- a/turn-context-field-lifetimes.md +++ b/turn-context-field-lifetimes.md @@ -26,44 +26,44 @@ Relevant ownership points: ## Field inventory -| Field | Real semantic lifetime and origin | Captured and used later | -|---|---|---| -| `sub_id` | Genuine turn/task identifier | Durable; copied into metadata, MCP manager, events, approvals, and child/review machinery | -| `trace_id` | Snapshot of the tracing span when the context is created | Copied into regular, compact, remote-compact, and user-shell task metadata; not durable | -| `realtime_active` | Runtime snapshot taken when the context is built; not live conversation state | Durable and Next turn; copied to review context | -| `config` | Newly allocated per-turn `Config`, mostly copied from thread configuration but rewritten with selected cwd, workspace roots, permission state, reasoning, service tier, and related values | Parts are Durable; full config is cloned into child agents, review, and alternate-model compaction | -| `auth_manager` | Live session-wide shared service handle, not a snapshot | Used later by `apps_enabled()` and copied into review contexts; otherwise redundant with session/provider access | -| `model_info` | Per-turn lookup of model catalog metadata for the selected thread model | Model slug is Durable and Next turn; full object drives sampling/tools and is copied or replaced in child/review contexts | -| `session_telemetry` | Session telemetry handle cloned and rebound to this turn's model and slug | Used through turn finish and copied/rebound for review and alternate-model contexts | -| `provider` | Recreated per turn from thread provider configuration plus live auth | Used for sampling and compaction; provider info is copied into child config and review | -| `reasoning_effort` | Snapshot of thread collaboration settings, with model compatibility adjustment in `with_model` | Durable; copied into model requests, child config, review, and compaction | -| `reasoning_summary` | Thread setting or model-default-derived turn value | Used for model, compact, and child requests; notably not persisted from this field—the rollout compatibility field is hardcoded to `Auto` | -| `session_source` | Immutable thread identity copied into every turn | Copied into metadata and review; child threads derive a new `SessionSource::SubAgent` | -| `parent_thread_id` | Immutable thread relationship copied into every turn | Copied into request metadata, review, and agent-tree handling | -| `environments` | Immutable turn snapshot of thread environment selections | Used throughout tools; selections and cwd escape into MCP and child sessions; review copies it. Underlying `Environment` Arcs and shell-snapshot futures are shared across turns | -| `cwd` | Turn-derived legacy projection: primary selected-environment cwd, otherwise thread fallback cwd | Durable, MCP, metadata, child, and review capture. This is explicitly deprecated and is the largest compatibility duplicate | -| `current_date` | Genuine creation-time turn snapshot | Durable; used in model environment context and copied to review | -| `timezone` | Genuine creation-time turn snapshot | Durable; used in model environment context and copied to review | -| `app_server_client_name` | Thread/session setting snapshot | Used later for hook payloads and plugin-install/tool exposure behavior; copied to review | -| `developer_instructions` | Thread configuration snapshot | Materialized into model history; copied into child config and `with_model`; deliberately cleared for review | -| `user_instructions` | Rendered snapshot of thread-loaded `AGENTS.md` instructions | Materialized into model history; preserved by `with_model`, cleared for review. Delegated children reload from the parent session rather than this field | -| `collaboration_mode` | Mutable thread setting frozen for this turn | Durable; used throughout the task and copied to review and alternate-model contexts | -| `multi_agent_version` | Session-latched value selected once and then copied into turns | Durable; used for tools and lifecycle. Review forces it to `Disabled` | -| `personality` | Mutable thread setting frozen for this turn | Durable; copied into prompts, compaction, and review | -| `approval_policy` | Mutable thread setting frozen for this turn | Durable; also copied into MCP, child config, review, and approval paths | -| `permission_profile` | Materialized per-turn permission snapshot derived from thread state and workspace roots | Durable; copied into MCP, metadata sandbox tag, child config, review, and execution paths | -| `network` | Turn-selected handle to the live session proxy. The choice of `Some` or `None` is turn-specific, but internals remain shared and live | Used by shell and unified exec and copied to review. Only `is_some()` enters metadata. `TurnContextItem.network` comes from config requirements, not this field | -| `windows_sandbox_level` | Mutable thread setting frozen for this turn | Used by execution and metadata sandbox tagging; copied to review. Child config inherits it indirectly through cloned `config` | -| `available_models` | Snapshot of the models-manager cache at turn creation | Used later only to build multi-agent/model-selection tool specs; review obtains a new list | -| `unified_exec_shell_mode` | Derived from session features, user shell, and runtime paths | Used later by unified-exec tool specs and runtime; recalculated for review | -| `final_output_json_schema` | Genuine request/turn-specific value | Copied into each model `Prompt`; not durable; review clears it | -| `dynamic_tools` | Thread/session configuration copied into every turn | Used by tool-router construction; copied into review | -| `turn_metadata_state` | Genuine mutable turn state | Shared by `with_model`; read for every model and MCP request. Its Git enrichment task clones this state independently and can briefly outlive `TurnContext` | -| `extension_data` | Genuine mutable turn extension store | Explicitly cloned into `RunningTask` and `SessionTaskContext`; shared by alternate-model contexts | -| `turn_skills` | Per-turn resolved skills outcome plus a genuinely turn-local implicit-invocation dedupe set | Outcome is also inserted into extension data. `with_model` shares both; review shares the outcome but creates a fresh dedupe set | -| `turn_timing_state` | Genuine mutable turn/task timing state | Sampling and tool guards retain it independently; read through completion or abort; shared by `with_model` | -| `server_model_warning_emitted` | Per-context response-stream dedupe flag | Used during streaming. `with_model` makes a distinct atomic initialized from the old value; review resets it | -| `model_verification_emitted` | Per-context response-stream dedupe flag | Streaming-only; copied by value into `with_model` and reset for review | +| Field | Real semantic lifetime and origin | Captured and used later | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `sub_id` | Genuine turn/task identifier | Durable; copied into metadata, MCP manager, events, approvals, and child/review machinery | +| `trace_id` | Snapshot of the tracing span when the context is created | Copied into regular, compact, remote-compact, and user-shell task metadata; not durable | +| `realtime_active` | Runtime snapshot taken when the context is built; not live conversation state | Durable and Next turn; copied to review context | +| `config` | Newly allocated per-turn `Config`, mostly copied from thread configuration but rewritten with selected cwd, workspace roots, permission state, reasoning, service tier, and related values | Parts are Durable; full config is cloned into child agents, review, and alternate-model compaction | +| `auth_manager` | Live session-wide shared service handle, not a snapshot | Used later by `apps_enabled()` and copied into review contexts; otherwise redundant with session/provider access | +| `model_info` | Per-turn lookup of model catalog metadata for the selected thread model | Model slug is Durable and Next turn; full object drives sampling/tools and is copied or replaced in child/review contexts | +| `session_telemetry` | Session telemetry handle cloned and rebound to this turn's model and slug | Used through turn finish and copied/rebound for review and alternate-model contexts | +| `provider` | Recreated per turn from thread provider configuration plus live auth | Used for sampling and compaction; provider info is copied into child config and review | +| `reasoning_effort` | Snapshot of thread collaboration settings, with model compatibility adjustment in `with_model` | Durable; copied into model requests, child config, review, and compaction | +| `reasoning_summary` | Thread setting or model-default-derived turn value | Used for model, compact, and child requests; notably not persisted from this field—the rollout compatibility field is hardcoded to `Auto` | +| `session_source` | Immutable thread identity copied into every turn | Copied into metadata and review; child threads derive a new `SessionSource::SubAgent` | +| `parent_thread_id` | Immutable thread relationship copied into every turn | Copied into request metadata, review, and agent-tree handling | +| `environments` | Immutable turn snapshot of thread environment selections | Used throughout tools; selections and cwd escape into MCP and child sessions; review copies it. Underlying `Environment` Arcs and shell-snapshot futures are shared across turns | +| `cwd` | Turn-derived legacy projection: primary selected-environment cwd, otherwise thread fallback cwd | Durable, MCP, metadata, child, and review capture. This is explicitly deprecated and is the largest compatibility duplicate | +| `current_date` | Genuine creation-time turn snapshot | Durable; used in model environment context and copied to review | +| `timezone` | Genuine creation-time turn snapshot | Durable; used in model environment context and copied to review | +| `app_server_client_name` | Thread/session setting snapshot | Used later for hook payloads and plugin-install/tool exposure behavior; copied to review | +| `developer_instructions` | Thread configuration snapshot | Materialized into model history; copied into child config and `with_model`; deliberately cleared for review | +| `user_instructions` | Rendered snapshot of thread-loaded `AGENTS.md` instructions | Materialized into model history; preserved by `with_model`, cleared for review. Delegated children reload from the parent session rather than this field | +| `collaboration_mode` | Mutable thread setting frozen for this turn | Durable; used throughout the task and copied to review and alternate-model contexts | +| `multi_agent_version` | Session-latched value selected once and then copied into turns | Durable; used for tools and lifecycle. Review forces it to `Disabled` | +| `personality` | Mutable thread setting frozen for this turn | Durable; copied into prompts, compaction, and review | +| `approval_policy` | Mutable thread setting frozen for this turn | Durable; also copied into MCP, child config, review, and approval paths | +| `permission_profile` | Materialized per-turn permission snapshot derived from thread state and workspace roots | Durable; copied into MCP, metadata sandbox tag, child config, review, and execution paths | +| `network` | Turn-selected handle to the live session proxy. The choice of `Some` or `None` is turn-specific, but internals remain shared and live | Used by shell and unified exec and copied to review. Only `is_some()` enters metadata. `TurnContextItem.network` comes from config requirements, not this field | +| `windows_sandbox_level` | Mutable thread setting frozen for this turn | Used by execution and metadata sandbox tagging; copied to review. Child config inherits it indirectly through cloned `config` | +| `available_models` | Snapshot of the models-manager cache at turn creation | Used later only to build multi-agent/model-selection tool specs; review obtains a new list | +| `unified_exec_shell_mode` | Derived from session features, user shell, and runtime paths | Used later by unified-exec tool specs and runtime; recalculated for review | +| `final_output_json_schema` | Genuine request/turn-specific value | Copied into each model `Prompt`; not durable; review clears it | +| `dynamic_tools` | Thread/session configuration copied into every turn | Used by tool-router construction; copied into review | +| `turn_metadata_state` | Genuine mutable turn state | Shared by `with_model`; read for every model and MCP request. Its Git enrichment task clones this state independently and can briefly outlive `TurnContext` | +| `extension_data` | Genuine mutable turn extension store | Explicitly cloned into `RunningTask` and `SessionTaskContext`; shared by alternate-model contexts | +| `turn_skills` | Per-turn resolved skills outcome plus a genuinely turn-local implicit-invocation dedupe set | Outcome is also inserted into extension data. `with_model` shares both; review shares the outcome but creates a fresh dedupe set | +| `turn_timing_state` | Genuine mutable turn/task timing state | Sampling and tool guards retain it independently; read through completion or abort; shared by `with_model` | +| `server_model_warning_emitted` | Per-context response-stream dedupe flag | Used during streaming. `with_model` makes a distinct atomic initialized from the old value; review resets it | +| `model_verification_emitted` | Per-context response-stream dedupe flag | Streaming-only; copied by value into `with_model` and reset for review | ## Independent longer-lived projections From 2cd09090befaa157d692fb41e2bc5965de72f3c1 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 16 Jun 2026 19:39:46 -0700 Subject: [PATCH 5/6] core: remove TurnContext field inventory --- turn-context-field-lifetimes.md | 88 --------------------------------- 1 file changed, 88 deletions(-) delete mode 100644 turn-context-field-lifetimes.md diff --git a/turn-context-field-lifetimes.md b/turn-context-field-lifetimes.md deleted file mode 100644 index f2ca3c069ab8..000000000000 --- a/turn-context-field-lifetimes.md +++ /dev/null @@ -1,88 +0,0 @@ -# `TurnContext` field lifetimes - -`TurnContext` is not actually turn-scoped as a whole. It contains four different kinds of state: - -- snapshots of mutable thread settings; -- session-wide live handles; -- values resolved specifically for one turn; -- genuinely mutable turn state. - -Physically, every field lives as long as the longest `Arc`. Normally that is turn completion, but unified-exec watchers retain it until the process exits, and the reusable Guardian trunk can retain the originating context for effectively the rest of the session. - -Relevant ownership points: - -- [`RunningTask` retains the context](codex-rs/core/src/state/turn.rs#L72). -- [The spawned task captures another `Arc`](codex-rs/core/src/tasks/mod.rs#L374). -- [Unified-exec exit watchers retain the context](codex-rs/core/src/unified_exec/async_watcher.rs#L107). -- [Guardian subagent forwarding captures the parent context](codex-rs/core/src/codex_delegate.rs#L142). - -## Terminology - -- **Durable**: copied into `TurnContextItem`, persisted to the rollout, and retained as the in-memory reference context for later turns. -- **Next turn**: copied into `PreviousTurnSettings` for decisions made during a subsequent turn. -- **MCP**: copied into the session-held MCP manager or its runtime context. -- **Child/review**: copied into a child-agent configuration, review context, or alternate-model context. -- **Live use**: read later through a retained `Arc`, without a separate longer-lived projection. - -## Field inventory - -| Field | Real semantic lifetime and origin | Captured and used later | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `sub_id` | Genuine turn/task identifier | Durable; copied into metadata, MCP manager, events, approvals, and child/review machinery | -| `trace_id` | Snapshot of the tracing span when the context is created | Copied into regular, compact, remote-compact, and user-shell task metadata; not durable | -| `realtime_active` | Runtime snapshot taken when the context is built; not live conversation state | Durable and Next turn; copied to review context | -| `config` | Newly allocated per-turn `Config`, mostly copied from thread configuration but rewritten with selected cwd, workspace roots, permission state, reasoning, service tier, and related values | Parts are Durable; full config is cloned into child agents, review, and alternate-model compaction | -| `auth_manager` | Live session-wide shared service handle, not a snapshot | Used later by `apps_enabled()` and copied into review contexts; otherwise redundant with session/provider access | -| `model_info` | Per-turn lookup of model catalog metadata for the selected thread model | Model slug is Durable and Next turn; full object drives sampling/tools and is copied or replaced in child/review contexts | -| `session_telemetry` | Session telemetry handle cloned and rebound to this turn's model and slug | Used through turn finish and copied/rebound for review and alternate-model contexts | -| `provider` | Recreated per turn from thread provider configuration plus live auth | Used for sampling and compaction; provider info is copied into child config and review | -| `reasoning_effort` | Snapshot of thread collaboration settings, with model compatibility adjustment in `with_model` | Durable; copied into model requests, child config, review, and compaction | -| `reasoning_summary` | Thread setting or model-default-derived turn value | Used for model, compact, and child requests; notably not persisted from this field—the rollout compatibility field is hardcoded to `Auto` | -| `session_source` | Immutable thread identity copied into every turn | Copied into metadata and review; child threads derive a new `SessionSource::SubAgent` | -| `parent_thread_id` | Immutable thread relationship copied into every turn | Copied into request metadata, review, and agent-tree handling | -| `environments` | Immutable turn snapshot of thread environment selections | Used throughout tools; selections and cwd escape into MCP and child sessions; review copies it. Underlying `Environment` Arcs and shell-snapshot futures are shared across turns | -| `cwd` | Turn-derived legacy projection: primary selected-environment cwd, otherwise thread fallback cwd | Durable, MCP, metadata, child, and review capture. This is explicitly deprecated and is the largest compatibility duplicate | -| `current_date` | Genuine creation-time turn snapshot | Durable; used in model environment context and copied to review | -| `timezone` | Genuine creation-time turn snapshot | Durable; used in model environment context and copied to review | -| `app_server_client_name` | Thread/session setting snapshot | Used later for hook payloads and plugin-install/tool exposure behavior; copied to review | -| `developer_instructions` | Thread configuration snapshot | Materialized into model history; copied into child config and `with_model`; deliberately cleared for review | -| `user_instructions` | Rendered snapshot of thread-loaded `AGENTS.md` instructions | Materialized into model history; preserved by `with_model`, cleared for review. Delegated children reload from the parent session rather than this field | -| `collaboration_mode` | Mutable thread setting frozen for this turn | Durable; used throughout the task and copied to review and alternate-model contexts | -| `multi_agent_version` | Session-latched value selected once and then copied into turns | Durable; used for tools and lifecycle. Review forces it to `Disabled` | -| `personality` | Mutable thread setting frozen for this turn | Durable; copied into prompts, compaction, and review | -| `approval_policy` | Mutable thread setting frozen for this turn | Durable; also copied into MCP, child config, review, and approval paths | -| `permission_profile` | Materialized per-turn permission snapshot derived from thread state and workspace roots | Durable; copied into MCP, metadata sandbox tag, child config, review, and execution paths | -| `network` | Turn-selected handle to the live session proxy. The choice of `Some` or `None` is turn-specific, but internals remain shared and live | Used by shell and unified exec and copied to review. Only `is_some()` enters metadata. `TurnContextItem.network` comes from config requirements, not this field | -| `windows_sandbox_level` | Mutable thread setting frozen for this turn | Used by execution and metadata sandbox tagging; copied to review. Child config inherits it indirectly through cloned `config` | -| `available_models` | Snapshot of the models-manager cache at turn creation | Used later only to build multi-agent/model-selection tool specs; review obtains a new list | -| `unified_exec_shell_mode` | Derived from session features, user shell, and runtime paths | Used later by unified-exec tool specs and runtime; recalculated for review | -| `final_output_json_schema` | Genuine request/turn-specific value | Copied into each model `Prompt`; not durable; review clears it | -| `dynamic_tools` | Thread/session configuration copied into every turn | Used by tool-router construction; copied into review | -| `turn_metadata_state` | Genuine mutable turn state | Shared by `with_model`; read for every model and MCP request. Its Git enrichment task clones this state independently and can briefly outlive `TurnContext` | -| `extension_data` | Genuine mutable turn extension store | Explicitly cloned into `RunningTask` and `SessionTaskContext`; shared by alternate-model contexts | -| `turn_skills` | Per-turn resolved skills outcome plus a genuinely turn-local implicit-invocation dedupe set | Outcome is also inserted into extension data. `with_model` shares both; review shares the outcome but creates a fresh dedupe set | -| `turn_timing_state` | Genuine mutable turn/task timing state | Sampling and tool guards retain it independently; read through completion or abort; shared by `with_model` | -| `server_model_warning_emitted` | Per-context response-stream dedupe flag | Used during streaming. `with_model` makes a distinct atomic initialized from the old value; review resets it | -| `model_verification_emitted` | Per-context response-stream dedupe flag | Streaming-only; copied by value into `with_model` and reset for review | - -## Independent longer-lived projections - -- [`TurnContextItem`](codex-rs/core/src/session/turn_context.rs#L409) is the durable rollout and reference-context projection. -- [`PreviousTurnSettings`](codex-rs/core/src/session/turn.rs#L175) retains model, compatibility hash, and realtime state for the next turn. -- [MCP refresh](codex-rs/core/src/session/mcp.rs#L320) captures selected cwd/environment, approval policy, turn ID, and permission profile into a session-held manager. -- [`TurnMetadataState`](codex-rs/core/src/turn_metadata.rs#L85) captures identity, cwd, policy, and metadata into independently mutable turn state. -- [Child-agent config construction](codex-rs/core/src/tools/handlers/multi_agents_common.rs#L220) copies model, provider, reasoning, instructions, approval, shell, cwd, permissions, and sandbox paths. -- [Review context construction](codex-rs/core/src/session/review.rs#L109) copies most of the parent context into another `TurnContext`. -- [`with_model`](codex-rs/core/src/session/turn_context.rs#L290) preserves nearly the entire context while replacing model-dependent fields. - -## Architectural conclusions - -- The genuinely turn-owned core is small: IDs and tracing, metadata, timing, extension state, skill dedupe, stream dedupe, final schema, and turn-resolved model/environment state. -- Most of the struct is a flattened thread-settings snapshot. -- `auth_manager` and probably `provider` are session services masquerading as turn state. -- Feature flags, shell policy, Linux sandbox executable, compact prompt, compatibility hash, truncation policy, and tool mode now come directly from `config` or `model_info` instead of duplicate `TurnContext` fields. -- `cwd` is duplicated legacy state and should disappear once consumers consistently use the selected turn environment. -- Mutable turn state such as metadata, timing, extension data, and stream dedupe would be clearer in a separate runtime-state object rather than mixed into an otherwise immutable snapshot. -- Background consumers should capture explicit narrow payloads rather than `Arc`. - -The most concerning lifetime leak is Guardian: the reusable trunk can retain and later consult the parent context from the turn that originally created it. That is not only memory retention; it creates a stale-authority boundary around cwd, approvals, permissions, and related settings. From d1f3d5a0e1f4c0a596e33776ea81d32916843600 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 16 Jun 2026 19:40:55 -0700 Subject: [PATCH 6/6] codex: fix CI failure on PR #28638 --- codex-rs/core/src/session/turn_context.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index eade9f2d73df..b76c9384f7db 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -536,9 +536,9 @@ impl Session { sub_id, trace_id: current_span_trace_id(), realtime_active: false, - config: per_turn_config.clone(), + config: per_turn_config, auth_manager: auth_manager_for_context, - model_info: model_info.clone(), + model_info, session_telemetry: session_telemetry_for_context, provider: provider_for_context, reasoning_effort,