From 38f6147d475634925b2a829107114010715d1f8d Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Sat, 30 May 2026 22:05:21 -0700 Subject: [PATCH] codex: keep config lock independent of runtime lock --- codex-rs/core/src/session/config_lock.rs | 79 +++--------------------- codex-rs/core/src/session/session.rs | 13 +--- 2 files changed, 9 insertions(+), 83 deletions(-) diff --git a/codex-rs/core/src/session/config_lock.rs b/codex-rs/core/src/session/config_lock.rs index f5aadd8e07..85815f8533 100644 --- a/codex-rs/core/src/session/config_lock.rs +++ b/codex-rs/core/src/session/config_lock.rs @@ -8,7 +8,6 @@ use codex_features::FeatureToml; use codex_features::FeaturesToml; use codex_features::MultiAgentV2ConfigToml; use codex_protocol::ThreadId; -use codex_protocol::protocol::MultiAgentVersion; use crate::config::Config; use crate::config_lock::ConfigLockReplayOptions; @@ -21,7 +20,6 @@ use super::SessionConfiguration; pub(crate) async fn validate_config_lock_if_configured( session_configuration: &SessionConfiguration, - multi_agent_version: Option, ) -> anyhow::Result<()> { if session_configuration.session_source.is_non_root_agent() { return Ok(()); @@ -33,8 +31,7 @@ pub(crate) async fn validate_config_lock_if_configured( else { return Ok(()); }; - let actual = session_configuration - .to_config_lockfile_toml_with_multi_agent_version(multi_agent_version)?; + let actual = session_configuration.to_config_lockfile_toml()?; let config = session_configuration.original_config_do_not_use.as_ref(); let options = ConfigLockReplayOptions { allow_codex_version_mismatch: config.config_lock_allow_codex_version_mismatch, @@ -47,15 +44,13 @@ pub(crate) async fn validate_config_lock_if_configured( pub(crate) async fn export_config_lock_if_configured( session_configuration: &SessionConfiguration, conversation_id: ThreadId, - multi_agent_version: Option, ) -> anyhow::Result<()> { let config = session_configuration.original_config_do_not_use.as_ref(); let Some(export_dir) = config.config_lock_export_dir.as_ref() else { return Ok(()); }; - let lock = session_configuration - .to_config_lockfile_toml_with_multi_agent_version(multi_agent_version)?; + let lock = session_configuration.to_config_lockfile_toml()?; let lock = toml::to_string_pretty(&lock).context("failed to serialize config lock")?; let path = export_dir.join(format!("{conversation_id}.config.lock.toml")); @@ -75,28 +70,15 @@ pub(crate) async fn export_config_lock_if_configured( } impl SessionConfiguration { - #[cfg(test)] pub(crate) fn to_config_lockfile_toml(&self) -> anyhow::Result { - self.to_config_lockfile_toml_with_multi_agent_version( - self.original_config_do_not_use - .multi_agent_version_from_features(), - ) - } - - fn to_config_lockfile_toml_with_multi_agent_version( - &self, - multi_agent_version: Option, - ) -> anyhow::Result { Ok(config_lockfile(session_configuration_to_lock_config_toml( self, - multi_agent_version, )?)) } } fn session_configuration_to_lock_config_toml( sc: &SessionConfiguration, - multi_agent_version: Option, ) -> anyhow::Result { let config = sc.original_config_do_not_use.as_ref(); // Start from the resolved layer stack, then patch in values that are only @@ -112,7 +94,7 @@ fn session_configuration_to_lock_config_toml( save_session_resolved_fields(sc, &mut lock_config); } - save_config_resolved_fields(config, multi_agent_version, &mut lock_config)?; + save_config_resolved_fields(config, &mut lock_config)?; drop_lockfile_inputs(&mut lock_config); Ok(lock_config) @@ -143,7 +125,6 @@ fn save_session_resolved_fields(sc: &SessionConfiguration, lock_config: &mut Con /// Codex actually ran with, not only the user-authored TOML inputs. fn save_config_resolved_fields( config: &Config, - multi_agent_version: Option, lock_config: &mut ConfigToml, ) -> anyhow::Result<()> { lock_config.web_search = Some(config.web_search_mode.value()); @@ -163,16 +144,10 @@ fn save_config_resolved_fields( let features = lock_config .features .get_or_insert_with(FeaturesToml::default); - let mut materialized_features = config.features.get().clone(); - materialized_features.set_enabled(Feature::Collab, multi_agent_version.is_some()); - materialized_features.set_enabled( - Feature::MultiAgentV2, - multi_agent_version == Some(MultiAgentVersion::V2), - ); - features.materialize_resolved_enabled(&materialized_features); + features.materialize_resolved_enabled(config.features.get()); let mut multi_agent_v2: MultiAgentV2ConfigToml = resolved_config_to_toml(&config.multi_agent_v2, "features.multi_agent_v2")?; - multi_agent_v2.enabled = Some(multi_agent_version == Some(MultiAgentVersion::V2)); + multi_agent_v2.enabled = Some(config.features.enabled(Feature::MultiAgentV2)); features.multi_agent_v2 = Some(FeatureToml::Config(multi_agent_v2)); features.apps_mcp_path_override = Some(FeatureToml::Config(AppsMcpPathOverrideConfigToml { enabled: Some(config.features.enabled(Feature::AppsMcpPathOverride)), @@ -186,10 +161,10 @@ fn save_config_resolved_fields( let agents = lock_config.agents.get_or_insert_with(Default::default); // Multi-agent v2 owns thread fanout through its feature config. Preserve // the legacy agents.max_threads setting only when v2 is disabled. - agents.max_threads = if multi_agent_version == Some(MultiAgentVersion::V2) { + agents.max_threads = if config.features.enabled(Feature::MultiAgentV2) { None } else { - config.effective_agent_max_threads(multi_agent_version)? + config.agent_max_threads }; agents.max_depth = Some(config.agent_max_depth); agents.job_max_runtime_seconds = config.agent_job_max_runtime_seconds; @@ -298,46 +273,6 @@ mod tests { assert_eq!(lockfile.version, crate::config_lock::CONFIG_LOCK_VERSION); } - #[tokio::test] - async fn lock_projects_multi_agent_version_over_current_feature_flags() { - let mut sc = crate::session::tests::make_session_configuration_for_tests().await; - let mut config = (*sc.original_config_do_not_use).clone(); - config - .features - .enable(Feature::MultiAgentV2) - .expect("test config should allow feature update"); - sc.original_config_do_not_use = Arc::new(config); - - for (multi_agent_version, collab, multi_agent_v2) in [ - (None, false, false), - (Some(MultiAgentVersion::V1), true, false), - (Some(MultiAgentVersion::V2), true, true), - ] { - let lockfile = sc - .to_config_lockfile_toml_with_multi_agent_version(multi_agent_version) - .expect("lock should serialize"); - let lock = &lockfile.config; - let entries = lock - .features - .as_ref() - .expect("lock should materialize features") - .entries(); - assert_eq!(entries.get(Feature::Collab.key()), Some(&collab)); - assert_eq!( - entries.get(Feature::MultiAgentV2.key()), - Some(&multi_agent_v2) - ); - assert_eq!( - lock.agents - .as_ref() - .expect("lock should materialize agent config") - .max_threads - .is_none(), - multi_agent_version == Some(MultiAgentVersion::V2) - ); - } - } - #[tokio::test] async fn lock_skips_session_values_when_model_catalog_fields_are_not_saved() { let mut sc = crate::session::tests::make_session_configuration_for_tests().await; diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index ddd35c8b96..937b5a08d7 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -888,17 +888,8 @@ impl Session { )) .await; session_configuration.thread_name = thread_name.clone(); - validate_config_lock_if_configured( - &session_configuration, - initial_multi_agent_version, - ) - .await?; - export_config_lock_if_configured( - &session_configuration, - thread_id, - initial_multi_agent_version, - ) - .await?; + validate_config_lock_if_configured(&session_configuration).await?; + export_config_lock_if_configured(&session_configuration, thread_id).await?; let state = SessionState::new(session_configuration.clone()); let managed_network_requirements_configured = config .config_layer_stack