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
1 change: 0 additions & 1 deletion codex-rs/core/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ pub(crate) use legacy_apply_patch_exec_command_warning::LegacyApplyPatchExecComm
pub(crate) use legacy_model_mismatch_warning::LegacyModelMismatchWarning;
pub(crate) use legacy_unified_exec_process_limit_warning::LegacyUnifiedExecProcessLimitWarning;
pub(crate) use model_switch_instructions::ModelSwitchInstructions;
pub(crate) use multi_agent_mode_instructions::MultiAgentModeInstructions;
pub(crate) use network_rule_saved::NetworkRuleSaved;
pub use permissions_instructions::ApprovalPromptContext;
pub use permissions_instructions::PermissionsInstructions;
Expand Down
4 changes: 2 additions & 2 deletions codex-rs/core/src/context/multi_agent_mode_instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ const EXPLICIT_REQUEST_ONLY_MULTI_AGENT_MODE_TEXT: &str = "Any earlier instructi
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 {
pub(super) struct MultiAgentModeInstructions {
multi_agent_mode: MultiAgentMode,
}

impl MultiAgentModeInstructions {
pub(crate) fn from_mode(multi_agent_mode: MultiAgentMode) -> Option<Self> {
pub(super) fn from_mode(multi_agent_mode: MultiAgentMode) -> Option<Self> {
if matches!(
&multi_agent_mode,
MultiAgentMode::Custom(hint_text) if hint_text.is_empty()
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/context/world_state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod apps_instructions;
mod collaboration_mode;
mod environment;
mod environments_instructions;
mod multi_agent_mode;
mod permissions;
mod plugins_instructions;
mod realtime;
Expand Down Expand Up @@ -30,6 +31,7 @@ pub(crate) use apps_instructions::AppsInstructionsState;
pub(crate) use collaboration_mode::CollaborationModeState;
pub(crate) use environment::EnvironmentsState;
pub(crate) use environments_instructions::EnvironmentsInstructionsState;
pub(crate) use multi_agent_mode::MultiAgentModeState;
pub(crate) use permissions::PermissionsState;
pub(crate) use plugins_instructions::PluginsInstructionsState;
pub(crate) use realtime::RealtimeState;
Expand Down
80 changes: 80 additions & 0 deletions codex-rs/core/src/context/world_state/multi_agent_mode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use super::PreviousSectionState;
use super::WorldStateSection;
use crate::context::ContextualUserFragment;
use crate::context::multi_agent_mode_instructions::MultiAgentModeInstructions;
use codex_protocol::config_types::MultiAgentMode;
use codex_utils_output_truncation::TruncationPolicy;
use codex_utils_output_truncation::truncate_text;
use serde::Deserialize;
use serde::Serialize;

const MULTI_AGENT_MODE_MAX_TOKENS: usize = 400;

/// Effective multi-agent mode currently visible to the model.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub(crate) struct MultiAgentModeState {
mode: Option<MultiAgentMode>,
}

impl MultiAgentModeState {
pub(crate) fn new(mode: Option<MultiAgentMode>) -> Self {
Self {
mode: mode.map(|mode| match mode {
MultiAgentMode::Custom(hint_text) => MultiAgentMode::Custom(truncate_text(
&hint_text,
TruncationPolicy::Tokens(MULTI_AGENT_MODE_MAX_TOKENS),
)),
mode @ (MultiAgentMode::ExplicitRequestOnly | MultiAgentMode::Proactive) => mode,
}),
}
}
}

impl WorldStateSection for MultiAgentModeState {
const ID: &'static str = "multi_agent_mode";
type Snapshot = Self;

fn snapshot(&self) -> Self::Snapshot {
self.clone()
}

fn matches_legacy_fragment(role: &str, text: &str) -> bool {
role == "developer" && MultiAgentModeInstructions::matches_text(text)
}

fn has_retained_fragment_matcher() -> bool {
true
}

fn matches_retained_fragment(role: &str, text: &str) -> bool {
Self::matches_legacy_fragment(role, text)
}

fn render_diff(
&self,
previous: PreviousSectionState<'_, Self::Snapshot>,
) -> Option<Box<dyn ContextualUserFragment>> {
let mode = match (&self.mode, previous) {
(Some(mode), PreviousSectionState::Known(previous))
if previous.mode.as_ref() == Some(mode) =>
{
return None;
}
(Some(mode), _) => mode.clone(),
(None, PreviousSectionState::Known(previous))
if previous.mode == Some(MultiAgentMode::Proactive) =>
{
MultiAgentMode::ExplicitRequestOnly
}
(None, PreviousSectionState::Unknown) => MultiAgentMode::ExplicitRequestOnly,
(None, PreviousSectionState::Absent | PreviousSectionState::Known(_)) => return None,
};

MultiAgentModeInstructions::from_mode(mode)
.map(|instructions| Box::new(instructions) as Box<dyn ContextualUserFragment>)
}
}

#[cfg(test)]
#[path = "multi_agent_mode_tests.rs"]
mod tests;
81 changes: 81 additions & 0 deletions codex-rs/core/src/context/world_state/multi_agent_mode_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use super::super::test_support::render_section_cases;
use super::*;
use crate::context::world_state::WorldState;
use codex_protocol::models::ResponseItem;
use codex_utils_output_truncation::approx_token_count;

fn state(mode: Option<MultiAgentMode>) -> MultiAgentModeState {
MultiAgentModeState::new(mode)
}

#[test]
fn snapshots() {
use PreviousSectionState::Absent;
use PreviousSectionState::Known;
use PreviousSectionState::Unknown;

let inactive = state(/*mode*/ None);
let explicit = state(Some(MultiAgentMode::ExplicitRequestOnly));
let proactive = state(Some(MultiAgentMode::Proactive));
let custom = state(Some(MultiAgentMode::Custom(
"use a custom policy".to_string(),
)));
let empty = state(Some(MultiAgentMode::Custom(String::new())));

insta::assert_snapshot!(render_section_cases(&[
(Absent, Absent),
(Absent, Known(&inactive)),
(Absent, Known(&explicit)),
(Known(&explicit), Known(&explicit)),
(Known(&explicit), Known(&proactive)),
(Known(&proactive), Known(&inactive)),
(Known(&explicit), Known(&inactive)),
(Known(&explicit), Known(&custom)),
(Known(&custom), Known(&empty)),
(Unknown, Known(&explicit)),
(Unknown, Known(&inactive)),
]));
}

#[test]
fn persisted_mode_is_restored_only_when_missing_from_history() {
let state = state(Some(MultiAgentMode::ExplicitRequestOnly));
let retained: ResponseItem = ContextualUserFragment::into(
MultiAgentModeInstructions::from_mode(MultiAgentMode::ExplicitRequestOnly)
.expect("explicit mode should render"),
);
let mut world_state = WorldState::default();
world_state.add_section(state);
let snapshot = world_state.snapshot();

assert_eq!(
world_state
.render_history_diff(/*previous*/ None, std::slice::from_ref(&retained))
.len(),
1,
);
assert_eq!(
world_state.render_history_diff(Some(&snapshot), &[]).len(),
1
);
assert!(
world_state
.render_history_diff(Some(&snapshot), &[retained])
.is_empty()
);
}

#[test]
fn custom_mode_is_bounded_before_snapshot_and_rendering() {
let state = state(Some(MultiAgentMode::Custom("custom mode ".repeat(1_000))));
let Some(MultiAgentMode::Custom(snapshot_mode)) = state.snapshot().mode else {
panic!("expected custom multi-agent mode")
};
assert!(approx_token_count(&snapshot_mode) < 1_000);

let rendered = state
.render_diff(PreviousSectionState::Absent)
.expect("custom mode should render")
.render();
assert!(approx_token_count(&rendered) < 1_000);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
source: core/src/context/world_state/multi_agent_mode_tests.rs
expression: "render_section_cases(&[(Absent, Absent), (Absent, Known(&inactive)),\n(Absent, Known(&explicit)), (Known(&explicit), Known(&explicit)),\n(Known(&explicit), Known(&proactive)), (Known(&proactive), Known(&inactive)),\n(Known(&explicit), Known(&inactive)), (Known(&explicit), Known(&custom)),\n(Known(&custom), Known(&empty)), (Unknown, Known(&explicit)),\n(Unknown, Known(&inactive)),])"
---
Absent -> Absent
None

Absent -> {}
None

Absent -> {"mode":"explicitRequestOnly"} (role - developer)
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. Do not spawn sub-agents unless the user or applicable AGENTS.md/skill instructions explicitly ask for sub-agents, delegation, or parallel agent work.</multi_agent_mode>

{"mode":"explicitRequestOnly"} -> {"mode":"explicitRequestOnly"}
None

{"mode":"explicitRequestOnly"} -> {"mode":"proactive"} (role - developer)
<multi_agent_mode>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.</multi_agent_mode>

{"mode":"proactive"} -> {} (role - developer)
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. Do not spawn sub-agents unless the user or applicable AGENTS.md/skill instructions explicitly ask for sub-agents, delegation, or parallel agent work.</multi_agent_mode>

{"mode":"explicitRequestOnly"} -> {}
None

{"mode":"explicitRequestOnly"} -> {"mode":{"custom":"use a custom policy"}} (role - developer)
<multi_agent_mode>use a custom policy</multi_agent_mode>

{"mode":{"custom":"use a custom policy"}} -> {"mode":{"custom":""}}
None

Unknown -> {"mode":"explicitRequestOnly"} (role - developer)
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. Do not spawn sub-agents unless the user or applicable AGENTS.md/skill instructions explicitly ask for sub-agents, delegation, or parallel agent work.</multi_agent_mode>

Unknown -> {} (role - developer)
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. Do not spawn sub-agents unless the user or applicable AGENTS.md/skill instructions explicitly ask for sub-agents, delegation, or parallel agent work.</multi_agent_mode>
24 changes: 0 additions & 24 deletions codex-rs/core/src/context_manager/updates.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,14 @@
use crate::context::ContextualUserFragment;
use crate::context::ModelSwitchInstructions;
use crate::context::MultiAgentModeInstructions;
use crate::context::PersonalitySpecInstructions;
use crate::session::PreviousTurnSettings;
use crate::session::turn_context::TurnContext;
use codex_protocol::config_types::MultiAgentMode;
use codex_protocol::config_types::Personality;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::protocol::TurnContextItem;

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);
let previous = previous?;
if previous.multi_agent_mode == effective_multi_agent_mode {
return None;
}

match effective_multi_agent_mode {
Some(multi_agent_mode) => MultiAgentModeInstructions::from_mode(multi_agent_mode)
.map(|instructions| instructions.render()),
None if previous.multi_agent_mode == Some(MultiAgentMode::Proactive) => {
MultiAgentModeInstructions::from_mode(MultiAgentMode::ExplicitRequestOnly)
.map(|instructions| instructions.render())
}
None => None,
}
}

fn build_personality_update_item(
previous: Option<&TurnContextItem>,
next: &TurnContext,
Expand Down Expand Up @@ -145,7 +122,6 @@ pub(crate) fn build_settings_update_items(
// Keep model-switch instructions first so model-specific guidance is read before
// any other context diffs on this turn.
build_model_instructions_update_item(previous_turn_settings, next),
build_multi_agent_mode_update_item(previous, next),
build_personality_update_item(previous, next, personality_feature_enabled),
]
.into_iter()
Expand Down
13 changes: 8 additions & 5 deletions codex-rs/core/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ use crate::config::resolve_tool_suggest_config_from_layer_stack;
use crate::context::ApprovedCommandPrefixSaved;
use crate::context::AvailableSkillsInstructions;
use crate::context::ContextualUserFragment;
use crate::context::MultiAgentModeInstructions;
use crate::context::NetworkRuleSaved;
use crate::context::PersonalitySpecInstructions;
use crate::context::RecommendedPluginsInstructions;
Expand Down Expand Up @@ -120,6 +119,7 @@ use codex_protocol::protocol::HasLegacyEvent;
use codex_protocol::protocol::InterAgentCommunication;
use codex_protocol::protocol::ItemCompletedEvent;
use codex_protocol::protocol::ItemStartedEvent;
use codex_protocol::protocol::MULTI_AGENT_MODE_OPEN_TAG;
use codex_protocol::protocol::MultiAgentVersion;
use codex_protocol::protocol::RawResponseItemEvent;
use codex_protocol::protocol::RolloutItem;
Expand Down Expand Up @@ -3437,8 +3437,13 @@ impl Session {
.push(crate::context::ContextWindowGuidance::new(guidance_message).render());
}
}
// Render the active mode after the usage hint so it can override that hint.
let mut initial_multi_agent_mode = None;
for fragment in world_state.render_full() {
match fragment.role() {
"developer" if fragment.markers().0 == MULTI_AGENT_MODE_OPEN_TAG => {
initial_multi_agent_mode = Some(fragment);
}
"developer" => developer_sections.push(fragment.render()),
"user" => contextual_user_sections.push(fragment.render()),
_ => {}
Expand Down Expand Up @@ -3469,10 +3474,8 @@ impl Session {
{
items.push(usage_hint_message);
}
if let Some(multi_agent_mode) = multi_agents::effective_multi_agent_mode(turn_context)
&& let Some(instructions) = MultiAgentModeInstructions::from_mode(multi_agent_mode)
{
items.push(ContextualUserFragment::into(instructions));
if let Some(initial_multi_agent_mode) = initial_multi_agent_mode {
items.push(initial_multi_agent_mode.into_boxed_response_item());
}
if let Some(contextual_user_message) =
crate::context_manager::updates::build_contextual_user_message(contextual_user_sections)
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/session/turn_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ impl TurnContext {
personality: self.personality,
collaboration_mode: Some(self.collaboration_mode()),
multi_agent_version: Some(self.multi_agent_version),
multi_agent_mode: super::multi_agents::effective_multi_agent_mode(self),
multi_agent_mode: None,
realtime_active: Some(self.realtime_active),
effort: self.reasoning_effort.clone(),
summary: ReasoningSummaryConfig::Auto,
Expand Down
4 changes: 4 additions & 0 deletions codex-rs/core/src/session/world_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::context::world_state::AppsInstructionsState;
use crate::context::world_state::CollaborationModeState;
use crate::context::world_state::EnvironmentsInstructionsState;
use crate::context::world_state::EnvironmentsState;
use crate::context::world_state::MultiAgentModeState;
use crate::context::world_state::PermissionsState;
use crate::context::world_state::PluginsInstructionsState;
use crate::context::world_state::RealtimeState;
Expand Down Expand Up @@ -129,6 +130,9 @@ impl Session {
world_state.add_extension_section(section);
}
}
world_state.add_section(MultiAgentModeState::new(
super::multi_agents::effective_multi_agent_mode(turn_context),
));
world_state
}
}
Loading
Loading