Resolve per-thread multi-agent runtime (#25722)

Stack split from #25708. Original PR intentionally left open. This third
PR resolves the effective per-thread multi-agent runtime from persisted
metadata, inherited runtime, and current model selection.
This commit is contained in:
jif-oai
2026-06-02 14:31:00 +02:00
committed by GitHub
parent 0c5ccd18ab
commit bf9fd885b2
24 changed files with 599 additions and 206 deletions
+27 -6
View File
@@ -86,6 +86,7 @@ use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::MultiAgentVersion;
use codex_protocol::protocol::NetworkAccess;
use codex_protocol::protocol::RealtimeVoice;
use codex_protocol::protocol::SandboxPolicy;
@@ -9801,7 +9802,13 @@ non_code_mode_only = true
assert_eq!(config.multi_agent_v2.min_wait_timeout_ms, 2500);
assert_eq!(config.multi_agent_v2.max_wait_timeout_ms, 120000);
assert_eq!(config.multi_agent_v2.default_wait_timeout_ms, 30000);
assert_eq!(config.agent_max_threads, Some(4));
assert_eq!(
(
config.agent_max_threads,
config.effective_agent_max_threads(MultiAgentVersion::V2)?
),
(None, Some(4))
);
assert!(!config.multi_agent_v2.usage_hint_enabled);
assert_eq!(
config.multi_agent_v2.usage_hint_text.as_deref(),
@@ -9845,7 +9852,13 @@ enabled = true
assert_eq!(config.multi_agent_v2.min_wait_timeout_ms, 10_000);
assert_eq!(config.multi_agent_v2.max_wait_timeout_ms, 3_600_000);
assert_eq!(config.multi_agent_v2.default_wait_timeout_ms, 30_000);
assert_eq!(config.agent_max_threads, Some(3));
assert_eq!(
(
config.agent_max_threads,
config.effective_agent_max_threads(MultiAgentVersion::V2)?
),
(None, Some(3))
);
assert_eq!(
config.multi_agent_v2.root_agent_usage_hint_text.as_deref(),
Some(DEFAULT_MULTI_AGENT_V2_ROOT_AGENT_USAGE_HINT_TEXT)
@@ -9912,17 +9925,19 @@ max_threads = 3
"#,
)?;
let err = ConfigBuilder::without_managed_config_for_tests()
let config = ConfigBuilder::without_managed_config_for_tests()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.build()
.await
.await?;
let err = config
.effective_agent_max_threads(MultiAgentVersion::V2)
.expect_err("agents.max_threads should conflict with multi_agent_v2");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!(
err.to_string(),
"agents.max_threads cannot be set when multi_agent_v2 is enabled"
"agents.max_threads cannot be set when the multi-agent runtime is v2"
);
Ok(())
@@ -10181,7 +10196,13 @@ max_concurrent_threads_per_session = 1
.await?;
assert_eq!(config.multi_agent_v2.max_concurrent_threads_per_session, 1);
assert_eq!(config.agent_max_threads, Some(0));
assert_eq!(
(
config.agent_max_threads,
config.effective_agent_max_threads(MultiAgentVersion::V2)?
),
(None, Some(0))
);
Ok(())
}
+43 -24
View File
@@ -99,6 +99,7 @@ use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::MultiAgentVersion;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::AbsolutePathBufGuard;
@@ -823,7 +824,7 @@ pub struct Config {
/// Token budget applied when storing tool/function outputs in the context manager.
pub tool_output_token_limit: Option<usize>,
/// Maximum number of agent threads that can be open concurrently.
/// User-configured maximum number of agent threads that can be open concurrently.
pub agent_max_threads: Option<usize>,
/// Maximum runtime in seconds for agent job workers before they are failed.
pub agent_job_max_runtime_seconds: Option<u64>,
@@ -1281,6 +1282,40 @@ impl ConfigBuilder {
}
impl Config {
pub(crate) fn multi_agent_version_from_features(&self) -> MultiAgentVersion {
if self.features.enabled(Feature::MultiAgentV2) {
MultiAgentVersion::V2
} else if self.features.enabled(Feature::Collab) {
MultiAgentVersion::V1
} else {
MultiAgentVersion::Disabled
}
}
pub(crate) fn effective_agent_max_threads(
&self,
multi_agent_version: MultiAgentVersion,
) -> std::io::Result<Option<usize>> {
match multi_agent_version {
MultiAgentVersion::V2 => {
if self.agent_max_threads.is_some() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"agents.max_threads cannot be set when the multi-agent runtime is v2",
));
}
Ok(Some(
self.multi_agent_v2
.max_concurrent_threads_per_session
.saturating_sub(1),
))
}
MultiAgentVersion::Disabled | MultiAgentVersion::V1 => {
Ok(self.agent_max_threads.or(DEFAULT_AGENT_MAX_THREADS))
}
}
}
pub fn legacy_sandbox_policy(&self) -> SandboxPolicy {
self.permissions.legacy_sandbox_policy(self.cwd.as_path())
}
@@ -3063,29 +3098,13 @@ impl Config {
));
}
validate_multi_agent_v2_tool_namespace(multi_agent_v2.tool_namespace.as_deref())?;
let agent_max_threads_from_config = cfg.agents.as_ref().and_then(|agents| agents.max_threads);
let agent_max_threads = if features.enabled(Feature::MultiAgentV2) {
if agent_max_threads_from_config.is_some() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"agents.max_threads cannot be set when multi_agent_v2 is enabled",
));
}
Some(
multi_agent_v2
.max_concurrent_threads_per_session
.saturating_sub(1),
)
} else {
let agent_max_threads = agent_max_threads_from_config.or(DEFAULT_AGENT_MAX_THREADS);
if agent_max_threads == Some(0) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"agents.max_threads must be at least 1",
));
}
agent_max_threads
};
let agent_max_threads = cfg.agents.as_ref().and_then(|agents| agents.max_threads);
if agent_max_threads == Some(0) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"agents.max_threads must be at least 1",
));
}
let agent_max_depth = cfg
.agents
.as_ref()