mirror of
https://github.com/openai/codex.git
synced 2026-11-08 16:38:30 +00:00
Add per-turn multi-agent mode (#28685)
## Why Multi-agent v2 currently carries an explicit-request-only delegation rule in its static usage hint. That provides a safe default, but it prevents clients from selecting proactive delegation per turn without changing static guidance or rewriting prior model context. This change makes delegation mode a session selection that can be updated through `turn/start`, while deriving the effective model-visible mode separately for each turn. Eligible multi-agent v2 turns remain explicit-request-only unless proactive mode is both selected and enabled. ## What changed - Add the experimental `turn/start.multiAgentMode` parameter with `explicitRequestOnly` and `proactive` values. Omission retains the loaded session's current optional selection. - Add the default-off `features.multi_agent_mode` feature gate. Eligible multi-agent v2 turns use the selected mode when enabled; an unset selection or disabled gate resolves to `explicitRequestOnly`. - Treat mode prompting as inapplicable for multi-agent v1 and other unsupported session configurations, producing no multi-agent mode developer message rather than rejecting the turn. - Move the explicit-request-only rule out of the static v2 usage hint and into a bounded, tagged developer context fragment. - Emit the effective mode in initial context and only when that effective mode changes on later turns. - Persist the effective mode in `TurnContextItem` as the durable baseline for resume and context-update comparisons. Historical rollout items are not rewritten. Later mode developer messages establish the current rule incrementally. ## Not covered - Initial selection through `thread/start` and selected-mode reporting from thread lifecycle/settings APIs; those are isolated in the stacked #28792. - A TUI control or slash command for selecting the mode. - Persisting a preferred mode to `config.toml`; selection remains session/turn scoped. - Changes to multi-agent concurrency limits, tool availability, or model catalog capability declarations. - Rewriting historical rollout prompt items. Cold resume restores the latest persisted effective mode when available while leaving historical developer messages intact. ## Verification - `CARGO_INCREMENTAL=0 just test -p codex-core multi_agent_mode` - Focused app-server coverage verifies that `turn/start.multiAgentMode` produces proactive developer instructions for an eligible v2 turn. ## Stack Followed by #28792, which adds `thread/start` initialization and lifecycle/settings observability.
This commit is contained in:
1 parent
f886e33e5a
commit
fc8c6b7384
41 files changed
+779
-8
No files matched your search
@@ -1760,6 +1760,14 @@
|
|||||||
"ModelProviderCapabilitiesReadParams": {
|
"ModelProviderCapabilitiesReadParams": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"MultiAgentMode": {
|
||||||
|
"description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.",
|
||||||
|
"enum": [
|
||||||
|
"explicitRequestOnly",
|
||||||
|
"proactive"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"NetworkAccess": {
|
"NetworkAccess": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"restricted",
|
"restricted",
|
||||||
|
|||||||
+8
@@ -12637,6 +12637,14 @@
|
|||||||
"title": "ModelVerificationNotification",
|
"title": "ModelVerificationNotification",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"MultiAgentMode": {
|
||||||
|
"description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.",
|
||||||
|
"enum": [
|
||||||
|
"explicitRequestOnly",
|
||||||
|
"proactive"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"NetworkAccess": {
|
"NetworkAccess": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"restricted",
|
"restricted",
|
||||||
|
|||||||
+8
@@ -9061,6 +9061,14 @@
|
|||||||
"title": "ModelVerificationNotification",
|
"title": "ModelVerificationNotification",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"MultiAgentMode": {
|
||||||
|
"description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.",
|
||||||
|
"enum": [
|
||||||
|
"explicitRequestOnly",
|
||||||
|
"proactive"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"NetworkAccess": {
|
"NetworkAccess": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"restricted",
|
"restricted",
|
||||||
|
|||||||
@@ -141,6 +141,14 @@
|
|||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"MultiAgentMode": {
|
||||||
|
"description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.",
|
||||||
|
"enum": [
|
||||||
|
"explicitRequestOnly",
|
||||||
|
"proactive"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"NetworkAccess": {
|
"NetworkAccess": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"restricted",
|
"restricted",
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||||
|
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controls whether the model should only spawn sub-agents after an explicit
|
||||||
|
* user request or may delegate proactively when doing so would help.
|
||||||
|
*/
|
||||||
|
export type MultiAgentMode = "explicitRequestOnly" | "proactive";
|
||||||
@@ -49,6 +49,7 @@ export type { LocalShellStatus } from "./LocalShellStatus";
|
|||||||
export type { McpServerInfo } from "./McpServerInfo";
|
export type { McpServerInfo } from "./McpServerInfo";
|
||||||
export type { MessagePhase } from "./MessagePhase";
|
export type { MessagePhase } from "./MessagePhase";
|
||||||
export type { ModeKind } from "./ModeKind";
|
export type { ModeKind } from "./ModeKind";
|
||||||
|
export type { MultiAgentMode } from "./MultiAgentMode";
|
||||||
export type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment";
|
export type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment";
|
||||||
export type { NetworkPolicyRuleAction } from "./NetworkPolicyRuleAction";
|
export type { NetworkPolicyRuleAction } from "./NetworkPolicyRuleAction";
|
||||||
export type { ParsedCommand } from "./ParsedCommand";
|
export type { ParsedCommand } from "./ParsedCommand";
|
||||||
|
|||||||
@@ -3774,6 +3774,7 @@ fn turn_start_params_preserve_explicit_null_service_tier() {
|
|||||||
summary: None,
|
summary: None,
|
||||||
output_schema: None,
|
output_schema: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
personality: None,
|
personality: None,
|
||||||
};
|
};
|
||||||
let serialized_without_override =
|
let serialized_without_override =
|
||||||
@@ -3781,6 +3782,29 @@ fn turn_start_params_preserve_explicit_null_service_tier() {
|
|||||||
assert_eq!(serialized_without_override.get("serviceTier"), None);
|
assert_eq!(serialized_without_override.get("serviceTier"), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn turn_start_params_round_trip_multi_agent_mode() {
|
||||||
|
let params: TurnStartParams = serde_json::from_value(json!({
|
||||||
|
"threadId": "thread_123",
|
||||||
|
"input": [],
|
||||||
|
"multiAgentMode": "proactive"
|
||||||
|
}))
|
||||||
|
.expect("params should deserialize");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
params.multi_agent_mode,
|
||||||
|
Some(codex_protocol::config_types::MultiAgentMode::Proactive)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
crate::experimental_api::ExperimentalApi::experimental_reason(¶ms),
|
||||||
|
Some("turn/start.multiAgentMode")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_value(params).expect("params should serialize")["multiAgentMode"],
|
||||||
|
"proactive"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn thread_settings_update_params_preserve_explicit_null_service_tier() {
|
fn thread_settings_update_params_preserve_explicit_null_service_tier() {
|
||||||
let params: ThreadSettingsUpdateParams = serde_json::from_value(json!({
|
let params: ThreadSettingsUpdateParams = serde_json::from_value(json!({
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use super::SandboxPolicy;
|
|||||||
use super::Turn;
|
use super::Turn;
|
||||||
use codex_experimental_api_macros::ExperimentalApi;
|
use codex_experimental_api_macros::ExperimentalApi;
|
||||||
use codex_protocol::config_types::CollaborationMode;
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::MultiAgentMode;
|
||||||
use codex_protocol::config_types::Personality;
|
use codex_protocol::config_types::Personality;
|
||||||
use codex_protocol::config_types::ReasoningSummary;
|
use codex_protocol::config_types::ReasoningSummary;
|
||||||
use codex_protocol::models::ImageDetail;
|
use codex_protocol::models::ImageDetail;
|
||||||
@@ -149,6 +150,12 @@ pub struct TurnStartParams {
|
|||||||
#[experimental("turn/start.collaborationMode")]
|
#[experimental("turn/start.collaborationMode")]
|
||||||
#[ts(optional = nullable)]
|
#[ts(optional = nullable)]
|
||||||
pub collaboration_mode: Option<CollaborationMode>,
|
pub collaboration_mode: Option<CollaborationMode>,
|
||||||
|
|
||||||
|
/// Controls whether multi-agent v2 delegation requires an explicit user request.
|
||||||
|
/// Omitted keeps the loaded session's current mode.
|
||||||
|
#[experimental("turn/start.multiAgentMode")]
|
||||||
|
#[ts(optional = nullable)]
|
||||||
|
pub multi_agent_mode: Option<MultiAgentMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ Example with notification opt-out:
|
|||||||
- `thread/backgroundTerminals/list` — list running background terminals for a loaded thread (experimental; requires `capabilities.experimentalApi`); returns `data` with the running terminal ids.
|
- `thread/backgroundTerminals/list` — list running background terminals for a loaded thread (experimental; requires `capabilities.experimentalApi`); returns `data` with the running terminal ids.
|
||||||
- `thread/backgroundTerminals/terminate` — terminate one running background terminal by app-server `processId` (experimental; requires `capabilities.experimentalApi`); returns whether a process was terminated.
|
- `thread/backgroundTerminals/terminate` — terminate one running background terminal by app-server `processId` (experimental; requires `capabilities.experimentalApi`); returns whether a process was terminated.
|
||||||
- `thread/rollback` — drop the last N turns from the agent’s in-memory context and persist a rollback marker in the rollout so future resumes see the pruned history; returns the updated `thread` (with `turns` populated) on success.
|
- `thread/rollback` — drop the last N turns from the agent’s in-memory context and persist a rollback marker in the rollout so future resumes see the pruned history; returns the updated `thread` (with `turns` populated) on success.
|
||||||
- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. `clientUserMessageId` is optional; when supplied, the corresponding `userMessage` item echoes it as `clientId`. Experimental `runtimeWorkspaceRoots` replaces the thread-scoped runtime workspace roots used to materialize `:workspace_roots`; paths must be absolute. Prefer experimental `permissions` profile selection by id for permission overrides; the legacy `sandboxPolicy` field is still accepted but cannot be combined with `permissions`. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode".
|
- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. `clientUserMessageId` is optional; when supplied, the corresponding `userMessage` item echoes it as `clientId`. Experimental `runtimeWorkspaceRoots` replaces the thread-scoped runtime workspace roots used to materialize `:workspace_roots`; paths must be absolute. Prefer experimental `permissions` profile selection by id for permission overrides; the legacy `sandboxPolicy` field is still accepted but cannot be combined with `permissions`. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode". Experimental `multiAgentMode` accepts `explicitRequestOnly` or `proactive`; omission keeps the loaded session's current mode. The requested mode is retained for the loaded session without rejecting unsupported configurations. Eligible multi-agent v2 turns use the requested mode when `features.multi_agent_mode` is enabled and otherwise use explicit-request-only developer instructions.
|
||||||
- `thread/inject_items` — append raw Responses API items to a loaded thread’s model-visible history without starting a user turn; returns `{}` on success.
|
- `thread/inject_items` — append raw Responses API items to a loaded thread’s model-visible history without starting a user turn; returns `{}` on success.
|
||||||
- `turn/steer` — add user input to an already in-flight regular turn without starting a new turn; returns the active `turnId` that accepted the input. `clientUserMessageId` is optional; when supplied, the corresponding `userMessage` item echoes it as `clientId`. Review and manual compaction turns reject `turn/steer`.
|
- `turn/steer` — add user input to an already in-flight regular turn without starting a new turn; returns the active `turnId` that accepted the input. `clientUserMessageId` is optional; when supplied, the corresponding `userMessage` item echoes it as `clientId`. Review and manual compaction turns reject `turn/steer`.
|
||||||
- `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`.
|
- `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`.
|
||||||
|
|||||||
@@ -673,6 +673,7 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
|
|||||||
personality: None,
|
personality: None,
|
||||||
output_schema: None,
|
output_schema: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Some(remote_trace),
|
Some(remote_trace),
|
||||||
|
|||||||
@@ -776,6 +776,7 @@ mod thread_processor_behavior_tests {
|
|||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
multi_agent_mode: Default::default(),
|
||||||
session_source: SessionSource::Cli,
|
session_source: SessionSource::Cli,
|
||||||
forked_from_thread_id: None,
|
forked_from_thread_id: None,
|
||||||
parent_thread_id: None,
|
parent_thread_id: None,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use codex_protocol::config_types::MultiAgentMode;
|
||||||
use codex_protocol::protocol::AdditionalContextEntry as CoreAdditionalContextEntry;
|
use codex_protocol::protocol::AdditionalContextEntry as CoreAdditionalContextEntry;
|
||||||
use codex_protocol::protocol::AdditionalContextKind as CoreAdditionalContextKind;
|
use codex_protocol::protocol::AdditionalContextKind as CoreAdditionalContextKind;
|
||||||
use codex_protocol::protocol::MultiAgentVersion;
|
use codex_protocol::protocol::MultiAgentVersion;
|
||||||
@@ -60,6 +61,7 @@ struct ThreadSettingsBuildParams {
|
|||||||
effort: Option<ReasoningEffort>,
|
effort: Option<ReasoningEffort>,
|
||||||
summary: Option<ReasoningSummary>,
|
summary: Option<ReasoningSummary>,
|
||||||
collaboration_mode: Option<CollaborationMode>,
|
collaboration_mode: Option<CollaborationMode>,
|
||||||
|
multi_agent_mode: Option<MultiAgentMode>,
|
||||||
personality: Option<Personality>,
|
personality: Option<Personality>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,6 +456,7 @@ impl TurnRequestProcessor {
|
|||||||
effort: params.effort,
|
effort: params.effort,
|
||||||
summary: params.summary,
|
summary: params.summary,
|
||||||
collaboration_mode: params.collaboration_mode,
|
collaboration_mode: params.collaboration_mode,
|
||||||
|
multi_agent_mode: params.multi_agent_mode,
|
||||||
personality: params.personality,
|
personality: params.personality,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -560,6 +563,7 @@ impl TurnRequestProcessor {
|
|||||||
effort,
|
effort,
|
||||||
summary,
|
summary,
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode,
|
||||||
personality,
|
personality,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
@@ -593,6 +597,7 @@ impl TurnRequestProcessor {
|
|||||||
|| effort.is_some()
|
|| effort.is_some()
|
||||||
|| summary.is_some()
|
|| summary.is_some()
|
||||||
|| collaboration_mode.is_some()
|
|| collaboration_mode.is_some()
|
||||||
|
|| multi_agent_mode.is_some()
|
||||||
|| personality.is_some();
|
|| personality.is_some();
|
||||||
|
|
||||||
let runtime_workspace_roots =
|
let runtime_workspace_roots =
|
||||||
@@ -669,6 +674,7 @@ impl TurnRequestProcessor {
|
|||||||
summary,
|
summary,
|
||||||
service_tier: service_tier.clone(),
|
service_tier: service_tier.clone(),
|
||||||
collaboration_mode: collaboration_mode.clone(),
|
collaboration_mode: collaboration_mode.clone(),
|
||||||
|
multi_agent_mode,
|
||||||
personality,
|
personality,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@@ -692,6 +698,7 @@ impl TurnRequestProcessor {
|
|||||||
summary,
|
summary,
|
||||||
service_tier,
|
service_tier,
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode,
|
||||||
personality,
|
personality,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -722,6 +729,7 @@ impl TurnRequestProcessor {
|
|||||||
effort: params.effort,
|
effort: params.effort,
|
||||||
summary: params.summary,
|
summary: params.summary,
|
||||||
collaboration_mode: params.collaboration_mode,
|
collaboration_mode: params.collaboration_mode,
|
||||||
|
multi_agent_mode: None,
|
||||||
personality: params.personality,
|
personality: params.personality,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ use codex_features::FEATURES;
|
|||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_protocol::config_types::CollaborationMode;
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
use codex_protocol::config_types::ModeKind;
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::MultiAgentMode;
|
||||||
use codex_protocol::config_types::Personality;
|
use codex_protocol::config_types::Personality;
|
||||||
use codex_protocol::config_types::ReasoningSummary;
|
use codex_protocol::config_types::ReasoningSummary;
|
||||||
use codex_protocol::config_types::Settings;
|
use codex_protocol::config_types::Settings;
|
||||||
@@ -1749,6 +1750,83 @@ async fn turn_start_accepts_personality_override_v2() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn turn_start_accepts_multi_agent_mode_v2() -> Result<()> {
|
||||||
|
skip_if_no_network!(Ok(()));
|
||||||
|
|
||||||
|
let server = responses::start_mock_server().await;
|
||||||
|
let body = responses::sse(vec![
|
||||||
|
responses::ev_response_created("resp-1"),
|
||||||
|
responses::ev_assistant_message("msg-1", "Done"),
|
||||||
|
responses::ev_completed("resp-1"),
|
||||||
|
]);
|
||||||
|
let response_mock = responses::mount_sse_once(&server, body).await;
|
||||||
|
|
||||||
|
let codex_home = TempDir::new()?;
|
||||||
|
create_config_toml(
|
||||||
|
codex_home.path(),
|
||||||
|
&server.uri(),
|
||||||
|
"never",
|
||||||
|
&BTreeMap::from([
|
||||||
|
(Feature::MultiAgentV2, true),
|
||||||
|
(Feature::MultiAgentMode, true),
|
||||||
|
]),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut mcp = TestAppServer::new(codex_home.path()).await?;
|
||||||
|
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||||
|
|
||||||
|
let thread_req = mcp
|
||||||
|
.send_thread_start_request(ThreadStartParams {
|
||||||
|
model: Some("mock-model".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
let thread_resp: JSONRPCResponse = timeout(
|
||||||
|
DEFAULT_READ_TIMEOUT,
|
||||||
|
mcp.read_stream_until_response_message(RequestId::Integer(thread_req)),
|
||||||
|
)
|
||||||
|
.await??;
|
||||||
|
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(thread_resp)?;
|
||||||
|
|
||||||
|
let turn_req = mcp
|
||||||
|
.send_turn_start_request(TurnStartParams {
|
||||||
|
thread_id: thread.id,
|
||||||
|
input: vec![V2UserInput::Text {
|
||||||
|
text: "Hello".to_string(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}],
|
||||||
|
multi_agent_mode: Some(MultiAgentMode::Proactive),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
let turn_resp: JSONRPCResponse = timeout(
|
||||||
|
DEFAULT_READ_TIMEOUT,
|
||||||
|
mcp.read_stream_until_response_message(RequestId::Integer(turn_req)),
|
||||||
|
)
|
||||||
|
.await??;
|
||||||
|
let _: TurnStartResponse = to_response(turn_resp)?;
|
||||||
|
|
||||||
|
timeout(
|
||||||
|
DEFAULT_READ_TIMEOUT,
|
||||||
|
mcp.read_stream_until_notification_message("turn/completed"),
|
||||||
|
)
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let developer_texts = response_mock
|
||||||
|
.single_request()
|
||||||
|
.message_input_texts("developer");
|
||||||
|
assert!(developer_texts.iter().any(|text| {
|
||||||
|
text.contains("<multi_agent_mode>")
|
||||||
|
&& text.contains("Proactive multi-agent delegation is active.")
|
||||||
|
}));
|
||||||
|
assert!(!developer_texts.iter().any(|text| {
|
||||||
|
text.contains("Do not spawn sub-agents unless the user explicitly asks for sub-agents")
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn turn_start_change_personality_mid_thread_v2() -> Result<()> {
|
async fn turn_start_change_personality_mid_thread_v2() -> Result<()> {
|
||||||
skip_if_no_network!(Ok(()));
|
skip_if_no_network!(Ok(()));
|
||||||
@@ -2367,6 +2445,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
|
|||||||
personality: None,
|
personality: None,
|
||||||
output_schema: None,
|
output_schema: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
timeout(
|
timeout(
|
||||||
@@ -2406,6 +2485,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
|
|||||||
personality: None,
|
personality: None,
|
||||||
output_schema: None,
|
output_schema: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
timeout(
|
timeout(
|
||||||
|
|||||||
@@ -557,6 +557,9 @@
|
|||||||
"multi_agent": {
|
"multi_agent": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"multi_agent_mode": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"multi_agent_v2": {
|
"multi_agent_v2": {
|
||||||
"$ref": "#/definitions/FeatureToml_for_MultiAgentV2ConfigToml"
|
"$ref": "#/definitions/FeatureToml_for_MultiAgentV2ConfigToml"
|
||||||
},
|
},
|
||||||
@@ -4793,6 +4796,9 @@
|
|||||||
"multi_agent": {
|
"multi_agent": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"multi_agent_mode": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"multi_agent_v2": {
|
"multi_agent_v2": {
|
||||||
"$ref": "#/definitions/FeatureToml_for_MultiAgentV2ConfigToml"
|
"$ref": "#/definitions/FeatureToml_for_MultiAgentV2ConfigToml"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use codex_otel::SessionTelemetry;
|
|||||||
use codex_protocol::ThreadId;
|
use codex_protocol::ThreadId;
|
||||||
use codex_protocol::config_types::ApprovalsReviewer;
|
use codex_protocol::config_types::ApprovalsReviewer;
|
||||||
use codex_protocol::config_types::CollaborationMode;
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::MultiAgentMode;
|
||||||
use codex_protocol::config_types::Personality;
|
use codex_protocol::config_types::Personality;
|
||||||
use codex_protocol::config_types::ReasoningSummary;
|
use codex_protocol::config_types::ReasoningSummary;
|
||||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||||
@@ -71,6 +72,7 @@ pub struct ThreadConfigSnapshot {
|
|||||||
pub reasoning_summary: Option<ReasoningSummary>,
|
pub reasoning_summary: Option<ReasoningSummary>,
|
||||||
pub personality: Option<Personality>,
|
pub personality: Option<Personality>,
|
||||||
pub collaboration_mode: CollaborationMode,
|
pub collaboration_mode: CollaborationMode,
|
||||||
|
pub multi_agent_mode: Option<MultiAgentMode>,
|
||||||
pub session_source: SessionSource,
|
pub session_source: SessionSource,
|
||||||
pub forked_from_thread_id: Option<ThreadId>,
|
pub forked_from_thread_id: Option<ThreadId>,
|
||||||
pub parent_thread_id: Option<ThreadId>,
|
pub parent_thread_id: Option<ThreadId>,
|
||||||
@@ -151,6 +153,7 @@ pub struct CodexThreadSettingsOverrides {
|
|||||||
pub summary: Option<ReasoningSummary>,
|
pub summary: Option<ReasoningSummary>,
|
||||||
pub service_tier: Option<Option<String>>,
|
pub service_tier: Option<Option<String>>,
|
||||||
pub collaboration_mode: Option<CollaborationMode>,
|
pub collaboration_mode: Option<CollaborationMode>,
|
||||||
|
pub multi_agent_mode: Option<MultiAgentMode>,
|
||||||
pub personality: Option<Personality>,
|
pub personality: Option<Personality>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,6 +374,7 @@ impl CodexThread {
|
|||||||
summary,
|
summary,
|
||||||
service_tier,
|
service_tier,
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode,
|
||||||
personality,
|
personality,
|
||||||
} = overrides;
|
} = overrides;
|
||||||
let collaboration_mode = if let Some(collaboration_mode) = collaboration_mode {
|
let collaboration_mode = if let Some(collaboration_mode) = collaboration_mode {
|
||||||
@@ -394,6 +398,7 @@ impl CodexThread {
|
|||||||
active_permission_profile,
|
active_permission_profile,
|
||||||
windows_sandbox_level,
|
windows_sandbox_level,
|
||||||
collaboration_mode: Some(collaboration_mode),
|
collaboration_mode: Some(collaboration_mode),
|
||||||
|
multi_agent_mode,
|
||||||
reasoning_summary: summary,
|
reasoning_summary: summary,
|
||||||
service_tier,
|
service_tier,
|
||||||
personality,
|
personality,
|
||||||
|
|||||||
@@ -10055,9 +10055,8 @@ max_concurrent_threads_per_session = 17
|
|||||||
|
|
||||||
let config = resolve_multi_agent_v2_config(&config_toml);
|
let config = resolve_multi_agent_v2_config(&config_toml);
|
||||||
let concurrency_guidance = "There are 17 available concurrency slots, meaning that up to 17 agents can be active at once, including you.";
|
let concurrency_guidance = "There are 17 available concurrency slots, meaning that up to 17 agents can be active at once, including you.";
|
||||||
let expected_suffix = format!(
|
let expected_suffix =
|
||||||
"{DEFAULT_MULTI_AGENT_V2_SHARED_USAGE_HINT_TEXT}\n{concurrency_guidance}\n\n{DEFAULT_MULTI_AGENT_V2_NO_SPAWN_HINT_TEXT}"
|
format!("{DEFAULT_MULTI_AGENT_V2_SHARED_USAGE_HINT_TEXT}\n{concurrency_guidance}");
|
||||||
);
|
|
||||||
assert!(
|
assert!(
|
||||||
[
|
[
|
||||||
config.root_agent_usage_hint_text,
|
config.root_agent_usage_hint_text,
|
||||||
|
|||||||
@@ -247,11 +247,9 @@ All agents share the same directory. In detail:
|
|||||||
- All agents use the same current working directory.
|
- All agents use the same current working directory.
|
||||||
- As a result, edits made by one agent are immediately visible to all other agents.
|
- As a result, edits made by one agent are immediately visible to all other agents.
|
||||||
"#;
|
"#;
|
||||||
const DEFAULT_MULTI_AGENT_V2_NO_SPAWN_HINT_TEXT: &str = "Do not spawn sub-agents unless the user explicitly asks for sub-agents, delegation, or parallel agent work.";
|
|
||||||
|
|
||||||
fn default_multi_agent_v2_usage_hint_text(usage_hint_text: &str, max_concurrency: usize) -> String {
|
fn default_multi_agent_v2_usage_hint_text(usage_hint_text: &str, max_concurrency: usize) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{usage_hint_text}\n{DEFAULT_MULTI_AGENT_V2_SHARED_USAGE_HINT_TEXT}\nThere are {max_concurrency} available concurrency slots, meaning that up to {max_concurrency} agents can be active at once, including you.\n\n{DEFAULT_MULTI_AGENT_V2_NO_SPAWN_HINT_TEXT}"
|
"{usage_hint_text}\n{DEFAULT_MULTI_AGENT_V2_SHARED_USAGE_HINT_TEXT}\nThere are {max_concurrency} available concurrency slots, meaning that up to {max_concurrency} agents can be active at once, including you."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ fn turn_context_item_filesystem_uses_workspace_roots_instead_of_cwd() {
|
|||||||
personality: None,
|
personality: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: None,
|
realtime_active: None,
|
||||||
effort: None,
|
effort: None,
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ mod legacy_apply_patch_exec_command_warning;
|
|||||||
mod legacy_model_mismatch_warning;
|
mod legacy_model_mismatch_warning;
|
||||||
mod legacy_unified_exec_process_limit_warning;
|
mod legacy_unified_exec_process_limit_warning;
|
||||||
mod model_switch_instructions;
|
mod model_switch_instructions;
|
||||||
|
mod multi_agent_mode_instructions;
|
||||||
mod network_rule_saved;
|
mod network_rule_saved;
|
||||||
mod permissions_instructions;
|
mod permissions_instructions;
|
||||||
mod personality_spec_instructions;
|
mod personality_spec_instructions;
|
||||||
@@ -59,6 +60,7 @@ pub(crate) use legacy_apply_patch_exec_command_warning::LegacyApplyPatchExecComm
|
|||||||
pub(crate) use legacy_model_mismatch_warning::LegacyModelMismatchWarning;
|
pub(crate) use legacy_model_mismatch_warning::LegacyModelMismatchWarning;
|
||||||
pub(crate) use legacy_unified_exec_process_limit_warning::LegacyUnifiedExecProcessLimitWarning;
|
pub(crate) use legacy_unified_exec_process_limit_warning::LegacyUnifiedExecProcessLimitWarning;
|
||||||
pub(crate) use model_switch_instructions::ModelSwitchInstructions;
|
pub(crate) use model_switch_instructions::ModelSwitchInstructions;
|
||||||
|
pub(crate) use multi_agent_mode_instructions::MultiAgentModeInstructions;
|
||||||
pub(crate) use network_rule_saved::NetworkRuleSaved;
|
pub(crate) use network_rule_saved::NetworkRuleSaved;
|
||||||
pub use permissions_instructions::PermissionsInstructions;
|
pub use permissions_instructions::PermissionsInstructions;
|
||||||
pub(crate) use personality_spec_instructions::PersonalitySpecInstructions;
|
pub(crate) use personality_spec_instructions::PersonalitySpecInstructions;
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
use super::ContextualUserFragment;
|
||||||
|
use codex_protocol::config_types::MultiAgentMode;
|
||||||
|
use codex_protocol::protocol::MULTI_AGENT_MODE_CLOSE_TAG;
|
||||||
|
use codex_protocol::protocol::MULTI_AGENT_MODE_OPEN_TAG;
|
||||||
|
|
||||||
|
const EXPLICIT_REQUEST_ONLY_MULTI_AGENT_MODE_TEXT: &str = "Do not spawn sub-agents unless the user explicitly asks for sub-agents, delegation, or parallel agent work.";
|
||||||
|
const PROACTIVE_MULTI_AGENT_MODE_TEXT: &str = "Proactive multi-agent delegation is active. Any earlier instruction requiring an explicit user request before spawning sub-agents no longer applies. Use sub-agents when parallel work would materially improve speed or quality. This mode remains active until a later multi-agent mode developer message changes it.";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub(crate) struct MultiAgentModeInstructions {
|
||||||
|
multi_agent_mode: MultiAgentMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MultiAgentModeInstructions {
|
||||||
|
pub(crate) fn new(multi_agent_mode: MultiAgentMode) -> Self {
|
||||||
|
Self { multi_agent_mode }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextualUserFragment for MultiAgentModeInstructions {
|
||||||
|
fn role(&self) -> &'static str {
|
||||||
|
"developer"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn markers(&self) -> (&'static str, &'static str) {
|
||||||
|
Self::type_markers()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_markers() -> (&'static str, &'static str) {
|
||||||
|
(MULTI_AGENT_MODE_OPEN_TAG, MULTI_AGENT_MODE_CLOSE_TAG)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn body(&self) -> String {
|
||||||
|
match self.multi_agent_mode {
|
||||||
|
MultiAgentMode::ExplicitRequestOnly => {
|
||||||
|
EXPLICIT_REQUEST_ONLY_MULTI_AGENT_MODE_TEXT.to_string()
|
||||||
|
}
|
||||||
|
MultiAgentMode::Proactive => PROACTIVE_MULTI_AGENT_MODE_TEXT.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -146,6 +146,7 @@ fn reference_context_item() -> TurnContextItem {
|
|||||||
personality: None,
|
personality: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: Some(false),
|
realtime_active: Some(false),
|
||||||
effort: None,
|
effort: None,
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
@@ -935,6 +936,7 @@ fn drop_last_n_user_turns_trims_context_updates_above_rolled_back_turn() {
|
|||||||
assistant_msg("turn 1 assistant"),
|
assistant_msg("turn 1 assistant"),
|
||||||
developer_msg("Generated images are saved to /tmp as /tmp/image-1.png by default."),
|
developer_msg("Generated images are saved to /tmp as /tmp/image-1.png by default."),
|
||||||
developer_msg("<collaboration_mode>ROLLED_BACK_DEV_INSTRUCTIONS</collaboration_mode>"),
|
developer_msg("<collaboration_mode>ROLLED_BACK_DEV_INSTRUCTIONS</collaboration_mode>"),
|
||||||
|
developer_msg("<multi_agent_mode>ROLLED_BACK_MULTI_AGENT_MODE</multi_agent_mode>"),
|
||||||
user_input_text_msg(
|
user_input_text_msg(
|
||||||
"<environment_context><cwd>PRETURN_CONTEXT_DIFF_CWD</cwd></environment_context>",
|
"<environment_context><cwd>PRETURN_CONTEXT_DIFF_CWD</cwd></environment_context>",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::context::CollaborationModeInstructions;
|
|||||||
use crate::context::ContextualUserFragment;
|
use crate::context::ContextualUserFragment;
|
||||||
use crate::context::EnvironmentContext;
|
use crate::context::EnvironmentContext;
|
||||||
use crate::context::ModelSwitchInstructions;
|
use crate::context::ModelSwitchInstructions;
|
||||||
|
use crate::context::MultiAgentModeInstructions;
|
||||||
use crate::context::PermissionsInstructions;
|
use crate::context::PermissionsInstructions;
|
||||||
use crate::context::PersonalitySpecInstructions;
|
use crate::context::PersonalitySpecInstructions;
|
||||||
use crate::context::RealtimeEndInstructions;
|
use crate::context::RealtimeEndInstructions;
|
||||||
@@ -12,6 +13,7 @@ use crate::session::turn_context::TurnContext;
|
|||||||
use crate::shell::Shell;
|
use crate::shell::Shell;
|
||||||
use codex_execpolicy::Policy;
|
use codex_execpolicy::Policy;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
|
use codex_protocol::config_types::MultiAgentMode;
|
||||||
use codex_protocol::config_types::Personality;
|
use codex_protocol::config_types::Personality;
|
||||||
use codex_protocol::models::ContentItem;
|
use codex_protocol::models::ContentItem;
|
||||||
use codex_protocol::models::ResponseItem;
|
use codex_protocol::models::ResponseItem;
|
||||||
@@ -95,6 +97,31 @@ fn build_collaboration_mode_update_item(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_multi_agent_mode_update_item(
|
||||||
|
previous: Option<&TurnContextItem>,
|
||||||
|
next: &TurnContext,
|
||||||
|
) -> Option<String> {
|
||||||
|
let effective_multi_agent_mode = crate::session::multi_agents::effective_multi_agent_mode(
|
||||||
|
next.multi_agent_version,
|
||||||
|
&next.config.multi_agent_v2,
|
||||||
|
&next.session_source,
|
||||||
|
next.multi_agent_mode,
|
||||||
|
next.features.enabled(Feature::MultiAgentMode),
|
||||||
|
);
|
||||||
|
let previous = previous?;
|
||||||
|
if previous.multi_agent_mode == effective_multi_agent_mode {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match effective_multi_agent_mode {
|
||||||
|
Some(multi_agent_mode) => Some(MultiAgentModeInstructions::new(multi_agent_mode).render()),
|
||||||
|
None if previous.multi_agent_mode == Some(MultiAgentMode::Proactive) => {
|
||||||
|
Some(MultiAgentModeInstructions::new(MultiAgentMode::ExplicitRequestOnly).render())
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn build_realtime_update_item(
|
pub(crate) fn build_realtime_update_item(
|
||||||
previous: Option<&TurnContextItem>,
|
previous: Option<&TurnContextItem>,
|
||||||
previous_turn_settings: Option<&PreviousTurnSettings>,
|
previous_turn_settings: Option<&PreviousTurnSettings>,
|
||||||
@@ -230,6 +257,7 @@ pub(crate) fn build_settings_update_items(
|
|||||||
build_model_instructions_update_item(previous_turn_settings, next),
|
build_model_instructions_update_item(previous_turn_settings, next),
|
||||||
build_permissions_update_item(previous, next, exec_policy),
|
build_permissions_update_item(previous, next, exec_policy),
|
||||||
build_collaboration_mode_update_item(previous, next),
|
build_collaboration_mode_update_item(previous, next),
|
||||||
|
build_multi_agent_mode_update_item(previous, next),
|
||||||
build_realtime_update_item(previous, previous_turn_settings, next),
|
build_realtime_update_item(previous, previous_turn_settings, next),
|
||||||
build_personality_update_item(previous, next, personality_feature_enabled),
|
build_personality_update_item(previous, next, personality_feature_enabled),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use codex_protocol::models::is_image_open_tag_text;
|
|||||||
use codex_protocol::models::is_local_image_close_tag_text;
|
use codex_protocol::models::is_local_image_close_tag_text;
|
||||||
use codex_protocol::models::is_local_image_open_tag_text;
|
use codex_protocol::models::is_local_image_open_tag_text;
|
||||||
use codex_protocol::protocol::COLLABORATION_MODE_OPEN_TAG;
|
use codex_protocol::protocol::COLLABORATION_MODE_OPEN_TAG;
|
||||||
|
use codex_protocol::protocol::MULTI_AGENT_MODE_OPEN_TAG;
|
||||||
use codex_protocol::protocol::REALTIME_CONVERSATION_OPEN_TAG;
|
use codex_protocol::protocol::REALTIME_CONVERSATION_OPEN_TAG;
|
||||||
use codex_protocol::protocol::SKILLS_INSTRUCTIONS_OPEN_TAG;
|
use codex_protocol::protocol::SKILLS_INSTRUCTIONS_OPEN_TAG;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
@@ -29,6 +30,7 @@ const CONTEXTUAL_DEVELOPER_PREFIXES: &[&str] = &[
|
|||||||
"<permissions instructions>",
|
"<permissions instructions>",
|
||||||
"<model_switch>",
|
"<model_switch>",
|
||||||
COLLABORATION_MODE_OPEN_TAG,
|
COLLABORATION_MODE_OPEN_TAG,
|
||||||
|
MULTI_AGENT_MODE_OPEN_TAG,
|
||||||
REALTIME_CONVERSATION_OPEN_TAG,
|
REALTIME_CONVERSATION_OPEN_TAG,
|
||||||
SKILLS_INSTRUCTIONS_OPEN_TAG,
|
SKILLS_INSTRUCTIONS_OPEN_TAG,
|
||||||
"<personality_spec>",
|
"<personality_spec>",
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ async fn thread_settings_update(
|
|||||||
summary,
|
summary,
|
||||||
service_tier,
|
service_tier,
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode,
|
||||||
personality,
|
personality,
|
||||||
} = thread_settings;
|
} = thread_settings;
|
||||||
let collaboration_mode = match collaboration_mode {
|
let collaboration_mode = match collaboration_mode {
|
||||||
@@ -149,6 +150,7 @@ async fn thread_settings_update(
|
|||||||
active_permission_profile,
|
active_permission_profile,
|
||||||
windows_sandbox_level,
|
windows_sandbox_level,
|
||||||
collaboration_mode: Some(collaboration_mode),
|
collaboration_mode: Some(collaboration_mode),
|
||||||
|
multi_agent_mode,
|
||||||
reasoning_summary: summary,
|
reasoning_summary: summary,
|
||||||
service_tier,
|
service_tier,
|
||||||
personality,
|
personality,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ use crate::context::AvailablePluginsInstructions;
|
|||||||
use crate::context::AvailableSkillsInstructions;
|
use crate::context::AvailableSkillsInstructions;
|
||||||
use crate::context::CollaborationModeInstructions;
|
use crate::context::CollaborationModeInstructions;
|
||||||
use crate::context::ContextualUserFragment;
|
use crate::context::ContextualUserFragment;
|
||||||
|
use crate::context::MultiAgentModeInstructions;
|
||||||
use crate::context::NetworkRuleSaved;
|
use crate::context::NetworkRuleSaved;
|
||||||
use crate::context::PermissionsInstructions;
|
use crate::context::PermissionsInstructions;
|
||||||
use crate::context::PersonalitySpecInstructions;
|
use crate::context::PersonalitySpecInstructions;
|
||||||
@@ -92,6 +93,7 @@ use codex_protocol::approvals::NetworkPolicyRuleAction;
|
|||||||
use codex_protocol::config_types::ApprovalsReviewer;
|
use codex_protocol::config_types::ApprovalsReviewer;
|
||||||
use codex_protocol::config_types::AutoCompactTokenLimitScope;
|
use codex_protocol::config_types::AutoCompactTokenLimitScope;
|
||||||
use codex_protocol::config_types::ModeKind;
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::MultiAgentMode;
|
||||||
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_protocol::config_types::Settings;
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::config_types::WebSearchMode;
|
use codex_protocol::config_types::WebSearchMode;
|
||||||
@@ -210,7 +212,7 @@ mod handlers;
|
|||||||
mod inject;
|
mod inject;
|
||||||
mod input_queue;
|
mod input_queue;
|
||||||
mod mcp;
|
mod mcp;
|
||||||
mod multi_agents;
|
pub(crate) mod multi_agents;
|
||||||
mod review;
|
mod review;
|
||||||
mod rollout_budget;
|
mod rollout_budget;
|
||||||
mod rollout_reconstruction;
|
mod rollout_reconstruction;
|
||||||
@@ -581,6 +583,7 @@ impl Codex {
|
|||||||
.await;
|
.await;
|
||||||
let multi_agent_version =
|
let multi_agent_version =
|
||||||
resolve_multi_agent_version(&conversation_history, inherited_multi_agent_version);
|
resolve_multi_agent_version(&conversation_history, inherited_multi_agent_version);
|
||||||
|
let multi_agent_mode = conversation_history.get_multi_agent_mode();
|
||||||
config
|
config
|
||||||
.validate_multi_agent_v2_config()
|
.validate_multi_agent_v2_config()
|
||||||
.map_err(|err| CodexErr::InvalidRequest(err.to_string()))?;
|
.map_err(|err| CodexErr::InvalidRequest(err.to_string()))?;
|
||||||
@@ -614,6 +617,7 @@ impl Codex {
|
|||||||
let session_configuration = SessionConfiguration {
|
let session_configuration = SessionConfiguration {
|
||||||
provider: config.model_provider.clone(),
|
provider: config.model_provider.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode,
|
||||||
model_reasoning_summary: config.model_reasoning_summary,
|
model_reasoning_summary: config.model_reasoning_summary,
|
||||||
service_tier,
|
service_tier,
|
||||||
developer_instructions: config.developer_instructions.clone(),
|
developer_instructions: config.developer_instructions.clone(),
|
||||||
@@ -3240,6 +3244,17 @@ impl Session {
|
|||||||
{
|
{
|
||||||
items.push(usage_hint_message);
|
items.push(usage_hint_message);
|
||||||
}
|
}
|
||||||
|
if let Some(multi_agent_mode) = multi_agents::effective_multi_agent_mode(
|
||||||
|
turn_context.multi_agent_version,
|
||||||
|
&turn_context.config.multi_agent_v2,
|
||||||
|
&session_source,
|
||||||
|
turn_context.multi_agent_mode,
|
||||||
|
turn_context.features.enabled(Feature::MultiAgentMode),
|
||||||
|
) {
|
||||||
|
items.push(ContextualUserFragment::into(
|
||||||
|
MultiAgentModeInstructions::new(multi_agent_mode),
|
||||||
|
));
|
||||||
|
}
|
||||||
if let Some(contextual_user_message) =
|
if let Some(contextual_user_message) =
|
||||||
crate::context_manager::updates::build_contextual_user_message(contextual_user_sections)
|
crate::context_manager::updates::build_contextual_user_message(contextual_user_sections)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
use crate::config::MultiAgentV2Config;
|
||||||
use crate::session::turn_context::TurnContext;
|
use crate::session::turn_context::TurnContext;
|
||||||
|
use codex_protocol::config_types::MultiAgentMode;
|
||||||
use codex_protocol::protocol::MultiAgentVersion;
|
use codex_protocol::protocol::MultiAgentVersion;
|
||||||
use codex_protocol::protocol::SessionSource;
|
use codex_protocol::protocol::SessionSource;
|
||||||
use codex_protocol::protocol::SubAgentSource;
|
use codex_protocol::protocol::SubAgentSource;
|
||||||
@@ -16,6 +18,13 @@ pub(super) fn usage_hint_text<'a>(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
match session_source {
|
||||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { .. }) => {
|
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { .. }) => {
|
||||||
multi_agent_v2.subagent_usage_hint_text.as_deref()
|
multi_agent_v2.subagent_usage_hint_text.as_deref()
|
||||||
@@ -29,3 +38,31 @@ pub(super) fn usage_hint_text<'a>(
|
|||||||
SessionSource::Internal(_) | SessionSource::SubAgent(_) => None,
|
SessionSource::Internal(_) | SessionSource::SubAgent(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn multi_agent_mode_is_applicable(
|
||||||
|
multi_agent_version: MultiAgentVersion,
|
||||||
|
multi_agent_v2: &MultiAgentV2Config,
|
||||||
|
session_source: &SessionSource,
|
||||||
|
) -> bool {
|
||||||
|
multi_agent_version == MultiAgentVersion::V2
|
||||||
|
&& multi_agent_v2.usage_hint_enabled
|
||||||
|
&& configured_usage_hint_text_for_source(multi_agent_v2, session_source).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn effective_multi_agent_mode(
|
||||||
|
multi_agent_version: MultiAgentVersion,
|
||||||
|
multi_agent_v2: &MultiAgentV2Config,
|
||||||
|
session_source: &SessionSource,
|
||||||
|
requested_multi_agent_mode: Option<MultiAgentMode>,
|
||||||
|
multi_agent_mode_enabled: bool,
|
||||||
|
) -> Option<MultiAgentMode> {
|
||||||
|
if !multi_agent_mode_is_applicable(multi_agent_version, multi_agent_v2, session_source) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(if multi_agent_mode_enabled {
|
||||||
|
requested_multi_agent_mode.unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
MultiAgentMode::ExplicitRequestOnly
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -123,6 +123,7 @@ pub(super) async fn spawn_review_thread(
|
|||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
user_instructions: None,
|
user_instructions: None,
|
||||||
collaboration_mode: parent_turn_context.collaboration_mode.clone(),
|
collaboration_mode: parent_turn_context.collaboration_mode.clone(),
|
||||||
|
multi_agent_mode: parent_turn_context.multi_agent_mode,
|
||||||
multi_agent_version: MultiAgentVersion::Disabled,
|
multi_agent_version: MultiAgentVersion::Disabled,
|
||||||
personality: parent_turn_context.personality,
|
personality: parent_turn_context.personality,
|
||||||
approval_policy: parent_turn_context.approval_policy.clone(),
|
approval_policy: parent_turn_context.approval_policy.clone(),
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
|
|||||||
personality: turn_context.personality,
|
personality: turn_context.personality,
|
||||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: Some(turn_context.realtime_active),
|
realtime_active: Some(turn_context.realtime_active),
|
||||||
effort: turn_context.reasoning_effort.clone(),
|
effort: turn_context.reasoning_effort.clone(),
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
@@ -142,6 +143,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
|
|||||||
personality: turn_context.personality,
|
personality: turn_context.personality,
|
||||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: Some(turn_context.realtime_active),
|
realtime_active: Some(turn_context.realtime_active),
|
||||||
effort: turn_context.reasoning_effort.clone(),
|
effort: turn_context.reasoning_effort.clone(),
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
@@ -1007,6 +1009,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
|||||||
personality: turn_context.personality,
|
personality: turn_context.personality,
|
||||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: Some(turn_context.realtime_active),
|
realtime_active: Some(turn_context.realtime_active),
|
||||||
effort: turn_context.reasoning_effort.clone(),
|
effort: turn_context.reasoning_effort.clone(),
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
@@ -1090,6 +1093,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
|||||||
personality: turn_context.personality,
|
personality: turn_context.personality,
|
||||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: Some(turn_context.realtime_active),
|
realtime_active: Some(turn_context.realtime_active),
|
||||||
effort: turn_context.reasoning_effort.clone(),
|
effort: turn_context.reasoning_effort.clone(),
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
@@ -1120,6 +1124,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
|
|||||||
personality: turn_context.personality,
|
personality: turn_context.personality,
|
||||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: Some(turn_context.realtime_active),
|
realtime_active: Some(turn_context.realtime_active),
|
||||||
effort: turn_context.reasoning_effort.clone(),
|
effort: turn_context.reasoning_effort.clone(),
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
@@ -1243,6 +1248,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
|
|||||||
personality: turn_context.personality,
|
personality: turn_context.personality,
|
||||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: Some(turn_context.realtime_active),
|
realtime_active: Some(turn_context.realtime_active),
|
||||||
effort: turn_context.reasoning_effort.clone(),
|
effort: turn_context.reasoning_effort.clone(),
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
@@ -1363,6 +1369,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
|
|||||||
personality: turn_context.personality,
|
personality: turn_context.personality,
|
||||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: Some(turn_context.realtime_active),
|
realtime_active: Some(turn_context.realtime_active),
|
||||||
effort: turn_context.reasoning_effort.clone(),
|
effort: turn_context.reasoning_effort.clone(),
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
@@ -1527,6 +1534,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
|
|||||||
personality: turn_context.personality,
|
personality: turn_context.personality,
|
||||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: Some(turn_context.realtime_active),
|
realtime_active: Some(turn_context.realtime_active),
|
||||||
effort: turn_context.reasoning_effort.clone(),
|
effort: turn_context.reasoning_effort.clone(),
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ pub(crate) struct SessionConfiguration {
|
|||||||
pub(super) provider: ModelProviderInfo,
|
pub(super) provider: ModelProviderInfo,
|
||||||
|
|
||||||
pub(super) collaboration_mode: CollaborationMode,
|
pub(super) collaboration_mode: CollaborationMode,
|
||||||
|
pub(super) multi_agent_mode: Option<MultiAgentMode>,
|
||||||
pub(super) model_reasoning_summary: Option<ReasoningSummaryConfig>,
|
pub(super) model_reasoning_summary: Option<ReasoningSummaryConfig>,
|
||||||
pub(super) service_tier: Option<String>,
|
pub(super) service_tier: Option<String>,
|
||||||
|
|
||||||
@@ -192,6 +193,7 @@ impl SessionConfiguration {
|
|||||||
reasoning_summary: self.model_reasoning_summary,
|
reasoning_summary: self.model_reasoning_summary,
|
||||||
personality: self.personality,
|
personality: self.personality,
|
||||||
collaboration_mode: self.collaboration_mode.clone(),
|
collaboration_mode: self.collaboration_mode.clone(),
|
||||||
|
multi_agent_mode: self.multi_agent_mode,
|
||||||
session_source: self.session_source.clone(),
|
session_source: self.session_source.clone(),
|
||||||
forked_from_thread_id: self.forked_from_thread_id,
|
forked_from_thread_id: self.forked_from_thread_id,
|
||||||
parent_thread_id: self.parent_thread_id,
|
parent_thread_id: self.parent_thread_id,
|
||||||
@@ -228,6 +230,9 @@ impl SessionConfiguration {
|
|||||||
if let Some(collaboration_mode) = updates.collaboration_mode.clone() {
|
if let Some(collaboration_mode) = updates.collaboration_mode.clone() {
|
||||||
next_configuration.collaboration_mode = collaboration_mode;
|
next_configuration.collaboration_mode = collaboration_mode;
|
||||||
}
|
}
|
||||||
|
if let Some(multi_agent_mode) = updates.multi_agent_mode {
|
||||||
|
next_configuration.multi_agent_mode = Some(multi_agent_mode);
|
||||||
|
}
|
||||||
if let Some(summary) = updates.reasoning_summary {
|
if let Some(summary) = updates.reasoning_summary {
|
||||||
next_configuration.model_reasoning_summary = Some(summary);
|
next_configuration.model_reasoning_summary = Some(summary);
|
||||||
}
|
}
|
||||||
@@ -422,6 +427,7 @@ pub(crate) struct SessionSettingsUpdate {
|
|||||||
pub(crate) active_permission_profile: Option<ActivePermissionProfile>,
|
pub(crate) active_permission_profile: Option<ActivePermissionProfile>,
|
||||||
pub(crate) windows_sandbox_level: Option<WindowsSandboxLevel>,
|
pub(crate) windows_sandbox_level: Option<WindowsSandboxLevel>,
|
||||||
pub(crate) collaboration_mode: Option<CollaborationMode>,
|
pub(crate) collaboration_mode: Option<CollaborationMode>,
|
||||||
|
pub(crate) multi_agent_mode: Option<MultiAgentMode>,
|
||||||
pub(crate) reasoning_summary: Option<ReasoningSummaryConfig>,
|
pub(crate) reasoning_summary: Option<ReasoningSummaryConfig>,
|
||||||
pub(crate) service_tier: Option<Option<String>>,
|
pub(crate) service_tier: Option<Option<String>>,
|
||||||
pub(crate) final_output_json_schema: Option<Option<Value>>,
|
pub(crate) final_output_json_schema: Option<Option<Value>>,
|
||||||
|
|||||||
@@ -2732,6 +2732,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
|
|||||||
personality: turn_context.personality,
|
personality: turn_context.personality,
|
||||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: Some(turn_context.realtime_active),
|
realtime_active: Some(turn_context.realtime_active),
|
||||||
effort: turn_context.reasoning_effort.clone(),
|
effort: turn_context.reasoning_effort.clone(),
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
@@ -3349,6 +3350,7 @@ async fn set_rate_limits_retains_previous_credits() {
|
|||||||
let session_configuration = SessionConfiguration {
|
let session_configuration = SessionConfiguration {
|
||||||
provider: config.model_provider.clone(),
|
provider: config.model_provider.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode: Default::default(),
|
||||||
model_reasoning_summary: config.model_reasoning_summary,
|
model_reasoning_summary: config.model_reasoning_summary,
|
||||||
developer_instructions: config.developer_instructions.clone(),
|
developer_instructions: config.developer_instructions.clone(),
|
||||||
loaded_agents_md: None,
|
loaded_agents_md: None,
|
||||||
@@ -3455,6 +3457,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
|||||||
let session_configuration = SessionConfiguration {
|
let session_configuration = SessionConfiguration {
|
||||||
provider: config.model_provider.clone(),
|
provider: config.model_provider.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode: Default::default(),
|
||||||
model_reasoning_summary: config.model_reasoning_summary,
|
model_reasoning_summary: config.model_reasoning_summary,
|
||||||
developer_instructions: config.developer_instructions.clone(),
|
developer_instructions: config.developer_instructions.clone(),
|
||||||
loaded_agents_md: None,
|
loaded_agents_md: None,
|
||||||
@@ -3982,6 +3985,7 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
|||||||
SessionConfiguration {
|
SessionConfiguration {
|
||||||
provider: config.model_provider.clone(),
|
provider: config.model_provider.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode: Default::default(),
|
||||||
model_reasoning_summary: config.model_reasoning_summary,
|
model_reasoning_summary: config.model_reasoning_summary,
|
||||||
developer_instructions: config.developer_instructions.clone(),
|
developer_instructions: config.developer_instructions.clone(),
|
||||||
loaded_agents_md: None,
|
loaded_agents_md: None,
|
||||||
@@ -4848,6 +4852,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_packaged_zsh() {
|
|||||||
let session_configuration = SessionConfiguration {
|
let session_configuration = SessionConfiguration {
|
||||||
provider: config.model_provider.clone(),
|
provider: config.model_provider.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode: Default::default(),
|
||||||
model_reasoning_summary: config.model_reasoning_summary,
|
model_reasoning_summary: config.model_reasoning_summary,
|
||||||
developer_instructions: config.developer_instructions.clone(),
|
developer_instructions: config.developer_instructions.clone(),
|
||||||
loaded_agents_md: None,
|
loaded_agents_md: None,
|
||||||
@@ -4960,6 +4965,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
|||||||
let session_configuration = SessionConfiguration {
|
let session_configuration = SessionConfiguration {
|
||||||
provider: config.model_provider.clone(),
|
provider: config.model_provider.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode: Default::default(),
|
||||||
model_reasoning_summary: config.model_reasoning_summary,
|
model_reasoning_summary: config.model_reasoning_summary,
|
||||||
developer_instructions: config.developer_instructions.clone(),
|
developer_instructions: config.developer_instructions.clone(),
|
||||||
loaded_agents_md: None,
|
loaded_agents_md: None,
|
||||||
@@ -5205,6 +5211,7 @@ async fn make_session_with_config_and_rx(
|
|||||||
let session_configuration = SessionConfiguration {
|
let session_configuration = SessionConfiguration {
|
||||||
provider: config.model_provider.clone(),
|
provider: config.model_provider.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode: Default::default(),
|
||||||
model_reasoning_summary: config.model_reasoning_summary,
|
model_reasoning_summary: config.model_reasoning_summary,
|
||||||
developer_instructions: config.developer_instructions.clone(),
|
developer_instructions: config.developer_instructions.clone(),
|
||||||
loaded_agents_md: None,
|
loaded_agents_md: None,
|
||||||
@@ -5311,6 +5318,7 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
|
|||||||
let session_configuration = SessionConfiguration {
|
let session_configuration = SessionConfiguration {
|
||||||
provider: config.model_provider.clone(),
|
provider: config.model_provider.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode: Default::default(),
|
||||||
model_reasoning_summary: config.model_reasoning_summary,
|
model_reasoning_summary: config.model_reasoning_summary,
|
||||||
developer_instructions: config.developer_instructions.clone(),
|
developer_instructions: config.developer_instructions.clone(),
|
||||||
loaded_agents_md: None,
|
loaded_agents_md: None,
|
||||||
@@ -7017,6 +7025,7 @@ where
|
|||||||
let session_configuration = SessionConfiguration {
|
let session_configuration = SessionConfiguration {
|
||||||
provider: config.model_provider.clone(),
|
provider: config.model_provider.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode: Default::default(),
|
||||||
model_reasoning_summary: config.model_reasoning_summary,
|
model_reasoning_summary: config.model_reasoning_summary,
|
||||||
developer_instructions: config.developer_instructions.clone(),
|
developer_instructions: config.developer_instructions.clone(),
|
||||||
loaded_agents_md: None,
|
loaded_agents_md: None,
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ pub struct TurnContext {
|
|||||||
pub(crate) developer_instructions: Option<String>,
|
pub(crate) developer_instructions: Option<String>,
|
||||||
pub(crate) user_instructions: Option<String>,
|
pub(crate) user_instructions: Option<String>,
|
||||||
pub(crate) collaboration_mode: CollaborationMode,
|
pub(crate) collaboration_mode: CollaborationMode,
|
||||||
|
pub(crate) multi_agent_mode: Option<MultiAgentMode>,
|
||||||
pub(crate) multi_agent_version: MultiAgentVersion,
|
pub(crate) multi_agent_version: MultiAgentVersion,
|
||||||
pub(crate) personality: Option<Personality>,
|
pub(crate) personality: Option<Personality>,
|
||||||
pub(crate) approval_policy: Constrained<AskForApproval>,
|
pub(crate) approval_policy: Constrained<AskForApproval>,
|
||||||
@@ -276,6 +277,7 @@ impl TurnContext {
|
|||||||
developer_instructions: self.developer_instructions.clone(),
|
developer_instructions: self.developer_instructions.clone(),
|
||||||
user_instructions: self.user_instructions.clone(),
|
user_instructions: self.user_instructions.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode: self.multi_agent_mode,
|
||||||
multi_agent_version: self.multi_agent_version,
|
multi_agent_version: self.multi_agent_version,
|
||||||
personality: self.personality,
|
personality: self.personality,
|
||||||
approval_policy: self.approval_policy.clone(),
|
approval_policy: self.approval_policy.clone(),
|
||||||
@@ -381,6 +383,13 @@ impl TurnContext {
|
|||||||
personality: self.personality,
|
personality: self.personality,
|
||||||
collaboration_mode: Some(self.collaboration_mode.clone()),
|
collaboration_mode: Some(self.collaboration_mode.clone()),
|
||||||
multi_agent_version: Some(self.multi_agent_version),
|
multi_agent_version: Some(self.multi_agent_version),
|
||||||
|
multi_agent_mode: super::multi_agents::effective_multi_agent_mode(
|
||||||
|
self.multi_agent_version,
|
||||||
|
&self.config.multi_agent_v2,
|
||||||
|
&self.session_source,
|
||||||
|
self.multi_agent_mode,
|
||||||
|
self.features.enabled(Feature::MultiAgentMode),
|
||||||
|
),
|
||||||
realtime_active: Some(self.realtime_active),
|
realtime_active: Some(self.realtime_active),
|
||||||
effort: self.reasoning_effort.clone(),
|
effort: self.reasoning_effort.clone(),
|
||||||
summary: ReasoningSummaryConfig::Auto,
|
summary: ReasoningSummaryConfig::Auto,
|
||||||
@@ -562,6 +571,7 @@ impl Session {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(LoadedAgentsMd::render),
|
.map(LoadedAgentsMd::render),
|
||||||
collaboration_mode: session_configuration.collaboration_mode.clone(),
|
collaboration_mode: session_configuration.collaboration_mode.clone(),
|
||||||
|
multi_agent_mode: session_configuration.multi_agent_mode,
|
||||||
multi_agent_version,
|
multi_agent_version,
|
||||||
personality: session_configuration.personality,
|
personality: session_configuration.personality,
|
||||||
approval_policy: session_configuration.approval_policy.clone(),
|
approval_policy: session_configuration.approval_policy.clone(),
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ mod model_switching;
|
|||||||
mod model_visible_layout;
|
mod model_visible_layout;
|
||||||
mod models_cache_ttl;
|
mod models_cache_ttl;
|
||||||
mod models_etag_responses;
|
mod models_etag_responses;
|
||||||
|
mod multi_agent_mode;
|
||||||
mod openai_file_mcp;
|
mod openai_file_mcp;
|
||||||
mod otel;
|
mod otel;
|
||||||
mod override_updates;
|
mod override_updates;
|
||||||
|
|||||||
@@ -0,0 +1,376 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use codex_features::Feature;
|
||||||
|
use codex_protocol::config_types::MultiAgentMode;
|
||||||
|
use codex_protocol::protocol::EventMsg;
|
||||||
|
use codex_protocol::protocol::MULTI_AGENT_MODE_OPEN_TAG;
|
||||||
|
use codex_protocol::protocol::Op;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
|
use codex_protocol::user_input::UserInput;
|
||||||
|
use core_test_support::responses::ev_completed;
|
||||||
|
use core_test_support::responses::ev_response_created;
|
||||||
|
use core_test_support::responses::mount_sse_once;
|
||||||
|
use core_test_support::responses::mount_sse_sequence;
|
||||||
|
use core_test_support::responses::sse;
|
||||||
|
use core_test_support::responses::start_mock_server;
|
||||||
|
use core_test_support::skip_if_no_network;
|
||||||
|
use core_test_support::test_codex::test_codex;
|
||||||
|
use core_test_support::wait_for_event;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
const NO_SPAWN_TEXT: &str = "Do not spawn sub-agents unless the user explicitly asks for sub-agents, delegation, or parallel agent work.";
|
||||||
|
const PROACTIVE_TEXT: &str = "Proactive multi-agent delegation is active.";
|
||||||
|
|
||||||
|
fn developer_texts(input: &[Value]) -> Vec<&str> {
|
||||||
|
input
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.get("role").and_then(Value::as_str) == Some("developer"))
|
||||||
|
.filter_map(|item| item.get("content")?.as_array())
|
||||||
|
.flatten()
|
||||||
|
.filter_map(|content| content.get("text")?.as_str())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_containing(texts: &[&str], target: &str) -> usize {
|
||||||
|
texts.iter().filter(|text| text.contains(target)).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn submit_turn(
|
||||||
|
codex: &codex_core::CodexThread,
|
||||||
|
prompt: &str,
|
||||||
|
mode: Option<MultiAgentMode>,
|
||||||
|
) -> Result<()> {
|
||||||
|
codex
|
||||||
|
.submit(Op::UserInput {
|
||||||
|
items: vec![UserInput::Text {
|
||||||
|
text: prompt.to_string(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}],
|
||||||
|
final_output_json_schema: None,
|
||||||
|
responsesapi_client_metadata: None,
|
||||||
|
additional_context: Default::default(),
|
||||||
|
thread_settings: ThreadSettingsOverrides {
|
||||||
|
multi_agent_mode: mode,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
wait_for_event(codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn multi_agent_mode_is_sticky_and_emits_only_on_change() -> Result<()> {
|
||||||
|
skip_if_no_network!(Ok(()));
|
||||||
|
|
||||||
|
let server = start_mock_server().await;
|
||||||
|
let responses = mount_sse_sequence(
|
||||||
|
&server,
|
||||||
|
(1..=3)
|
||||||
|
.map(|index| {
|
||||||
|
sse(vec![
|
||||||
|
ev_response_created(&format!("resp-{index}")),
|
||||||
|
ev_completed(&format!("resp-{index}")),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let test = test_codex()
|
||||||
|
.with_config(|config| {
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::MultiAgentV2)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::MultiAgentMode)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
})
|
||||||
|
.build(&server)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
submit_turn(&test.codex, "turn one", /*mode*/ None).await?;
|
||||||
|
assert_eq!(test.codex.config_snapshot().await.multi_agent_mode, None);
|
||||||
|
submit_turn(&test.codex, "turn two", Some(MultiAgentMode::Proactive)).await?;
|
||||||
|
submit_turn(&test.codex, "turn three", /*mode*/ None).await?;
|
||||||
|
|
||||||
|
let requests = responses.requests();
|
||||||
|
let inputs = requests
|
||||||
|
.iter()
|
||||||
|
.map(core_test_support::responses::ResponsesRequest::input)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let first = developer_texts(&inputs[0]);
|
||||||
|
let second = developer_texts(&inputs[1]);
|
||||||
|
let third = developer_texts(&inputs[2]);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
count_containing(&first, MULTI_AGENT_MODE_OPEN_TAG),
|
||||||
|
count_containing(&first, NO_SPAWN_TEXT),
|
||||||
|
count_containing(&first, PROACTIVE_TEXT),
|
||||||
|
),
|
||||||
|
(1, 1, 0)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
count_containing(&second, MULTI_AGENT_MODE_OPEN_TAG),
|
||||||
|
count_containing(&second, NO_SPAWN_TEXT),
|
||||||
|
count_containing(&second, PROACTIVE_TEXT),
|
||||||
|
),
|
||||||
|
(2, 1, 1)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
count_containing(&third, MULTI_AGENT_MODE_OPEN_TAG),
|
||||||
|
count_containing(&third, NO_SPAWN_TEXT),
|
||||||
|
count_containing(&third, PROACTIVE_TEXT),
|
||||||
|
),
|
||||||
|
(2, 1, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn multi_agent_mode_feature_uses_explicit_mode_when_disabled() -> Result<()> {
|
||||||
|
skip_if_no_network!(Ok(()));
|
||||||
|
|
||||||
|
let server = start_mock_server().await;
|
||||||
|
let responses = mount_sse_once(
|
||||||
|
&server,
|
||||||
|
sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let test = test_codex()
|
||||||
|
.with_config(|config| {
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::MultiAgentV2)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
})
|
||||||
|
.build(&server)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
submit_turn(&test.codex, "hello", /*mode*/ None).await?;
|
||||||
|
|
||||||
|
let input = responses.single_request().input();
|
||||||
|
let texts = developer_texts(&input);
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
count_containing(&texts, MULTI_AGENT_MODE_OPEN_TAG),
|
||||||
|
count_containing(&texts, NO_SPAWN_TEXT),
|
||||||
|
count_containing(&texts, PROACTIVE_TEXT),
|
||||||
|
),
|
||||||
|
(1, 1, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn resume_compares_against_previous_effective_multi_agent_mode() -> Result<()> {
|
||||||
|
skip_if_no_network!(Ok(()));
|
||||||
|
|
||||||
|
let server = start_mock_server().await;
|
||||||
|
let responses = mount_sse_sequence(
|
||||||
|
&server,
|
||||||
|
(1..=4)
|
||||||
|
.map(|index| {
|
||||||
|
sse(vec![
|
||||||
|
ev_response_created(&format!("resp-{index}")),
|
||||||
|
ev_completed(&format!("resp-{index}")),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let initial = test_codex()
|
||||||
|
.with_config(|config| {
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::MultiAgentV2)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
})
|
||||||
|
.build(&server)
|
||||||
|
.await?;
|
||||||
|
let home = initial.home.clone();
|
||||||
|
let rollout_path = initial
|
||||||
|
.session_configured
|
||||||
|
.rollout_path
|
||||||
|
.clone()
|
||||||
|
.expect("rollout path");
|
||||||
|
|
||||||
|
submit_turn(
|
||||||
|
&initial.codex,
|
||||||
|
"before resume",
|
||||||
|
Some(MultiAgentMode::Proactive),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
drop(initial);
|
||||||
|
|
||||||
|
let mut resume_builder = test_codex().with_config(|config| {
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::MultiAgentV2)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::MultiAgentMode)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
});
|
||||||
|
let resumed = resume_builder.resume(&server, home, rollout_path).await?;
|
||||||
|
submit_turn(
|
||||||
|
&resumed.codex,
|
||||||
|
"after resume",
|
||||||
|
Some(MultiAgentMode::Proactive),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let requests = responses.requests();
|
||||||
|
let resumed_input = requests[1].input();
|
||||||
|
let texts = developer_texts(&resumed_input);
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
count_containing(&texts, MULTI_AGENT_MODE_OPEN_TAG),
|
||||||
|
count_containing(&texts, NO_SPAWN_TEXT),
|
||||||
|
count_containing(&texts, PROACTIVE_TEXT),
|
||||||
|
),
|
||||||
|
(2, 1, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
let resumed_rollout_path = resumed
|
||||||
|
.session_configured
|
||||||
|
.rollout_path
|
||||||
|
.clone()
|
||||||
|
.expect("resumed rollout path");
|
||||||
|
let resumed_home = resumed.home.clone();
|
||||||
|
drop(resumed);
|
||||||
|
let mut same_mode_resume_builder = test_codex().with_config(|config| {
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::MultiAgentV2)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::MultiAgentMode)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
});
|
||||||
|
let resumed_same_mode = same_mode_resume_builder
|
||||||
|
.resume(&server, resumed_home, resumed_rollout_path)
|
||||||
|
.await?;
|
||||||
|
submit_turn(
|
||||||
|
&resumed_same_mode.codex,
|
||||||
|
"after same-mode resume",
|
||||||
|
/*mode*/ None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resumed_same_mode
|
||||||
|
.codex
|
||||||
|
.config_snapshot()
|
||||||
|
.await
|
||||||
|
.multi_agent_mode,
|
||||||
|
Some(MultiAgentMode::Proactive)
|
||||||
|
);
|
||||||
|
let requests = responses.requests();
|
||||||
|
let resumed_same_mode_input = requests[2].input();
|
||||||
|
let texts = developer_texts(&resumed_same_mode_input);
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
count_containing(&texts, MULTI_AGENT_MODE_OPEN_TAG),
|
||||||
|
count_containing(&texts, NO_SPAWN_TEXT),
|
||||||
|
count_containing(&texts, PROACTIVE_TEXT),
|
||||||
|
),
|
||||||
|
(2, 1, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
let resumed_same_mode_rollout_path = resumed_same_mode
|
||||||
|
.session_configured
|
||||||
|
.rollout_path
|
||||||
|
.clone()
|
||||||
|
.expect("same-mode resumed rollout path");
|
||||||
|
let resumed_same_mode_home = resumed_same_mode.home.clone();
|
||||||
|
drop(resumed_same_mode);
|
||||||
|
let mut disabled_mode_resume_builder = test_codex().with_config(|config| {
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::MultiAgentV2)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
});
|
||||||
|
let resumed_disabled_mode = disabled_mode_resume_builder
|
||||||
|
.resume(
|
||||||
|
&server,
|
||||||
|
resumed_same_mode_home,
|
||||||
|
resumed_same_mode_rollout_path,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
submit_turn(
|
||||||
|
&resumed_disabled_mode.codex,
|
||||||
|
"after disabled-mode resume",
|
||||||
|
/*mode*/ None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resumed_disabled_mode
|
||||||
|
.codex
|
||||||
|
.config_snapshot()
|
||||||
|
.await
|
||||||
|
.multi_agent_mode,
|
||||||
|
Some(MultiAgentMode::Proactive)
|
||||||
|
);
|
||||||
|
let requests = responses.requests();
|
||||||
|
let resumed_disabled_mode_input = requests[3].input();
|
||||||
|
let texts = developer_texts(&resumed_disabled_mode_input);
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
count_containing(&texts, MULTI_AGENT_MODE_OPEN_TAG),
|
||||||
|
count_containing(&texts, NO_SPAWN_TEXT),
|
||||||
|
count_containing(&texts, PROACTIVE_TEXT),
|
||||||
|
),
|
||||||
|
(3, 2, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn explicit_multi_agent_mode_is_retained_without_multi_agent_v2() -> Result<()> {
|
||||||
|
skip_if_no_network!(Ok(()));
|
||||||
|
|
||||||
|
let server = start_mock_server().await;
|
||||||
|
let responses = mount_sse_once(
|
||||||
|
&server,
|
||||||
|
sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let test = test_codex()
|
||||||
|
.with_config(|config| {
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::MultiAgentMode)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
})
|
||||||
|
.build(&server)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
submit_turn(&test.codex, "hello", Some(MultiAgentMode::Proactive)).await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
test.codex.config_snapshot().await.multi_agent_mode,
|
||||||
|
Some(MultiAgentMode::Proactive)
|
||||||
|
);
|
||||||
|
let input = responses.single_request().input();
|
||||||
|
let texts = developer_texts(&input);
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
count_containing(&texts, MULTI_AGENT_MODE_OPEN_TAG),
|
||||||
|
count_containing(&texts, PROACTIVE_TEXT),
|
||||||
|
),
|
||||||
|
(0, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@ fn resume_history(
|
|||||||
personality: None,
|
personality: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: None,
|
realtime_active: None,
|
||||||
effort: config.model_reasoning_effort.clone(),
|
effort: config.model_reasoning_effort.clone(),
|
||||||
summary: config
|
summary: config
|
||||||
|
|||||||
@@ -891,6 +891,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
|
|||||||
personality: None,
|
personality: None,
|
||||||
output_schema,
|
output_schema,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"turn/start",
|
"turn/start",
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ pub enum Feature {
|
|||||||
Collab,
|
Collab,
|
||||||
/// Enable task-path-based multi-agent routing.
|
/// Enable task-path-based multi-agent routing.
|
||||||
MultiAgentV2,
|
MultiAgentV2,
|
||||||
|
/// Enable per-turn multi-agent mode selection.
|
||||||
|
MultiAgentMode,
|
||||||
/// Enable CSV-backed agent job tools.
|
/// Enable CSV-backed agent job tools.
|
||||||
SpawnCsv,
|
SpawnCsv,
|
||||||
/// Enable apps.
|
/// Enable apps.
|
||||||
@@ -1018,6 +1020,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
|||||||
stage: Stage::UnderDevelopment,
|
stage: Stage::UnderDevelopment,
|
||||||
default_enabled: false,
|
default_enabled: false,
|
||||||
},
|
},
|
||||||
|
FeatureSpec {
|
||||||
|
id: Feature::MultiAgentMode,
|
||||||
|
key: "multi_agent_mode",
|
||||||
|
stage: Stage::UnderDevelopment,
|
||||||
|
default_enabled: false,
|
||||||
|
},
|
||||||
FeatureSpec {
|
FeatureSpec {
|
||||||
id: Feature::SpawnCsv,
|
id: Feature::SpawnCsv,
|
||||||
key: "enable_fanout",
|
key: "enable_fanout",
|
||||||
|
|||||||
@@ -296,6 +296,20 @@ pub enum Personality {
|
|||||||
Pragmatic,
|
Pragmatic,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Controls whether the model should only spawn sub-agents after an explicit
|
||||||
|
/// user request or may delegate proactively when doing so would help.
|
||||||
|
#[derive(
|
||||||
|
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default,
|
||||||
|
)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(rename_all = "camelCase")]
|
||||||
|
#[strum(serialize_all = "camelCase")]
|
||||||
|
pub enum MultiAgentMode {
|
||||||
|
#[default]
|
||||||
|
ExplicitRequestOnly,
|
||||||
|
Proactive,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default,
|
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default,
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use crate::approvals::ElicitationRequestEvent;
|
|||||||
use crate::config_types::ApprovalsReviewer;
|
use crate::config_types::ApprovalsReviewer;
|
||||||
use crate::config_types::CollaborationMode;
|
use crate::config_types::CollaborationMode;
|
||||||
use crate::config_types::ModeKind;
|
use crate::config_types::ModeKind;
|
||||||
|
use crate::config_types::MultiAgentMode;
|
||||||
use crate::config_types::Personality;
|
use crate::config_types::Personality;
|
||||||
use crate::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
use crate::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
||||||
use crate::config_types::WindowsSandboxLevel;
|
use crate::config_types::WindowsSandboxLevel;
|
||||||
@@ -104,6 +105,8 @@ pub const PLUGINS_INSTRUCTIONS_OPEN_TAG: &str = "<plugins_instructions>";
|
|||||||
pub const PLUGINS_INSTRUCTIONS_CLOSE_TAG: &str = "</plugins_instructions>";
|
pub const PLUGINS_INSTRUCTIONS_CLOSE_TAG: &str = "</plugins_instructions>";
|
||||||
pub const COLLABORATION_MODE_OPEN_TAG: &str = "<collaboration_mode>";
|
pub const COLLABORATION_MODE_OPEN_TAG: &str = "<collaboration_mode>";
|
||||||
pub const COLLABORATION_MODE_CLOSE_TAG: &str = "</collaboration_mode>";
|
pub const COLLABORATION_MODE_CLOSE_TAG: &str = "</collaboration_mode>";
|
||||||
|
pub const MULTI_AGENT_MODE_OPEN_TAG: &str = "<multi_agent_mode>";
|
||||||
|
pub const MULTI_AGENT_MODE_CLOSE_TAG: &str = "</multi_agent_mode>";
|
||||||
pub const REALTIME_CONVERSATION_OPEN_TAG: &str = "<realtime_conversation>";
|
pub const REALTIME_CONVERSATION_OPEN_TAG: &str = "<realtime_conversation>";
|
||||||
pub const REALTIME_CONVERSATION_CLOSE_TAG: &str = "</realtime_conversation>";
|
pub const REALTIME_CONVERSATION_CLOSE_TAG: &str = "</realtime_conversation>";
|
||||||
pub const USER_MESSAGE_BEGIN: &str = "## My request for Codex:";
|
pub const USER_MESSAGE_BEGIN: &str = "## My request for Codex:";
|
||||||
@@ -479,6 +482,9 @@ pub struct ThreadSettingsOverrides {
|
|||||||
/// Takes precedence over model, effort, and developer instructions if set.
|
/// Takes precedence over model, effort, and developer instructions if set.
|
||||||
pub collaboration_mode: Option<CollaborationMode>,
|
pub collaboration_mode: Option<CollaborationMode>,
|
||||||
|
|
||||||
|
/// Updated multi-agent mode for this turn and subsequent turns.
|
||||||
|
pub multi_agent_mode: Option<MultiAgentMode>,
|
||||||
|
|
||||||
/// Updated personality preference.
|
/// Updated personality preference.
|
||||||
pub personality: Option<Personality>,
|
pub personality: Option<Personality>,
|
||||||
}
|
}
|
||||||
@@ -2548,6 +2554,14 @@ impl InitialHistory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_multi_agent_mode(&self) -> Option<MultiAgentMode> {
|
||||||
|
match self {
|
||||||
|
InitialHistory::New | InitialHistory::Cleared => None,
|
||||||
|
InitialHistory::Resumed(resumed) => multi_agent_mode_from_items(&resumed.history),
|
||||||
|
InitialHistory::Forked(items) => multi_agent_mode_from_items(items),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_resumed_session_sources(&self) -> Option<(SessionSource, Option<ThreadSource>)> {
|
pub fn get_resumed_session_sources(&self) -> Option<(SessionSource, Option<ThreadSource>)> {
|
||||||
let meta = self.get_resumed_session_meta()?;
|
let meta = self.get_resumed_session_meta()?;
|
||||||
Some((meta.source.clone(), meta.thread_source.clone()))
|
Some((meta.source.clone(), meta.thread_source.clone()))
|
||||||
@@ -2860,6 +2874,17 @@ fn multi_agent_version_from_items(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn multi_agent_mode_from_items(items: &[RolloutItem]) -> Option<MultiAgentMode> {
|
||||||
|
items.iter().rev().find_map(|item| match item {
|
||||||
|
RolloutItem::TurnContext(turn_context) => turn_context.multi_agent_mode,
|
||||||
|
RolloutItem::SessionMeta(_)
|
||||||
|
| RolloutItem::ResponseItem(_)
|
||||||
|
| RolloutItem::InterAgentCommunication(_)
|
||||||
|
| RolloutItem::Compacted(_)
|
||||||
|
| RolloutItem::EventMsg(_) => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, JsonSchema, TS)]
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, JsonSchema, TS)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
#[ts(rename_all = "snake_case")]
|
#[ts(rename_all = "snake_case")]
|
||||||
@@ -3027,6 +3052,9 @@ pub struct TurnContextItem {
|
|||||||
pub collaboration_mode: Option<CollaborationMode>,
|
pub collaboration_mode: Option<CollaborationMode>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub multi_agent_version: Option<MultiAgentVersion>,
|
pub multi_agent_version: Option<MultiAgentVersion>,
|
||||||
|
/// Effective model-visible mode used as the durable context-diff baseline.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub multi_agent_mode: Option<MultiAgentMode>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub realtime_active: Option<bool>,
|
pub realtime_active: Option<bool>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -5399,6 +5427,7 @@ mod tests {
|
|||||||
personality: None,
|
personality: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: None,
|
realtime_active: None,
|
||||||
effort: None,
|
effort: None,
|
||||||
summary: ReasoningSummaryConfig::Auto,
|
summary: ReasoningSummaryConfig::Auto,
|
||||||
|
|||||||
@@ -1152,6 +1152,7 @@ async fn resume_candidate_matches_cwd_reads_latest_turn_context() -> std::io::Re
|
|||||||
personality: None,
|
personality: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: None,
|
realtime_active: None,
|
||||||
effort: None,
|
effort: None,
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
|
|||||||
@@ -368,6 +368,7 @@ mod tests {
|
|||||||
personality: None,
|
personality: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: None,
|
realtime_active: None,
|
||||||
effort: None,
|
effort: None,
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
@@ -412,6 +413,7 @@ mod tests {
|
|||||||
personality: None,
|
personality: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: None,
|
realtime_active: None,
|
||||||
effort: None,
|
effort: None,
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
@@ -452,6 +454,7 @@ mod tests {
|
|||||||
personality: None,
|
personality: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: None,
|
realtime_active: None,
|
||||||
effort: Some(ReasoningEffort::High),
|
effort: Some(ReasoningEffort::High),
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
@@ -489,6 +492,7 @@ mod tests {
|
|||||||
personality: None,
|
personality: None,
|
||||||
collaboration_mode: None,
|
collaboration_mode: None,
|
||||||
multi_agent_version: None,
|
multi_agent_version: None,
|
||||||
|
multi_agent_mode: None,
|
||||||
realtime_active: None,
|
realtime_active: None,
|
||||||
effort: Some(ReasoningEffort::High),
|
effort: Some(ReasoningEffort::High),
|
||||||
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
summary: codex_protocol::config_types::ReasoningSummary::Auto,
|
||||||
|
|||||||
@@ -786,6 +786,7 @@ impl AppServerSession {
|
|||||||
personality,
|
personality,
|
||||||
output_schema,
|
output_schema,
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
|
multi_agent_mode: None,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|||||||
Reference in new issue
Block a user