mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
[codex] Simplify multi-agent version selection
This commit is contained in:
@@ -5,7 +5,6 @@ use crate::agent::role::DEFAULT_ROLE_NAME;
|
||||
use crate::agent::role::resolve_role_config;
|
||||
use crate::agent::status::is_final;
|
||||
use crate::codex_thread::ThreadConfigSnapshot;
|
||||
use crate::multi_agent_runtime::MultiAgentRuntime;
|
||||
use crate::session::emit_subagent_session_started;
|
||||
use crate::session_prefix::format_subagent_context_line;
|
||||
use crate::session_prefix::format_subagent_notification_message;
|
||||
@@ -213,26 +212,13 @@ impl AgentControl {
|
||||
|
||||
async fn spawn_agent_internal(
|
||||
&self,
|
||||
mut config: crate::config::Config,
|
||||
config: crate::config::Config,
|
||||
initial_operation: Op,
|
||||
session_source: Option<SessionSource>,
|
||||
options: SpawnAgentOptions,
|
||||
) -> CodexResult<LiveAgent> {
|
||||
let state = self.upgrade()?;
|
||||
let mut multi_agent_runtime = state.resolve_multi_agent_runtime(&config).await;
|
||||
if let Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn { depth, .. })) =
|
||||
session_source.as_ref()
|
||||
&& *depth >= config.agent_max_depth
|
||||
&& !multi_agent_runtime.multi_agent_v2_enabled()
|
||||
{
|
||||
let _ = config.features.disable(Feature::SpawnCsv);
|
||||
let _ = config.features.disable(Feature::Collab);
|
||||
multi_agent_runtime.disable_collab_tools();
|
||||
}
|
||||
let mut reservation = self
|
||||
.state
|
||||
.reserve_spawn_slot(multi_agent_runtime.agent_max_threads(&config))?;
|
||||
let multi_agent_v2_enabled = multi_agent_runtime.multi_agent_v2_enabled();
|
||||
let mut reservation = self.state.reserve_spawn_slot(config.agent_max_threads)?;
|
||||
let inherited_shell_snapshot = self
|
||||
.inherited_shell_snapshot_for_source(&state, session_source.as_ref())
|
||||
.await;
|
||||
@@ -268,7 +254,6 @@ impl AgentControl {
|
||||
Box::pin(self.spawn_forked_thread(
|
||||
&state,
|
||||
config,
|
||||
multi_agent_runtime,
|
||||
session_source,
|
||||
&options,
|
||||
inherited_shell_snapshot,
|
||||
@@ -354,7 +339,7 @@ impl AgentControl {
|
||||
|
||||
self.send_input(new_thread.thread_id, initial_operation)
|
||||
.await?;
|
||||
if !multi_agent_v2_enabled {
|
||||
if !new_thread.thread.enabled(Feature::MultiAgentV2) {
|
||||
let child_reference = agent_metadata
|
||||
.agent_path
|
||||
.as_ref()
|
||||
@@ -379,7 +364,6 @@ impl AgentControl {
|
||||
&self,
|
||||
state: &Arc<ThreadManagerState>,
|
||||
config: crate::config::Config,
|
||||
multi_agent_runtime: MultiAgentRuntime,
|
||||
session_source: SessionSource,
|
||||
options: &SpawnAgentOptions,
|
||||
inherited_shell_snapshot: Option<Arc<ShellSnapshot>>,
|
||||
@@ -434,14 +418,8 @@ impl AgentControl {
|
||||
}
|
||||
let multi_agent_v2_usage_hint_texts_to_filter: Vec<String> =
|
||||
if let Some(parent_thread) = parent_thread.as_ref() {
|
||||
let parent_config = parent_thread.codex.session.get_config().await;
|
||||
if parent_thread
|
||||
.codex
|
||||
.session
|
||||
.multi_agent_runtime()
|
||||
.await
|
||||
.multi_agent_v2_enabled()
|
||||
{
|
||||
if parent_thread.enabled(Feature::MultiAgentV2) {
|
||||
let parent_config = parent_thread.codex.session.get_config().await;
|
||||
[
|
||||
parent_config
|
||||
.multi_agent_v2
|
||||
@@ -458,7 +436,7 @@ impl AgentControl {
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
} else if multi_agent_runtime.multi_agent_v2_enabled() {
|
||||
} else if config.features.enabled(Feature::MultiAgentV2) {
|
||||
[
|
||||
config.multi_agent_v2.root_agent_usage_hint_text.clone(),
|
||||
config.multi_agent_v2.subagent_usage_hint_text.clone(),
|
||||
@@ -494,7 +472,7 @@ impl AgentControl {
|
||||
}
|
||||
}
|
||||
if preserve_reference_context_item
|
||||
&& multi_agent_runtime.multi_agent_v2_enabled()
|
||||
&& config.features.enabled(Feature::MultiAgentV2)
|
||||
&& let Some(subagent_usage_hint_text) =
|
||||
config.multi_agent_v2.subagent_usage_hint_text.clone()
|
||||
&& let Some(subagent_usage_hint_message) =
|
||||
@@ -603,21 +581,16 @@ impl AgentControl {
|
||||
thread_id: ThreadId,
|
||||
session_source: SessionSource,
|
||||
) -> CodexResult<ThreadId> {
|
||||
let state = self.upgrade()?;
|
||||
let mut multi_agent_runtime = state.resolve_multi_agent_runtime(&config).await;
|
||||
if let SessionSource::SubAgent(SubAgentSource::ThreadSpawn { depth, .. }) = &session_source
|
||||
&& *depth >= config.agent_max_depth
|
||||
&& !multi_agent_runtime.multi_agent_v2_enabled()
|
||||
&& !config.features.enabled(Feature::MultiAgentV2)
|
||||
{
|
||||
let _ = config.features.disable(Feature::SpawnCsv);
|
||||
let _ = config.features.disable(Feature::Collab);
|
||||
multi_agent_runtime.disable_collab_tools();
|
||||
}
|
||||
let state = self.upgrade()?;
|
||||
let state_db_ctx = state.state_db();
|
||||
let mut reservation = self
|
||||
.state
|
||||
.reserve_spawn_slot(multi_agent_runtime.agent_max_threads(&config))?;
|
||||
let multi_agent_v2_enabled = multi_agent_runtime.multi_agent_v2_enabled();
|
||||
let mut reservation = self.state.reserve_spawn_slot(config.agent_max_threads)?;
|
||||
let (session_source, agent_metadata) = match session_source {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
@@ -686,7 +659,7 @@ impl AgentControl {
|
||||
// Resumed threads are re-registered in-memory and need the same listener
|
||||
// attachment path as freshly spawned threads.
|
||||
state.notify_thread_created(resumed_thread.thread_id);
|
||||
if !multi_agent_v2_enabled {
|
||||
if !resumed_thread.thread.enabled(Feature::MultiAgentV2) {
|
||||
let child_reference = agent_metadata
|
||||
.agent_path
|
||||
.as_ref()
|
||||
@@ -1071,17 +1044,12 @@ impl AgentControl {
|
||||
};
|
||||
let child_thread = state.get_thread(child_thread_id).await.ok();
|
||||
let message = format_subagent_notification_message(child_reference.as_str(), &status);
|
||||
let child_multi_agent_v2_enabled = if let Some(child_thread) = child_thread.as_ref() {
|
||||
child_thread
|
||||
.codex
|
||||
.session
|
||||
.multi_agent_runtime()
|
||||
.await
|
||||
.multi_agent_v2_enabled()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if child_agent_path.is_some() && child_multi_agent_v2_enabled {
|
||||
if child_agent_path.is_some()
|
||||
&& child_thread
|
||||
.as_ref()
|
||||
.map(|thread| thread.enabled(Feature::MultiAgentV2))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
let Some(child_agent_path) = child_agent_path.clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -60,6 +60,7 @@ pub use codex_mcp::SandboxState;
|
||||
mod mcp_openai_file;
|
||||
mod mcp_tool_call;
|
||||
pub(crate) mod mention_syntax;
|
||||
mod tool_mode;
|
||||
pub(crate) mod utils;
|
||||
pub use mention_syntax::PLUGIN_TEXT_MENTION_SIGIL;
|
||||
pub use mention_syntax::TOOL_MENTION_SIGIL;
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
use crate::config::Config;
|
||||
use crate::config::DEFAULT_AGENT_MAX_THREADS;
|
||||
use crate::config::ManagedFeatures;
|
||||
use codex_features::Feature;
|
||||
use codex_models_manager::manager::RefreshStrategy;
|
||||
use codex_models_manager::manager::SharedModelsManager;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::MultiAgentVersion;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct MultiAgentRuntime {
|
||||
version: Option<MultiAgentVersion>,
|
||||
collab_tools_disabled: bool,
|
||||
}
|
||||
|
||||
impl MultiAgentRuntime {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn from_features(features: &ManagedFeatures) -> Self {
|
||||
Self {
|
||||
version: multi_agent_version_from_features(features),
|
||||
collab_tools_disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve(model_info: &ModelInfo, features: &ManagedFeatures) -> Self {
|
||||
Self {
|
||||
version: model_info
|
||||
.multi_agent_version
|
||||
.or_else(|| multi_agent_version_from_features(features)),
|
||||
collab_tools_disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn resolve_for_config(
|
||||
models_manager: &SharedModelsManager,
|
||||
config: &Config,
|
||||
) -> Self {
|
||||
let model = models_manager
|
||||
.get_default_model(&config.model, RefreshStrategy::Offline)
|
||||
.await;
|
||||
let model_info = models_manager
|
||||
.get_model_info(&model, &config.to_models_manager_config())
|
||||
.await;
|
||||
Self::resolve(&model_info, &config.features)
|
||||
}
|
||||
|
||||
pub(crate) fn with_model(self, model_info: &ModelInfo, features: &ManagedFeatures) -> Self {
|
||||
Self {
|
||||
collab_tools_disabled: self.collab_tools_disabled,
|
||||
..Self::resolve(model_info, features)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn collab_tools_enabled(self) -> bool {
|
||||
!self.collab_tools_disabled && self.version.is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn multi_agent_v2_enabled(self) -> bool {
|
||||
self.version == Some(MultiAgentVersion::V2)
|
||||
}
|
||||
|
||||
pub(crate) fn agent_max_threads(self, config: &Config) -> Option<usize> {
|
||||
if self.multi_agent_v2_enabled() {
|
||||
Some(
|
||||
config
|
||||
.multi_agent_v2
|
||||
.max_concurrent_threads_per_session
|
||||
.saturating_sub(1),
|
||||
)
|
||||
} else if config.features.enabled(Feature::MultiAgentV2) {
|
||||
DEFAULT_AGENT_MAX_THREADS
|
||||
} else {
|
||||
config.agent_max_threads
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn disable_collab_tools(&mut self) {
|
||||
self.collab_tools_disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn multi_agent_version_from_features(features: &ManagedFeatures) -> Option<MultiAgentVersion> {
|
||||
if features.enabled(Feature::MultiAgentV2) {
|
||||
Some(MultiAgentVersion::V2)
|
||||
} else if features.enabled(Feature::Collab) {
|
||||
Some(MultiAgentVersion::V1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_features::Features;
|
||||
|
||||
fn features(enabled: &[Feature]) -> ManagedFeatures {
|
||||
let mut features = Features::default();
|
||||
for feature in enabled {
|
||||
features.enable(*feature);
|
||||
}
|
||||
features.into()
|
||||
}
|
||||
|
||||
fn model_info(multi_agent_version: Option<MultiAgentVersion>) -> ModelInfo {
|
||||
let mut model_info = codex_models_manager::model_info::model_info_from_slug("test-model");
|
||||
model_info.multi_agent_version = multi_agent_version;
|
||||
model_info
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn omitted_selector_follows_feature_flags() {
|
||||
let runtime = MultiAgentRuntime::resolve(
|
||||
&model_info(/*multi_agent_version*/ None),
|
||||
&features(&[Feature::MultiAgentV2]),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
runtime,
|
||||
MultiAgentRuntime {
|
||||
version: Some(MultiAgentVersion::V2),
|
||||
collab_tools_disabled: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_selector_overrides_feature_flags() {
|
||||
let runtime = MultiAgentRuntime::resolve(
|
||||
&model_info(Some(MultiAgentVersion::V1)),
|
||||
&features(&[Feature::MultiAgentV2]),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
runtime,
|
||||
MultiAgentRuntime {
|
||||
version: Some(MultiAgentVersion::V1),
|
||||
collab_tools_disabled: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ use crate::context::PersonalitySpecInstructions;
|
||||
use crate::default_skill_metadata_budget;
|
||||
use crate::environment_selection::ResolvedTurnEnvironments;
|
||||
use crate::exec_policy::ExecPolicyManager;
|
||||
use crate::multi_agent_runtime::MultiAgentRuntime;
|
||||
use crate::parse_turn_item;
|
||||
use crate::path_utils::normalize_for_native_workdir;
|
||||
use crate::realtime_conversation::RealtimeConversationManager;
|
||||
@@ -486,6 +485,14 @@ impl Codex {
|
||||
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
|
||||
let (tx_event, rx_event) = async_channel::unbounded();
|
||||
|
||||
if let SessionSource::SubAgent(SubAgentSource::ThreadSpawn { depth, .. }) = session_source
|
||||
&& depth >= config.agent_max_depth
|
||||
&& !config.features.enabled(Feature::MultiAgentV2)
|
||||
{
|
||||
let _ = config.features.disable(Feature::SpawnCsv);
|
||||
let _ = config.features.disable(Feature::Collab);
|
||||
}
|
||||
|
||||
let primary_environment = environment_selections.primary_environment();
|
||||
let mut user_instruction_warnings = Vec::new();
|
||||
let user_instructions = AgentsMdManager::new(&config)
|
||||
@@ -511,7 +518,7 @@ impl Codex {
|
||||
)
|
||||
};
|
||||
|
||||
let mut config = Arc::new(config);
|
||||
let config = Arc::new(config);
|
||||
let refresh_strategy = if session_source.is_non_root_agent() {
|
||||
codex_models_manager::manager::RefreshStrategy::Offline
|
||||
} else {
|
||||
@@ -536,19 +543,6 @@ impl Codex {
|
||||
let model_info = models_manager
|
||||
.get_model_info(model.as_str(), &config.to_models_manager_config())
|
||||
.await;
|
||||
let mut multi_agent_runtime = MultiAgentRuntime::resolve(&model_info, &config.features);
|
||||
{
|
||||
let config = Arc::make_mut(&mut config);
|
||||
if let SessionSource::SubAgent(SubAgentSource::ThreadSpawn { depth, .. }) =
|
||||
session_source
|
||||
&& depth >= config.agent_max_depth
|
||||
&& !multi_agent_runtime.multi_agent_v2_enabled()
|
||||
{
|
||||
let _ = config.features.disable(Feature::SpawnCsv);
|
||||
let _ = config.features.disable(Feature::Collab);
|
||||
multi_agent_runtime.disable_collab_tools();
|
||||
}
|
||||
}
|
||||
let base_instructions = config
|
||||
.base_instructions
|
||||
.clone()
|
||||
@@ -579,7 +573,6 @@ impl Codex {
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_runtime,
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
service_tier,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
@@ -1457,11 +1450,6 @@ impl Session {
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub(crate) async fn multi_agent_runtime(&self) -> MultiAgentRuntime {
|
||||
let state = self.state.lock().await;
|
||||
state.session_configuration.multi_agent_runtime
|
||||
}
|
||||
|
||||
pub(crate) async fn provider(&self) -> ModelProviderInfo {
|
||||
let state = self.state.lock().await;
|
||||
state.session_configuration.provider.clone()
|
||||
@@ -1662,7 +1650,7 @@ impl Session {
|
||||
turn_context: &TurnContext,
|
||||
msg: &EventMsg,
|
||||
) {
|
||||
if !turn_context.multi_agent_v2_enabled() {
|
||||
if !self.enabled(Feature::MultiAgentV2) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
|
||||
@@ -6,7 +7,7 @@ pub(super) fn usage_hint_text<'a>(
|
||||
turn_context: &'a TurnContext,
|
||||
session_source: &SessionSource,
|
||||
) -> Option<&'a str> {
|
||||
if !turn_context.multi_agent_v2_enabled() {
|
||||
if !turn_context.features.enabled(Feature::MultiAgentV2) {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use codex_protocol::openai_models::ToolMode;
|
||||
use crate::tool_mode::resolve_tool_mode;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
/// Spawn a review thread using the given prompt.
|
||||
@@ -48,15 +48,7 @@ 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
|
||||
}
|
||||
});
|
||||
let tool_mode = resolve_tool_mode(&model_info, &per_turn_config.features);
|
||||
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!(
|
||||
|
||||
@@ -2,7 +2,6 @@ use super::input_queue::InputQueue;
|
||||
use super::*;
|
||||
use crate::config::ConstraintError;
|
||||
use crate::goals::GoalRuntimeState;
|
||||
use crate::multi_agent_runtime::MultiAgentRuntime;
|
||||
use crate::skills::SkillError;
|
||||
use crate::state::ActiveTurn;
|
||||
use codex_protocol::SessionId;
|
||||
@@ -46,7 +45,6 @@ pub(crate) struct SessionConfiguration {
|
||||
pub(super) provider: ModelProviderInfo,
|
||||
|
||||
pub(super) collaboration_mode: CollaborationMode,
|
||||
pub(super) multi_agent_runtime: MultiAgentRuntime,
|
||||
pub(super) model_reasoning_summary: Option<ReasoningSummaryConfig>,
|
||||
pub(super) service_tier: Option<String>,
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ use crate::goals::ExternalGoalPreviousStatus;
|
||||
use crate::goals::ExternalGoalSet;
|
||||
use crate::goals::GoalRuntimeEvent;
|
||||
use crate::goals::SetGoalRequest;
|
||||
use crate::multi_agent_runtime::MultiAgentRuntime;
|
||||
use crate::rollout::recorder::RolloutRecorder;
|
||||
use crate::state::ActiveTurn;
|
||||
use crate::state::TaskKind;
|
||||
@@ -3072,7 +3071,6 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_runtime: MultiAgentRuntime::from_features(&config.features),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
@@ -3178,7 +3176,6 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_runtime: MultiAgentRuntime::from_features(&config.features),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
@@ -3707,7 +3704,6 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
||||
SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_runtime: MultiAgentRuntime::from_features(&config.features),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
@@ -4452,7 +4448,6 @@ async fn session_new_fails_when_zsh_fork_enabled_without_packaged_zsh() {
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_runtime: MultiAgentRuntime::from_features(&config.features),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
@@ -4563,7 +4558,6 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_runtime: MultiAgentRuntime::from_features(&config.features),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
@@ -4799,7 +4793,6 @@ async fn make_session_with_config_and_rx(
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_runtime: MultiAgentRuntime::from_features(&config.features),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
@@ -4904,7 +4897,6 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_runtime: MultiAgentRuntime::from_features(&config.features),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
@@ -6409,7 +6401,6 @@ where
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_runtime: MultiAgentRuntime::from_features(&config.features),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::*;
|
||||
use crate::SkillLoadOutcome;
|
||||
use crate::config::GhostSnapshotConfig;
|
||||
use crate::environment_selection::ResolvedTurnEnvironments;
|
||||
use crate::tool_mode::resolve_tool_mode;
|
||||
use codex_model_provider::SharedModelProvider;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_protocol::SessionId;
|
||||
@@ -164,6 +165,14 @@ impl TurnContext {
|
||||
self.goal_tools_supported && self.features.get().enabled(Feature::Goals)
|
||||
}
|
||||
|
||||
pub(crate) fn code_mode_enabled(&self) -> bool {
|
||||
matches!(self.tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly)
|
||||
}
|
||||
|
||||
pub(crate) fn code_mode_only_enabled(&self) -> bool {
|
||||
self.tool_mode == ToolMode::CodeModeOnly
|
||||
}
|
||||
|
||||
pub(crate) async fn with_model(
|
||||
&self,
|
||||
model: String,
|
||||
@@ -174,15 +183,7 @@ 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 tool_mode = resolve_tool_mode(&model_info, &config.features);
|
||||
let truncation_policy = model_info.truncation_policy.into();
|
||||
let supported_reasoning_levels = model_info
|
||||
.supported_reasoning_levels
|
||||
@@ -487,15 +488,7 @@ 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
|
||||
}
|
||||
});
|
||||
let tool_mode = resolve_tool_mode(&model_info, &per_turn_config.features);
|
||||
per_turn_config.service_tier = get_service_tier(
|
||||
per_turn_config.service_tier,
|
||||
per_turn_config.features.enabled(Feature::FastMode),
|
||||
|
||||
@@ -27,7 +27,6 @@ use crate::goals::GoalRuntimeEvent;
|
||||
use crate::hook_runtime::inspect_pending_input;
|
||||
use crate::hook_runtime::record_additional_contexts;
|
||||
use crate::hook_runtime::record_pending_input;
|
||||
use crate::multi_agent_runtime::MultiAgentRuntime;
|
||||
use crate::session::TurnInput;
|
||||
use crate::session::session::Session;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
@@ -71,11 +70,11 @@ pub(crate) enum InterruptedTurnHistoryMarker {
|
||||
}
|
||||
|
||||
impl InterruptedTurnHistoryMarker {
|
||||
pub(crate) fn from_config(config: &Config, multi_agent_runtime: MultiAgentRuntime) -> Self {
|
||||
pub(crate) fn from_config(config: &Config) -> Self {
|
||||
if !config.agent_interrupt_message_enabled {
|
||||
return Self::Disabled;
|
||||
}
|
||||
if multi_agent_runtime.multi_agent_v2_enabled() {
|
||||
if config.features.enabled(Feature::MultiAgentV2) {
|
||||
Self::Developer
|
||||
} else {
|
||||
Self::ContextualUser
|
||||
@@ -841,11 +840,9 @@ impl Session {
|
||||
.await;
|
||||
|
||||
if reason == TurnAbortReason::Interrupted
|
||||
&& let Some(marker) =
|
||||
interrupted_turn_history_marker(InterruptedTurnHistoryMarker::from_config(
|
||||
task.turn_context.config.as_ref(),
|
||||
task.turn_context.multi_agent_runtime,
|
||||
))
|
||||
&& let Some(marker) = interrupted_turn_history_marker(
|
||||
InterruptedTurnHistoryMarker::from_config(task.turn_context.config.as_ref()),
|
||||
)
|
||||
{
|
||||
self.record_conversation_items(
|
||||
task.turn_context.as_ref(),
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::config::ThreadStoreConfig;
|
||||
use crate::environment_selection::default_thread_environment_selections;
|
||||
use crate::environment_selection::resolve_environment_selections;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::multi_agent_runtime::MultiAgentRuntime;
|
||||
use crate::rollout::truncation;
|
||||
use crate::session::Codex;
|
||||
use crate::session::CodexSpawnArgs;
|
||||
@@ -638,11 +637,10 @@ impl ThreadManager {
|
||||
))
|
||||
})?;
|
||||
let history = stored_thread_to_initial_history(stored_thread, fork_source.rollout_path())?;
|
||||
let multi_agent_runtime = self.resolve_multi_agent_runtime(&options.config).await;
|
||||
options.initial_history = fork_history_from_snapshot(
|
||||
ForkSnapshot::Interrupted,
|
||||
history,
|
||||
InterruptedTurnHistoryMarker::from_config(&options.config, multi_agent_runtime),
|
||||
InterruptedTurnHistoryMarker::from_config(&options.config),
|
||||
);
|
||||
self.start_thread_with_options_and_fork_source(options, Some(forked_from_thread_id))
|
||||
.await
|
||||
@@ -897,9 +895,7 @@ impl ThreadManager {
|
||||
InitialHistory::Forked(_) => history.forked_from_id(),
|
||||
InitialHistory::New | InitialHistory::Cleared => None,
|
||||
};
|
||||
let multi_agent_runtime = self.resolve_multi_agent_runtime(&config).await;
|
||||
let interrupted_marker =
|
||||
InterruptedTurnHistoryMarker::from_config(&config, multi_agent_runtime);
|
||||
let interrupted_marker = InterruptedTurnHistoryMarker::from_config(&config);
|
||||
let history = fork_history_from_snapshot(snapshot, history, interrupted_marker);
|
||||
let environments = default_thread_environment_selections(
|
||||
self.state.environment_manager.as_ref(),
|
||||
@@ -926,10 +922,6 @@ impl ThreadManager {
|
||||
AgentControl::new(Arc::downgrade(&self.state))
|
||||
}
|
||||
|
||||
async fn resolve_multi_agent_runtime(&self, config: &Config) -> MultiAgentRuntime {
|
||||
self.state.resolve_multi_agent_runtime(config).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn captured_ops(&self) -> Vec<(ThreadId, Op)> {
|
||||
self.state
|
||||
@@ -941,10 +933,6 @@ impl ThreadManager {
|
||||
}
|
||||
|
||||
impl ThreadManagerState {
|
||||
pub(crate) async fn resolve_multi_agent_runtime(&self, config: &Config) -> MultiAgentRuntime {
|
||||
MultiAgentRuntime::resolve_for_config(&self.models_manager, config).await
|
||||
}
|
||||
|
||||
pub(crate) fn state_db(&self) -> Option<StateDbHandle> {
|
||||
self.state_db.clone()
|
||||
}
|
||||
|
||||
@@ -119,13 +119,13 @@ async fn build_runner_options(
|
||||
"agent depth limit reached; this session cannot spawn more subagents".to_string(),
|
||||
));
|
||||
}
|
||||
let agent_max_threads = turn.multi_agent_runtime.agent_max_threads(&turn.config);
|
||||
if agent_max_threads == Some(0) {
|
||||
if turn.config.agent_max_threads == Some(0) {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"agent thread limit reached; this session cannot spawn more subagents".to_string(),
|
||||
));
|
||||
}
|
||||
let max_concurrency = normalize_concurrency(requested_concurrency, agent_max_threads);
|
||||
let max_concurrency =
|
||||
normalize_concurrency(requested_concurrency, turn.config.agent_max_threads);
|
||||
let base_instructions = session.get_base_instructions().await;
|
||||
let spawn_config = build_agent_spawn_config(&base_instructions, turn.as_ref())?;
|
||||
Ok(JobRunnerOptions {
|
||||
|
||||
@@ -109,7 +109,7 @@ async fn handle_spawn_agent(
|
||||
)
|
||||
.await?;
|
||||
apply_spawn_agent_runtime_overrides(&mut config, turn.as_ref())?;
|
||||
apply_spawn_agent_overrides(&mut config, turn.multi_agent_runtime, child_depth);
|
||||
apply_spawn_agent_overrides(&mut config, child_depth);
|
||||
|
||||
let result = Box::pin(session.services.agent_control.spawn_agent_with_metadata(
|
||||
config,
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::config::Config;
|
||||
use crate::config::DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS;
|
||||
use crate::config::HARD_MAX_MULTI_AGENT_V2_TIMEOUT_MS;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::multi_agent_runtime::MultiAgentRuntime;
|
||||
use crate::session::session::Session;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
@@ -217,7 +216,7 @@ pub(crate) fn build_agent_resume_config(
|
||||
child_depth: i32,
|
||||
) -> Result<Config, FunctionCallError> {
|
||||
let mut config = build_agent_shared_config(turn)?;
|
||||
apply_spawn_agent_overrides(&mut config, turn.multi_agent_runtime, child_depth);
|
||||
apply_spawn_agent_overrides(&mut config, child_depth);
|
||||
// For resume, keep base instructions sourced from rollout/session metadata.
|
||||
config.base_instructions = None;
|
||||
Ok(config)
|
||||
@@ -281,12 +280,8 @@ pub(crate) fn apply_spawn_agent_runtime_overrides(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn apply_spawn_agent_overrides(
|
||||
config: &mut Config,
|
||||
multi_agent_runtime: MultiAgentRuntime,
|
||||
child_depth: i32,
|
||||
) {
|
||||
if child_depth >= config.agent_max_depth && !multi_agent_runtime.multi_agent_v2_enabled() {
|
||||
pub(crate) fn apply_spawn_agent_overrides(config: &mut Config, child_depth: i32) {
|
||||
if child_depth >= config.agent_max_depth && !config.features.enabled(Feature::MultiAgentV2) {
|
||||
let _ = config.features.disable(Feature::SpawnCsv);
|
||||
let _ = config.features.disable(Feature::Collab);
|
||||
}
|
||||
@@ -315,6 +310,7 @@ pub(crate) async fn apply_requested_spawn_agent_model_overrides(
|
||||
.models_manager
|
||||
.get_model_info(&selected_model_name, &config.to_models_manager_config())
|
||||
.await;
|
||||
|
||||
config.model = Some(selected_model_name.clone());
|
||||
if let Some(reasoning_effort) = requested_reasoning_effort {
|
||||
validate_spawn_agent_reasoning_effort(
|
||||
@@ -368,6 +364,7 @@ pub(crate) async fn apply_spawn_agent_service_tier(
|
||||
.models_manager
|
||||
.get_model_info(model.as_str(), &config.to_models_manager_config())
|
||||
.await;
|
||||
|
||||
if let Some(requested_service_tier) = requested_service_tier
|
||||
&& !model_info.supports_service_tier(requested_service_tier)
|
||||
{
|
||||
|
||||
@@ -108,7 +108,7 @@ async fn handle_spawn_agent(
|
||||
)
|
||||
.await?;
|
||||
apply_spawn_agent_runtime_overrides(&mut config, turn.as_ref())?;
|
||||
apply_spawn_agent_overrides(&mut config, turn.multi_agent_runtime, child_depth);
|
||||
apply_spawn_agent_overrides(&mut config, child_depth);
|
||||
|
||||
let spawn_source = thread_spawn_source(
|
||||
session.conversation_id,
|
||||
|
||||
@@ -60,7 +60,7 @@ use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ToolMode;
|
||||
use codex_protocol::openai_models::MultiAgentVersion;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_tools::DiscoverableTool;
|
||||
@@ -231,10 +231,8 @@ fn spec_for_model_request(
|
||||
exposure: ToolExposure,
|
||||
spec: ToolSpec,
|
||||
) -> ToolSpec {
|
||||
if matches!(
|
||||
turn_context.tool_mode,
|
||||
ToolMode::CodeMode | ToolMode::CodeModeOnly
|
||||
) && exposure != ToolExposure::DirectModelOnly
|
||||
if code_mode_enabled(turn_context)
|
||||
&& exposure != ToolExposure::DirectModelOnly
|
||||
&& codex_code_mode::is_code_mode_nested_tool(spec.name())
|
||||
{
|
||||
codex_tools::augment_tool_spec_for_code_mode(spec)
|
||||
@@ -285,12 +283,32 @@ fn namespace_tools_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context.provider.capabilities().namespace_tools
|
||||
}
|
||||
|
||||
fn code_mode_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context.code_mode_enabled()
|
||||
}
|
||||
|
||||
fn code_mode_only_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context.code_mode_only_enabled()
|
||||
}
|
||||
|
||||
fn multi_agent_v2_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context.multi_agent_v2_enabled()
|
||||
multi_agent_version(turn_context) == Some(MultiAgentVersion::V2)
|
||||
}
|
||||
|
||||
fn collab_tools_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context.collab_tools_enabled()
|
||||
multi_agent_version(turn_context).is_some()
|
||||
}
|
||||
|
||||
fn multi_agent_version(turn_context: &TurnContext) -> Option<MultiAgentVersion> {
|
||||
turn_context.model_info.multi_agent_version.or_else(|| {
|
||||
if turn_context.features.get().enabled(Feature::MultiAgentV2) {
|
||||
Some(MultiAgentVersion::V2)
|
||||
} else if turn_context.features.get().enabled(Feature::Collab) {
|
||||
Some(MultiAgentVersion::V1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn goal_tools_enabled(turn_context: &TurnContext) -> bool {
|
||||
@@ -393,7 +411,7 @@ fn is_hidden_by_code_mode_only(
|
||||
tool_name: &ToolName,
|
||||
exposure: ToolExposure,
|
||||
) -> bool {
|
||||
turn_context.tool_mode == ToolMode::CodeModeOnly
|
||||
code_mode_only_enabled(turn_context)
|
||||
&& exposure != ToolExposure::DirectModelOnly
|
||||
&& codex_code_mode::is_code_mode_nested_tool(&codex_tools::code_mode_name_for_tool_name(
|
||||
tool_name,
|
||||
@@ -405,10 +423,7 @@ fn build_code_mode_executors(
|
||||
executors: &[Arc<dyn CoreToolRuntime>],
|
||||
deferred_tools_available: bool,
|
||||
) -> Vec<Arc<dyn CoreToolRuntime>> {
|
||||
if !matches!(
|
||||
turn_context.tool_mode,
|
||||
ToolMode::CodeMode | ToolMode::CodeModeOnly
|
||||
) {
|
||||
if !code_mode_enabled(turn_context) {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
@@ -442,7 +457,7 @@ fn build_code_mode_executors(
|
||||
create_code_mode_tool(
|
||||
&enabled_tools,
|
||||
&namespace_descriptions,
|
||||
turn_context.tool_mode == ToolMode::CodeModeOnly,
|
||||
code_mode_only_enabled(turn_context),
|
||||
deferred_tools_available,
|
||||
),
|
||||
code_mode_nested_tool_specs,
|
||||
@@ -845,10 +860,7 @@ fn append_extension_tool_executors(
|
||||
.iter()
|
||||
.map(|executor| executor.tool_name())
|
||||
.collect::<HashSet<_>>();
|
||||
if matches!(
|
||||
turn_context.tool_mode,
|
||||
ToolMode::CodeMode | ToolMode::CodeModeOnly
|
||||
) {
|
||||
if code_mode_enabled(turn_context) {
|
||||
reserved_tool_names.insert(ToolName::plain(codex_code_mode::PUBLIC_TOOL_NAME));
|
||||
reserved_tool_names.insert(ToolName::plain(codex_code_mode::WAIT_TOOL_NAME));
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_protocol::openai_models::ApplyPatchToolType;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::MultiAgentVersion;
|
||||
use codex_protocol::openai_models::ToolMode;
|
||||
use codex_protocol::openai_models::WebSearchToolType;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
@@ -32,6 +33,7 @@ use serde_json::json;
|
||||
|
||||
use crate::session::tests::make_session_and_context;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::tool_mode::resolve_tool_mode;
|
||||
use crate::tools::handlers::multi_agents_spec::MULTI_AGENT_V1_NAMESPACE;
|
||||
use crate::tools::router::ToolRouter;
|
||||
use crate::tools::router::ToolRouterParams;
|
||||
@@ -216,15 +218,7 @@ fn set_feature(turn: &mut TurnContext, feature: Feature, enabled: bool) {
|
||||
.expect("test feature should be disableable in config");
|
||||
}
|
||||
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
|
||||
}
|
||||
});
|
||||
resolve_tool_mode_for_turn(turn);
|
||||
}
|
||||
|
||||
fn set_features(turn: &mut TurnContext, features: &[Feature]) {
|
||||
@@ -239,6 +233,10 @@ fn update_config(turn: &mut TurnContext, update: impl FnOnce(&mut crate::config:
|
||||
turn.config = Arc::new(config);
|
||||
}
|
||||
|
||||
fn resolve_tool_mode_for_turn(turn: &mut TurnContext) {
|
||||
turn.tool_mode = resolve_tool_mode(&turn.model_info, &turn.config.features);
|
||||
}
|
||||
|
||||
fn set_web_search_mode(turn: &mut TurnContext, mode: WebSearchMode) {
|
||||
update_config(turn, |config| {
|
||||
config
|
||||
@@ -812,7 +810,7 @@ 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;
|
||||
resolve_tool_mode_for_turn(turn);
|
||||
})
|
||||
.await;
|
||||
direct.assert_visible_lacks(&[
|
||||
@@ -821,6 +819,40 @@ async fn tool_mode_selector_overrides_feature_flags() {
|
||||
]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn multi_agent_version_selector_overrides_feature_flags() {
|
||||
let v1 = probe(|turn| {
|
||||
set_feature(turn, Feature::MultiAgentV2, /*enabled*/ true);
|
||||
turn.model_info.multi_agent_version = Some(MultiAgentVersion::V1);
|
||||
})
|
||||
.await;
|
||||
|
||||
v1.assert_visible_contains(&[
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
]);
|
||||
v1.assert_visible_lacks(&["send_message", "followup_task", "list_agents"]);
|
||||
|
||||
let v2 = probe(|turn| {
|
||||
set_feature(turn, Feature::Collab, /*enabled*/ false);
|
||||
set_feature(turn, Feature::MultiAgentV2, /*enabled*/ false);
|
||||
turn.model_info.multi_agent_version = Some(MultiAgentVersion::V2);
|
||||
})
|
||||
.await;
|
||||
|
||||
v2.assert_visible_contains(&[
|
||||
"spawn_agent",
|
||||
"send_message",
|
||||
"followup_task",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"list_agents",
|
||||
]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn v1_multi_agent_tools_defer_when_tool_search_available() {
|
||||
let plan = probe(|turn| {
|
||||
|
||||
Reference in New Issue
Block a user