Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions codex-rs/analytics/src/analytics_client_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ fn sample_thread_start_response(
sandbox: AppServerSandboxPolicy::DangerFullAccess,
active_permission_profile: None,
reasoning_effort: None,
multi_agent_mode: Default::default(),
})
}

Expand Down Expand Up @@ -288,6 +289,7 @@ fn sample_thread_resume_response_with_source(
sandbox: AppServerSandboxPolicy::DangerFullAccess,
active_permission_profile: None,
reasoning_effort: None,
multi_agent_mode: Default::default(),
initial_turns_page: None,
})
}
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/analytics/src/client_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ fn sample_thread_start_response() -> ClientResponsePayload {
sandbox: AppServerSandboxPolicy::DangerFullAccess,
active_permission_profile: None,
reasoning_effort: None,
multi_agent_mode: Default::default(),
})
}

Expand All @@ -331,6 +332,7 @@ fn sample_thread_resume_response() -> ClientResponsePayload {
sandbox: AppServerSandboxPolicy::DangerFullAccess,
active_permission_profile: None,
reasoning_effort: None,
multi_agent_mode: Default::default(),
initial_turns_page: None,
})
}
Expand All @@ -349,6 +351,7 @@ fn sample_thread_fork_response() -> ClientResponsePayload {
sandbox: AppServerSandboxPolicy::DangerFullAccess,
active_permission_profile: None,
reasoning_effort: None,
multi_agent_mode: Default::default(),
})
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion codex-rs/app-server-protocol/src/protocol/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2583,6 +2583,7 @@ mod tests {
sandbox: v2::SandboxPolicy::DangerFullAccess,
active_permission_profile: None,
reasoning_effort: None,
multi_agent_mode: None,
},
};

