mirror of
https://github.com/openai/codex.git
synced 2026-11-13 21:30:47 +00:00
Centralize resolved multi-agent runtime version
This commit is contained in:
@@ -218,12 +218,6 @@ impl AgentControl {
|
||||
options: SpawnAgentOptions,
|
||||
) -> CodexResult<LiveAgent> {
|
||||
let state = self.upgrade()?;
|
||||
let multi_agent_v2 = match session_source.as_ref() {
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn { agent_path, .. })) => {
|
||||
agent_path.is_some()
|
||||
}
|
||||
_ => config.features.enabled(Feature::MultiAgentV2),
|
||||
};
|
||||
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())
|
||||
@@ -345,7 +339,7 @@ impl AgentControl {
|
||||
|
||||
self.send_input(new_thread.thread_id, initial_operation)
|
||||
.await?;
|
||||
if !multi_agent_v2 {
|
||||
if agent_metadata.agent_path.is_none() {
|
||||
let child_reference = agent_metadata
|
||||
.agent_path
|
||||
.as_ref()
|
||||
@@ -422,10 +416,9 @@ impl AgentControl {
|
||||
forked_rollout_items =
|
||||
truncate_rollout_to_last_n_fork_turns(&forked_rollout_items, *last_n_turns);
|
||||
}
|
||||
let multi_agent_v2 = session_source.get_agent_path().is_some();
|
||||
let multi_agent_v2_usage_hint_texts_to_filter: Vec<String> =
|
||||
if let Some(parent_thread) = parent_thread.as_ref() {
|
||||
if multi_agent_v2 {
|
||||
if session_source.get_agent_path().is_some() {
|
||||
let parent_config = parent_thread.codex.session.get_config().await;
|
||||
[
|
||||
parent_config
|
||||
@@ -443,7 +436,7 @@ impl AgentControl {
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
} else if multi_agent_v2 {
|
||||
} else if session_source.get_agent_path().is_some() {
|
||||
[
|
||||
config.multi_agent_v2.root_agent_usage_hint_text.clone(),
|
||||
config.multi_agent_v2.subagent_usage_hint_text.clone(),
|
||||
@@ -479,7 +472,7 @@ impl AgentControl {
|
||||
}
|
||||
}
|
||||
if preserve_reference_context_item
|
||||
&& multi_agent_v2
|
||||
&& session_source.get_agent_path().is_some()
|
||||
&& let Some(subagent_usage_hint_text) =
|
||||
config.multi_agent_v2.subagent_usage_hint_text.clone()
|
||||
&& let Some(subagent_usage_hint_message) =
|
||||
@@ -595,15 +588,9 @@ impl AgentControl {
|
||||
thread_id: ThreadId,
|
||||
session_source: SessionSource,
|
||||
) -> CodexResult<ThreadId> {
|
||||
let multi_agent_v2 = match &session_source {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { agent_path, .. }) => {
|
||||
agent_path.is_some()
|
||||
}
|
||||
_ => config.features.enabled(Feature::MultiAgentV2),
|
||||
};
|
||||
if let SessionSource::SubAgent(SubAgentSource::ThreadSpawn { depth, .. }) = &session_source
|
||||
&& *depth >= config.agent_max_depth
|
||||
&& !multi_agent_v2
|
||||
&& !config.features.enabled(Feature::MultiAgentV2)
|
||||
{
|
||||
let _ = config.features.disable(Feature::SpawnCsv);
|
||||
let _ = config.features.disable(Feature::Collab);
|
||||
@@ -679,7 +666,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 {
|
||||
if agent_metadata.agent_path.is_none() {
|
||||
let child_reference = agent_metadata
|
||||
.agent_path
|
||||
.as_ref()
|
||||
|
||||
@@ -226,7 +226,6 @@ use self::turn::collect_explicit_app_ids_from_skill_items;
|
||||
use self::turn::realtime_text_for_event;
|
||||
use self::turn_context::TurnContext;
|
||||
use self::turn_context::TurnSkillsContext;
|
||||
use self::turn_context::resolve_multi_agent_version;
|
||||
#[cfg(test)]
|
||||
mod rollout_reconstruction_tests;
|
||||
|
||||
@@ -487,6 +486,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)
|
||||
@@ -512,7 +519,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 {
|
||||
@@ -537,16 +544,6 @@ impl Codex {
|
||||
let model_info = models_manager
|
||||
.get_model_info(model.as_str(), &config.to_models_manager_config())
|
||||
.await;
|
||||
let multi_agent_version =
|
||||
resolve_multi_agent_version(&model_info, &config, &session_source);
|
||||
if let SessionSource::SubAgent(SubAgentSource::ThreadSpawn { depth, .. }) = &session_source
|
||||
&& *depth >= config.agent_max_depth
|
||||
&& multi_agent_version != Some(MultiAgentVersion::V2)
|
||||
{
|
||||
let config = Arc::make_mut(&mut config);
|
||||
let _ = config.features.disable(Feature::SpawnCsv);
|
||||
let _ = config.features.disable(Feature::Collab);
|
||||
}
|
||||
let base_instructions = config
|
||||
.base_instructions
|
||||
.clone()
|
||||
@@ -1654,6 +1651,10 @@ impl Session {
|
||||
turn_context: &TurnContext,
|
||||
msg: &EventMsg,
|
||||
) {
|
||||
if turn_context.multi_agent_version != Some(MultiAgentVersion::V2) {
|
||||
return;
|
||||
}
|
||||
|
||||
if !matches!(msg, EventMsg::TurnComplete(_) | EventMsg::TurnAborted(_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -57,11 +57,6 @@ pub(super) async fn spawn_review_thread(
|
||||
ToolMode::Direct
|
||||
}
|
||||
});
|
||||
let multi_agent_version = resolve_multi_agent_version(
|
||||
&model_info,
|
||||
&per_turn_config,
|
||||
&parent_turn_context.session_source,
|
||||
);
|
||||
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!(
|
||||
@@ -112,7 +107,7 @@ pub(super) async fn spawn_review_thread(
|
||||
auth_manager: auth_manager_for_context,
|
||||
model_info: model_info.clone(),
|
||||
tool_mode,
|
||||
multi_agent_version,
|
||||
multi_agent_version: None,
|
||||
session_telemetry: session_telemetry_for_context,
|
||||
provider: provider_for_context,
|
||||
reasoning_effort,
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::*;
|
||||
use crate::SkillLoadOutcome;
|
||||
use crate::config::GhostSnapshotConfig;
|
||||
use crate::environment_selection::ResolvedTurnEnvironments;
|
||||
use crate::guardian::is_guardian_reviewer_source;
|
||||
use codex_model_provider::SharedModelProvider;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_protocol::SessionId;
|
||||
@@ -110,14 +111,27 @@ pub(crate) fn resolve_multi_agent_version(
|
||||
config: &Config,
|
||||
session_source: &SessionSource,
|
||||
) -> Option<MultiAgentVersion> {
|
||||
if is_guardian_reviewer_source(session_source)
|
||||
|| matches!(
|
||||
session_source,
|
||||
SessionSource::SubAgent(SubAgentSource::Review)
|
||||
)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
match session_source {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
agent_path: Some(_),
|
||||
..
|
||||
}) => Some(MultiAgentVersion::V2),
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
depth,
|
||||
agent_path: None,
|
||||
..
|
||||
}) if *depth < config.agent_max_depth => Some(MultiAgentVersion::V1),
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
agent_path: None, ..
|
||||
}) => Some(MultiAgentVersion::V1),
|
||||
}) => None,
|
||||
_ => model_info.multi_agent_version.or_else(|| {
|
||||
if config.features.enabled(Feature::MultiAgentV2) {
|
||||
Some(MultiAgentVersion::V2)
|
||||
|
||||
@@ -71,20 +71,11 @@ pub(crate) enum InterruptedTurnHistoryMarker {
|
||||
}
|
||||
|
||||
impl InterruptedTurnHistoryMarker {
|
||||
pub(crate) fn from_config(
|
||||
config: &Config,
|
||||
multi_agent_version: Option<MultiAgentVersion>,
|
||||
) -> Self {
|
||||
pub(crate) fn from_config(config: &Config) -> Self {
|
||||
if !config.agent_interrupt_message_enabled {
|
||||
return Self::Disabled;
|
||||
}
|
||||
if multi_agent_version.or_else(|| {
|
||||
config
|
||||
.features
|
||||
.enabled(Feature::MultiAgentV2)
|
||||
.then_some(MultiAgentVersion::V2)
|
||||
}) == Some(MultiAgentVersion::V2)
|
||||
{
|
||||
if config.features.enabled(Feature::MultiAgentV2) {
|
||||
Self::Developer
|
||||
} else {
|
||||
Self::ContextualUser
|
||||
@@ -850,11 +841,15 @@ 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_version,
|
||||
))
|
||||
&& let Some(marker) = interrupted_turn_history_marker(
|
||||
if !task.turn_context.config.agent_interrupt_message_enabled {
|
||||
InterruptedTurnHistoryMarker::Disabled
|
||||
} else if task.turn_context.multi_agent_version == Some(MultiAgentVersion::V2) {
|
||||
InterruptedTurnHistoryMarker::Developer
|
||||
} else {
|
||||
InterruptedTurnHistoryMarker::ContextualUser
|
||||
},
|
||||
)
|
||||
{
|
||||
self.record_conversation_items(
|
||||
task.turn_context.as_ref(),
|
||||
|
||||
@@ -640,10 +640,7 @@ impl ThreadManager {
|
||||
options.initial_history = fork_history_from_snapshot(
|
||||
ForkSnapshot::Interrupted,
|
||||
history,
|
||||
InterruptedTurnHistoryMarker::from_config(
|
||||
&options.config,
|
||||
/*multi_agent_version*/ None,
|
||||
),
|
||||
InterruptedTurnHistoryMarker::from_config(&options.config),
|
||||
);
|
||||
self.start_thread_with_options_and_fork_source(options, Some(forked_from_thread_id))
|
||||
.await
|
||||
@@ -898,8 +895,7 @@ impl ThreadManager {
|
||||
InitialHistory::Forked(_) => history.forked_from_id(),
|
||||
InitialHistory::New | InitialHistory::Cleared => None,
|
||||
};
|
||||
let interrupted_marker =
|
||||
InterruptedTurnHistoryMarker::from_config(&config, /*multi_agent_version*/ None);
|
||||
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(),
|
||||
|
||||
@@ -14,7 +14,6 @@ use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ReasoningItemReasoningSummary;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_protocol::openai_models::MultiAgentVersion;
|
||||
use codex_protocol::protocol::AgentMessageEvent;
|
||||
use codex_protocol::protocol::InitialHistory;
|
||||
use codex_protocol::protocol::InternalSessionSource;
|
||||
@@ -65,23 +64,6 @@ fn developer_interrupted_marker() -> ResponseItem {
|
||||
.expect("developer interrupted marker should be enabled")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn interrupted_turn_history_marker_uses_resolved_multi_agent_version() {
|
||||
let mut config = test_config().await;
|
||||
let _ = config.features.enable(Feature::MultiAgentV2);
|
||||
|
||||
assert_eq!(
|
||||
(
|
||||
InterruptedTurnHistoryMarker::from_config(&config, Some(MultiAgentVersion::V1),),
|
||||
InterruptedTurnHistoryMarker::from_config(&config, Some(MultiAgentVersion::V2),),
|
||||
),
|
||||
(
|
||||
InterruptedTurnHistoryMarker::ContextualUser,
|
||||
InterruptedTurnHistoryMarker::Developer,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncates_before_requested_user_message() {
|
||||
let items = [
|
||||
|
||||
@@ -119,13 +119,13 @@ async fn build_runner_options(
|
||||
"agent depth limit reached; this session cannot spawn more subagents".to_string(),
|
||||
));
|
||||
}
|
||||
let max_threads = turn.config.agent_max_threads;
|
||||
if 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, 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 {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::guardian::is_guardian_reviewer_source;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::tools::code_mode::execute_spec::create_code_mode_tool;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
@@ -640,20 +639,6 @@ fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut
|
||||
|
||||
fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) {
|
||||
let turn_context = context.turn_context;
|
||||
if is_guardian_reviewer_source(&turn_context.session_source)
|
||||
|| matches!(
|
||||
&turn_context.session_source,
|
||||
SessionSource::SubAgent(SubAgentSource::Review)
|
||||
)
|
||||
|| matches!(
|
||||
&turn_context.session_source,
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { depth, .. })
|
||||
if turn_context.multi_agent_version == Some(MultiAgentVersion::V1)
|
||||
&& *depth >= turn_context.config.agent_max_depth
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if turn_context.multi_agent_version.is_some() {
|
||||
if turn_context.multi_agent_version == Some(MultiAgentVersion::V2) {
|
||||
let exposure = if turn_context.config.multi_agent_v2.non_code_mode_only {
|
||||
|
||||
@@ -35,6 +35,7 @@ use serde_json::json;
|
||||
use crate::guardian::GUARDIAN_REVIEWER_NAME;
|
||||
use crate::session::tests::make_session_and_context;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::session::turn_context::resolve_multi_agent_version;
|
||||
use crate::tools::handlers::multi_agents_spec::MULTI_AGENT_V1_NAMESPACE;
|
||||
use crate::tools::router::ToolRouter;
|
||||
use crate::tools::router::ToolRouterParams;
|
||||
@@ -882,8 +883,12 @@ async fn multi_agent_version_selector_overrides_feature_flags() {
|
||||
]);
|
||||
|
||||
let review = probe(|turn| {
|
||||
turn.multi_agent_version = Some(MultiAgentVersion::V2);
|
||||
turn.session_source = SessionSource::SubAgent(SubAgentSource::Review);
|
||||
turn.multi_agent_version = resolve_multi_agent_version(
|
||||
&turn.model_info,
|
||||
turn.config.as_ref(),
|
||||
&turn.session_source,
|
||||
);
|
||||
})
|
||||
.await;
|
||||
review.assert_visible_lacks(&[
|
||||
@@ -897,9 +902,13 @@ async fn multi_agent_version_selector_overrides_feature_flags() {
|
||||
]);
|
||||
|
||||
let guardian_review = probe(|turn| {
|
||||
turn.multi_agent_version = Some(MultiAgentVersion::V2);
|
||||
turn.session_source =
|
||||
SessionSource::SubAgent(SubAgentSource::Other(GUARDIAN_REVIEWER_NAME.to_string()));
|
||||
turn.multi_agent_version = resolve_multi_agent_version(
|
||||
&turn.model_info,
|
||||
turn.config.as_ref(),
|
||||
&turn.session_source,
|
||||
);
|
||||
})
|
||||
.await;
|
||||
guardian_review.assert_visible_lacks(&[
|
||||
@@ -913,7 +922,6 @@ async fn multi_agent_version_selector_overrides_feature_flags() {
|
||||
]);
|
||||
|
||||
let max_depth_v1 = probe(|turn| {
|
||||
turn.multi_agent_version = Some(MultiAgentVersion::V1);
|
||||
turn.session_source = SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id: ThreadId::new(),
|
||||
depth: turn.config.agent_max_depth,
|
||||
@@ -921,6 +929,11 @@ async fn multi_agent_version_selector_overrides_feature_flags() {
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
});
|
||||
turn.multi_agent_version = resolve_multi_agent_version(
|
||||
&turn.model_info,
|
||||
turn.config.as_ref(),
|
||||
&turn.session_source,
|
||||
);
|
||||
})
|
||||
.await;
|
||||
max_depth_v1.assert_visible_lacks(&[MULTI_AGENT_V1_NAMESPACE]);
|
||||
|
||||
Reference in New Issue
Block a user