Files
codex/codex-rs/core/src/session/multi_agents.rs
Shijie Rao da4c8ca57d [codex] Add configurable multi-agent mode hint text (#30493)
## 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.
2026-07-02 18:44:34 -07:00

69 lines
2.4 KiB
Rust

use crate::config::MultiAgentV2Config;
use crate::session::turn_context::TurnContext;
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;
pub(super) fn usage_hint_text<'a>(
turn_context: &'a TurnContext,
session_source: &SessionSource,
) -> Option<&'a str> {
if turn_context.multi_agent_version != MultiAgentVersion::V2 {
return None;
}
let multi_agent_v2 = &turn_context.config.multi_agent_v2;
configured_usage_hint_text_for_source(multi_agent_v2, session_source)
}
fn configured_usage_hint_text_for_source<'a>(
multi_agent_v2: &'a MultiAgentV2Config,
session_source: &SessionSource,
) -> Option<&'a str> {
match session_source {
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { .. }) => {
multi_agent_v2.subagent_usage_hint_text.as_deref()
}
SessionSource::Cli
| SessionSource::VSCode
| SessionSource::Exec
| SessionSource::Mcp
| SessionSource::Custom(_)
| SessionSource::Unknown => multi_agent_v2.root_agent_usage_hint_text.as_deref(),
SessionSource::Internal(_) | SessionSource::SubAgent(_) => None,
}
}
pub(crate) fn effective_multi_agent_mode(turn_context: &TurnContext) -> Option<MultiAgentMode> {
if turn_context.multi_agent_version != MultiAgentVersion::V2 {
return None;
}
// A configured hint, including an empty string, defines a custom policy instead of an
// effort-derived built-in policy.
let multi_agent_mode = match &turn_context
.config
.multi_agent_v2
.multi_agent_mode_hint_text
{
Some(hint_text) => MultiAgentMode::Custom(hint_text.clone()),
None => match turn_context.effective_reasoning_effort() {
Some(ReasoningEffort::Ultra) => MultiAgentMode::Proactive,
_ => MultiAgentMode::ExplicitRequestOnly,
},
};
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,
}
}