Expand Down Expand Up @@ -2630,7 +2631,8 @@ mod tests {
"type": "dangerFullAccess"
},
"activePermissionProfile": null,
"reasoningEffort": null
"reasoningEffort": null,
"multiAgentMode": null
}
}),
serde_json::to_value(&response)?,
Expand Down Expand Up @@ -3545,6 +3547,7 @@ mod tests {
developer_instructions: None,
},
},
multi_agent_mode: Default::default(),
personality: None,
},
});
Expand Down
30 changes: 30 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/v2/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ fn thread_resume_response_round_trips_initial_turns_page() {
sandbox: SandboxPolicy::DangerFullAccess,
active_permission_profile: None,
reasoning_effort: None,
multi_agent_mode: Default::default(),
initial_turns_page: Some(TurnsPage {
data: Vec::new(),
next_cursor: Some("cursor_next".to_string()),
Expand Down Expand Up @@ -3701,6 +3702,14 @@ fn thread_lifecycle_responses_default_missing_optional_fields() {
assert_eq!(resume.active_permission_profile, None);
assert_eq!(resume.initial_turns_page, None);
assert_eq!(fork.active_permission_profile, None);
assert_eq!(
(
start.multi_agent_mode,
resume.multi_agent_mode,
fork.multi_agent_mode,
),
(None, None, None)
);

let foreign_source: LegacyAppPathString =
serde_json::from_value(json!(r"C:\workspace\AGENTS.md")).expect("foreign source");
Expand Down Expand Up @@ -3805,6 +3814,27 @@ fn turn_start_params_round_trip_multi_agent_mode() {
);
}

#[test]
fn thread_start_params_round_trip_multi_agent_mode() {
let params: ThreadStartParams = serde_json::from_value(json!({
"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(&params),
Some("thread/start.multiAgentMode")
);
assert_eq!(
serde_json::to_value(params).expect("params should serialize")["multiAgentMode"],
"proactive"
);
}

#[test]
fn thread_settings_update_params_preserve_explicit_null_service_tier() {
let params: ThreadSettingsUpdateParams = serde_json::from_value(json!({
Expand Down
29 changes: 28 additions & 1 deletion codex-rs/app-server-protocol/src/protocol/v2/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use codex_experimental_api_macros::ExperimentalApi;
pub use codex_protocol::capabilities::CapabilityRootLocation;
pub use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::MultiAgentMode;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
pub use codex_protocol::dynamic_tools::DynamicToolFunctionSpec;
Expand Down Expand Up @@ -93,6 +94,12 @@ pub struct ThreadStartParams {
pub developer_instructions: Option<String>,
#[ts(optional = nullable)]
pub personality: Option<Personality>,
/// Set the initial multi-agent mode for this thread.
/// Omitted leaves the thread without a selected mode. Eligible multi-agent
/// v2 turns still default to `explicitRequestOnly`.
#[experimental("thread/start.multiAgentMode")]
#[ts(optional = nullable)]
pub multi_agent_mode: Option<MultiAgentMode>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Split the multi-agent rollout into stages

This commit is 656 changed lines across 46 files, and the edits are not mechanical: the option is threaded through protocol/schema/docs, app-server start/resume/fork/settings behavior, core spawn/resume/fork inheritance, analytics/TUI call sites, and test suites. Because that is a complex logic change over the 500-line target, split it into smaller reviewable stages, such as landing settings/API plumbing separately from AgentControl/ThreadManager lifecycle inheritance.

AGENTS.md reference: AGENTS.md:L125-L131

Useful? React with 👍 / 👎.

#[ts(optional = nullable)]
pub ephemeral: Option<bool>,
#[ts(optional = nullable)]
Expand Down Expand Up @@ -179,6 +186,10 @@ pub struct ThreadStartResponse {
#[serde(default)]
pub active_permission_profile: Option<ActivePermissionProfile>,
pub reasoning_effort: Option<ReasoningEffort>,
/// Current selected multi-agent mode for this thread, if one was selected.
#[experimental("thread/start.multiAgentMode")]
#[serde(default)]
pub multi_agent_mode: Option<MultiAgentMode>,
}

impl ThreadStartResponse {
Expand Down Expand Up @@ -239,6 +250,10 @@ pub struct ThreadSettingsUpdateParams {
#[experimental("thread/settings/update.collaborationMode")]
#[ts(optional = nullable)]
pub collaboration_mode: Option<CollaborationMode>,
/// Select the multi-agent mode for subsequent turns.
#[experimental("thread/settings/update.multiAgentMode")]
#[ts(optional = nullable)]
pub multi_agent_mode: Option<MultiAgentMode>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None is a meaningful lifecycle state in the design, but here it merge it with omission. This feels odd to me?

/// Override the personality for subsequent turns.
#[ts(optional = nullable)]
pub personality: Option<Personality>,
Expand All @@ -249,7 +264,7 @@ pub struct ThreadSettingsUpdateParams {
#[ts(export_to = "v2/")]
pub struct ThreadSettingsUpdateResponse {}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadSettings {
Expand All @@ -264,6 +279,10 @@ pub struct ThreadSettings {
pub effort: Option<ReasoningEffort>,
pub summary: Option<ReasoningSummary>,
pub collaboration_mode: CollaborationMode,
/// Current selected multi-agent mode for this thread, if one was selected.
#[experimental("thread/settings.multiAgentMode")]
#[serde(default)]
pub multi_agent_mode: Option<MultiAgentMode>,
pub personality: Option<Personality>,
}

Expand Down Expand Up @@ -400,6 +419,10 @@ pub struct ThreadResumeResponse {
#[serde(default)]
pub active_permission_profile: Option<ActivePermissionProfile>,
pub reasoning_effort: Option<ReasoningEffort>,
/// Current selected multi-agent mode for this thread, if one was selected.
#[experimental("thread/resume.multiAgentMode")]
#[serde(default)]
pub multi_agent_mode: Option<MultiAgentMode>,
/// `thread/turns/list` page returned when requested by `initialTurnsPage`.
#[experimental("thread/resume.initialTurnsPage")]
#[serde(default)]
Expand Down Expand Up @@ -555,6 +578,10 @@ pub struct ThreadForkResponse {
#[serde(default)]
pub active_permission_profile: Option<ActivePermissionProfile>,
pub reasoning_effort: Option<ReasoningEffort>,
/// Current selected multi-agent mode for this thread, if one was selected.
#[experimental("thread/fork.multiAgentMode")]
#[serde(default)]
pub multi_agent_mode: Option<MultiAgentMode>,
}

impl ThreadForkResponse {
Expand Down
Loading
Loading