mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
## Why
Multi-agent V2 normally derives its mode instructions from reasoning
effort: Ultra enables proactive delegation, while other efforts require
an explicit request. Some deployments need to provide one configured
delegation policy that replaces those built-ins and remains stable when
reasoning effort changes.
## What changed
- Add `features.multi_agent_v2.multi_agent_mode_hint_text` alongside the
existing root and subagent hint settings.
- Treat any configured value, including an empty string, as
`MultiAgentMode::Custom(hint_text)`, so the configured text replaces the
built-in explicit-only and proactive policies.
- Persist the full custom variant and hint text in the turn-context
snapshot, so the durable comparison baseline detects both
reasoning-effort changes and configured policy-text changes.
- Preserve the existing explicit-only/proactive behavior when the
setting is absent.
- Replace the ambiguous `MultiAgentMode::None` variant with
`MultiAgentMode::Custom(String)` in new rollouts and API schemas. A
compatibility wire type maps legacy serialized `none` values to
`Custom("")` when resuming existing rollouts.
- Regenerate the config and app-server schemas.
## Configuration examples
The distinction is whether `multi_agent_mode_hint_text` is present. An
empty string is still a configured value and intentionally suppresses
the built-in mode instructions.
### Unset: preserve existing effort-derived behavior
```toml
[features.multi_agent_v2]
enabled = true
# multi_agent_mode_hint_text is omitted
```
- Ultra reasoning uses the built-in proactive delegation instructions.
- Other reasoning efforts use the built-in explicit-request-only
instructions.
### Empty: suppress all mode hint text
```toml
[features.multi_agent_v2]
enabled = true
multi_agent_mode_hint_text = ""
```
This selects effective mode `custom` at every reasoning effort and
injects an empty mode body, suppressing both built-in policies.
### Set: always use the configured text
```toml
[features.multi_agent_v2]
enabled = true
multi_agent_mode_hint_text = "Delegate to subagents when it will materially improve the result."
```
This selects effective mode `custom` at every reasoning effort and
injects the configured text verbatim instead of either built-in policy.
## Verification
- `just test -p codex-core multi_agent_mode`
- Covers a configured hint across High and Ultra reasoning efforts and
verifies the full custom hint is recorded for both turns.
- Covers an empty-string override suppressing both built-in instruction
bodies.
- `just test -p codex-protocol -p codex-app-server-protocol`
- Covers legacy `none` turn-context deserialization as `Custom("")` and
verifies the regenerated schemas.
251 lines
9.1 KiB
Rust
251 lines
9.1 KiB
Rust
use crate::FeatureConfig;
|
|
use schemars::JsonSchema;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use std::collections::BTreeMap;
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct CodeModeConfigToml {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub enabled: Option<bool>,
|
|
/// Exact tool namespaces to omit from the code-mode nested tool surface.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub excluded_tool_namespaces: Option<Vec<String>>,
|
|
/// Exact tool namespaces to expose only as direct model tools.
|
|
/// These tools bypass deferral, remain top-level in code-mode-only sessions, and are omitted
|
|
/// from the nested code-mode tool surface.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub direct_only_tool_namespaces: Option<Vec<String>>,
|
|
}
|
|
|
|
impl FeatureConfig for CodeModeConfigToml {
|
|
fn enabled(&self) -> Option<bool> {
|
|
self.enabled
|
|
}
|
|
|
|
fn set_enabled(&mut self, enabled: bool) {
|
|
self.enabled = Some(enabled);
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct MultiAgentV2ConfigToml {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub enabled: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[schemars(range(min = 1))]
|
|
pub max_concurrent_threads_per_session: Option<usize>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[schemars(range(min = 0, max = 3600000))]
|
|
pub min_wait_timeout_ms: Option<i64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[schemars(range(min = 0, max = 3600000))]
|
|
pub max_wait_timeout_ms: Option<i64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[schemars(range(min = 0, max = 3600000))]
|
|
pub default_wait_timeout_ms: Option<i64>,
|
|
/// Deprecated compatibility field. Its value is ignored.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub usage_hint_enabled: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub usage_hint_text: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub root_agent_usage_hint_text: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub subagent_usage_hint_text: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub multi_agent_mode_hint_text: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[schemars(length(min = 1, max = 64), regex(pattern = r"^[a-zA-Z0-9_-]+$"))]
|
|
pub tool_namespace: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub hide_spawn_agent_metadata: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub non_code_mode_only: Option<bool>,
|
|
}
|
|
|
|
impl FeatureConfig for MultiAgentV2ConfigToml {
|
|
fn enabled(&self) -> Option<bool> {
|
|
self.enabled
|
|
}
|
|
|
|
fn set_enabled(&mut self, enabled: bool) {
|
|
self.enabled = Some(enabled);
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct TokenBudgetConfigToml {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub enabled: Option<bool>,
|
|
/// Number of tokens remaining before auto-compaction when the wrap-up reminder is emitted.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[schemars(range(min = 1))]
|
|
pub reminder_threshold_tokens: Option<i64>,
|
|
/// Reminder template. `{n_remaining}` is replaced with the tokens remaining before
|
|
/// auto-compaction.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[schemars(length(min = 1, max = 2000))]
|
|
pub reminder_message_template: Option<String>,
|
|
/// Guidance appended to the context-window metadata in a developer message.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[schemars(length(max = 2000))]
|
|
pub guidance_message: Option<String>,
|
|
}
|
|
|
|
impl FeatureConfig for TokenBudgetConfigToml {
|
|
fn enabled(&self) -> Option<bool> {
|
|
self.enabled
|
|
}
|
|
|
|
fn set_enabled(&mut self, enabled: bool) {
|
|
self.enabled = Some(enabled);
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct RolloutBudgetConfigToml {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub enabled: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[schemars(range(min = 1))]
|
|
pub limit_tokens: Option<i64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
/// Remaining weighted-token values that trigger reminders when crossed.
|
|
pub reminder_at_remaining_tokens: Option<Vec<i64>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[schemars(range(min = 0.0))]
|
|
pub sampling_token_weight: Option<f64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[schemars(range(min = 0.0))]
|
|
pub prefill_token_weight: Option<f64>,
|
|
}
|
|
|
|
impl FeatureConfig for RolloutBudgetConfigToml {
|
|
fn enabled(&self) -> Option<bool> {
|
|
self.enabled
|
|
}
|
|
|
|
fn set_enabled(&mut self, enabled: bool) {
|
|
self.enabled = Some(enabled);
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq, JsonSchema)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum CurrentTimeSource {
|
|
#[default]
|
|
System,
|
|
External,
|
|
}
|
|
|
|
/// Which inference boundaries may receive current-time reminders.
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq, JsonSchema)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum CurrentTimeReminderDeliveryMode {
|
|
/// Allow a reminder before any inference request once the interval is due.
|
|
#[default]
|
|
AnyInference,
|
|
/// Allow reminders after user input or tool output; new context windows still force one.
|
|
AfterUserOrToolOutput,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct CurrentTimeReminderConfigToml {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub enabled: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub reminder_interval_seconds: Option<u64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub clock_source: Option<CurrentTimeSource>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub delivery_mode: Option<CurrentTimeReminderDeliveryMode>,
|
|
/// Expose the input-interruptible `clock.sleep` tool.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub sleep_tool: Option<bool>,
|
|
}
|
|
|
|
impl FeatureConfig for CurrentTimeReminderConfigToml {
|
|
fn enabled(&self) -> Option<bool> {
|
|
self.enabled
|
|
}
|
|
|
|
fn set_enabled(&mut self, enabled: bool) {
|
|
self.enabled = Some(enabled);
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub(crate) struct RemovedAppsMcpPathOverrideConfigToml {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
enabled: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
path: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct NetworkProxyConfigToml {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub enabled: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub proxy_url: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub enable_socks5: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub socks_url: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub enable_socks5_udp: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub allow_upstream_proxy: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub dangerously_allow_non_loopback_proxy: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub dangerously_allow_all_unix_sockets: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub mode: Option<NetworkProxyModeToml>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub domains: Option<BTreeMap<String, NetworkProxyDomainPermissionToml>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub unix_sockets: Option<BTreeMap<String, NetworkProxyUnixSocketPermissionToml>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub allow_local_binding: Option<bool>,
|
|
}
|
|
|
|
impl FeatureConfig for NetworkProxyConfigToml {
|
|
fn enabled(&self) -> Option<bool> {
|
|
self.enabled
|
|
}
|
|
|
|
fn set_enabled(&mut self, enabled: bool) {
|
|
self.enabled = Some(enabled);
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum NetworkProxyModeToml {
|
|
Limited,
|
|
Full,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum NetworkProxyDomainPermissionToml {
|
|
Allow,
|
|
Deny,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum NetworkProxyUnixSocketPermissionToml {
|
|
Allow,
|
|
Deny,
|
|
}
|