mirror of
https://github.com/openai/codex.git
synced 2026-11-14 21:40:39 +00:00
## What changed - When `multi_agent_v2.disable_native_tools` is enabled, use bundled mode policies instead of model catalog guidance, while preserving explicitly configured mode hints. - Refer to multi-agent tools and instruct the model to read the full multi-agent skill before calling them. Keep proactive delegation enabled for `Ultra` reasoning and explicit-request-only delegation for other reasoning levels. - Reset retained nonempty custom mode hints to explicit-request-only guidance when multi-agent support is disabled, including after a cold resume. ## Testing Extend integration tests and request snapshots to cover hosted-tool guidance, configured hint precedence, proactive mode with `Ultra` reasoning, and the mode reset after a cold resume. GitOrigin-RevId: 403791ef76f990e385d9c155d9c2edc9acfcd4fe
139 lines
5.4 KiB
Rust
139 lines
5.4 KiB
Rust
use crate::agent::types::ResolvedMultiAgentV2UsageHints;
|
|
use crate::config::MultiAgentV2Config;
|
|
use crate::context::MultiAgentRoleInstructions;
|
|
use crate::session::step_context::StepContext;
|
|
use codex_prompts::ResolvedMessage;
|
|
use codex_prompts::ResolvedModelMessages;
|
|
use codex_prompts::ResolvedMultiAgentMessages;
|
|
use codex_protocol::config_types::MultiAgentMode;
|
|
use codex_protocol::openai_models::ReasoningEffort;
|
|
use codex_protocol::protocol::MultiAgentVersion;
|
|
use codex_protocol::protocol::SessionSource;
|
|
use codex_protocol::protocol::SubAgentSource;
|
|
|
|
const HOSTED_MULTI_AGENT_SKILL_GUIDANCE: &str = "Before calling any multi-agent tool, read the full multi-agent skill using its listed package locator. Skip the read only if the full skill text is already in your current context. If the read fails, make note of the blocker.";
|
|
|
|
pub(super) fn usage_hint_text(step_context: &StepContext) -> Option<MultiAgentRoleInstructions> {
|
|
let turn_context = step_context.turn.as_ref();
|
|
if turn_context.multi_agent_version != MultiAgentVersion::V2 {
|
|
return None;
|
|
}
|
|
|
|
let multi_agent_messages =
|
|
ResolvedModelMessages::from_model(&step_context.settings.model_info).multi_agent();
|
|
let snapshot = resolve_usage_hints(
|
|
&turn_context.config.multi_agent_v2,
|
|
multi_agent_messages,
|
|
!turn_context.config.update_plan_enabled && turn_context.config.model_catalog.is_none(),
|
|
);
|
|
match &turn_context.session_source {
|
|
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { .. }) => snapshot.subagent,
|
|
SessionSource::Cli
|
|
| SessionSource::VSCode
|
|
| SessionSource::Exec
|
|
| SessionSource::Mcp
|
|
| SessionSource::Custom(_)
|
|
| SessionSource::Unknown => snapshot.root,
|
|
SessionSource::Internal(_) | SessionSource::SubAgent(_) => None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn resolve_usage_hints(
|
|
config: &MultiAgentV2Config,
|
|
multi_agent_messages: ResolvedMultiAgentMessages<'_>,
|
|
omit_update_plan_instructions: bool,
|
|
) -> ResolvedMultiAgentV2UsageHints {
|
|
let resolve_role = |configured: Option<&str>, message: ResolvedMessage<'_>| {
|
|
// Configured roles take precedence; empty configured or catalog roles suppress fallback.
|
|
if let Some(configured) = configured {
|
|
return (!configured.is_empty())
|
|
.then(|| MultiAgentRoleInstructions::Configured(configured.to_owned()));
|
|
}
|
|
|
|
let base = message.text();
|
|
if base.is_empty() {
|
|
return None;
|
|
}
|
|
if config.disable_native_tools {
|
|
return None;
|
|
}
|
|
Some(MultiAgentRoleInstructions::Composed {
|
|
base: base.to_owned(),
|
|
marked: message.catalog_override().is_some(),
|
|
omit_update_plan_instructions,
|
|
max_concurrency: config.max_concurrent_threads_per_session,
|
|
wait_agent_enabled: config.wait_agent_enabled,
|
|
expose_model_overrides: config.expose_spawn_agent_model_overrides,
|
|
})
|
|
};
|
|
|
|
ResolvedMultiAgentV2UsageHints {
|
|
root: resolve_role(
|
|
config.root_agent_usage_hint_text.as_deref(),
|
|
multi_agent_messages.root,
|
|
),
|
|
subagent: resolve_role(
|
|
config.subagent_usage_hint_text.as_deref(),
|
|
multi_agent_messages.subagent,
|
|
),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn effective_multi_agent_mode(step_context: &StepContext) -> Option<MultiAgentMode> {
|
|
let turn_context = step_context.turn.as_ref();
|
|
let settings = &step_context.settings;
|
|
if turn_context.multi_agent_version != MultiAgentVersion::V2 {
|
|
return None;
|
|
}
|
|
|
|
let config = &turn_context.config.multi_agent_v2;
|
|
let model_messages = if config.disable_native_tools {
|
|
ResolvedModelMessages::bundled()
|
|
} else {
|
|
ResolvedModelMessages::from_model(&settings.model_info)
|
|
};
|
|
let multi_agent_messages = model_messages.multi_agent();
|
|
let hint = config
|
|
.multi_agent_mode_hint_text
|
|
.as_deref()
|
|
.or(multi_agent_messages.hint);
|
|
let multi_agent_mode = match hint {
|
|
Some(text) => MultiAgentMode::Custom(text.to_owned()),
|
|
None => {
|
|
let (message, builtin) =
|
|
if settings.effective_reasoning_effort() == Some(ReasoningEffort::Ultra) {
|
|
(multi_agent_messages.proactive, MultiAgentMode::Proactive)
|
|
} else {
|
|
(
|
|
multi_agent_messages.explicit,
|
|
MultiAgentMode::ExplicitRequestOnly,
|
|
)
|
|
};
|
|
if config.disable_native_tools {
|
|
let policy = message
|
|
.text()
|
|
.replace("collaboration tools", "multi-agent tools");
|
|
MultiAgentMode::Custom(format!(
|
|
"\n{policy}\n\n{HOSTED_MULTI_AGENT_SKILL_GUIDANCE}\n"
|
|
))
|
|
} else {
|
|
match message {
|
|
ResolvedMessage::Catalog(text) => MultiAgentMode::Custom(text.to_owned()),
|
|
ResolvedMessage::Bundled(_) => builtin,
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
match &turn_context.session_source {
|
|
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { .. })
|
|
| SessionSource::Cli
|
|
| SessionSource::VSCode
|
|
| SessionSource::Exec
|
|
| SessionSource::Mcp
|
|
| SessionSource::Custom(_)
|
|
| SessionSource::Unknown => Some(multi_agent_mode),
|
|
SessionSource::Internal(_) | SessionSource::SubAgent(_) => None,
|
|
}
|
|
}
|