use super::AgentControl; use crate::codex_thread::CodexThread; use codex_protocol::error::CodexErr; use codex_protocol::error::CodexErrorDetails; use codex_protocol::error::Result as CodexResult; use codex_protocol::protocol::MultiAgentVersion; use codex_protocol::protocol::SessionSource; use std::sync::Arc; use std::sync::OnceLock; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; #[derive(Default)] pub(super) struct AgentExecutionLimiter { active: AtomicUsize, max_threads: OnceLock, } pub(crate) struct AgentExecutionGuard { limiter: Arc, } impl Drop for AgentExecutionGuard { fn drop(&mut self) { self.limiter.active.fetch_sub(1, Ordering::AcqRel); } } impl AgentControl { pub(crate) async fn ensure_execution_capacity_for_turn_start( &self, thread: &CodexThread, ) -> CodexResult<()> { if thread.session.active_turn.lock().await.is_some() { return Ok(()); } let config = thread.session.get_config().await; let multi_agent_version = thread .multi_agent_version() .unwrap_or_else(|| config.multi_agent_version_from_features()); self.ensure_execution_capacity(multi_agent_version, &thread.session_source) } pub(crate) fn ensure_execution_capacity( &self, multi_agent_version: MultiAgentVersion, session_source: &SessionSource, ) -> CodexResult<()> { if !is_execution_limited(multi_agent_version, session_source) { return Ok(()); } let max_threads = self.agent_execution_limiter.max_threads(); if self.agent_execution_limiter.has_capacity() { Ok(()) } else { Err(CodexErr::new(CodexErrorDetails::AgentLimitReached { max_threads, })) } } pub(crate) fn execution_guard( &self, multi_agent_version: MultiAgentVersion, session_source: &SessionSource, ) -> Option { is_execution_limited(multi_agent_version, session_source) .then(|| Arc::clone(&self.agent_execution_limiter).guard()) } } impl AgentExecutionLimiter { pub(super) fn initialize(&self, max_threads: usize) { self.max_threads.get_or_init(|| max_threads); } fn max_threads(&self) -> usize { self.max_threads.get().copied().unwrap_or(usize::MAX) } fn has_capacity(&self) -> bool { self.active.load(Ordering::Acquire) < self.max_threads() } fn guard(self: Arc) -> AgentExecutionGuard { self.active.fetch_add(1, Ordering::AcqRel); AgentExecutionGuard { limiter: self } } } fn is_execution_limited( multi_agent_version: MultiAgentVersion, session_source: &SessionSource, ) -> bool { multi_agent_version == MultiAgentVersion::V2 && matches!(session_source, SessionSource::SubAgent(_)) } #[cfg(test)] #[path = "execution_tests.rs"] mod tests;