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
6 changes: 6 additions & 0 deletions codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,12 @@
"minimum": 1.0,
"type": "integer"
},
"root_agent_usage_hint_text": {
"type": "string"
},
"subagent_usage_hint_text": {
"type": "string"
},
"usage_hint_enabled": {
"type": "boolean"
},
Expand Down
35 changes: 34 additions & 1 deletion codex-rs/core/src/agent/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use codex_protocol::AgentPath;
use codex_protocol::ThreadId;
use codex_protocol::error::CodexErr;
use codex_protocol::error::Result as CodexResult;
use codex_protocol::models::ContentItem;
use codex_protocol::models::MessagePhase;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::InitialHistory;
Expand Down Expand Up @@ -395,7 +396,39 @@ impl AgentControl {
forked_rollout_items =
truncate_rollout_to_last_n_fork_turns(&forked_rollout_items, *last_n_turns);
}
forked_rollout_items.retain(keep_forked_rollout_item);
// MultiAgentV2 root/subagent usage hints are injected as standalone developer
// messages at thread start. When forking history, drop hints from the parent
// so the child gets a fresh hint that matches its own session source/config.
let multi_agent_v2_usage_hint_texts_to_filter: Vec<String> =
if let Some(parent_thread) = parent_thread.as_ref() {
parent_thread
.codex
.configured_multi_agent_v2_usage_hint_texts()
.await
} else if config.features.enabled(Feature::MultiAgentV2) {
[
config.multi_agent_v2.root_agent_usage_hint_text.clone(),
config.multi_agent_v2.subagent_usage_hint_text.clone(),
]
Comment thread
jif-oai marked this conversation as resolved.
.into_iter()
.flatten()
.collect()
} else {
Vec::new()
};
forked_rollout_items.retain(|item| {
if let RolloutItem::ResponseItem(ResponseItem::Message { role, content, .. }) = item
&& role == "developer"
&& let [ContentItem::InputText { text }] = content.as_slice()
&& multi_agent_v2_usage_hint_texts_to_filter
.iter()
.any(|usage_hint_text| usage_hint_text == text)
{
return false;
}

keep_forked_rollout_item(item)
});

state
.fork_thread_with_source(
Expand Down
38 changes: 36 additions & 2 deletions codex-rs/core/src/agent/control_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,25 @@ async fn spawn_agent_creates_thread_and_sends_prompt() {
#[tokio::test]
async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
let harness = AgentControlHarness::new().await;
let (parent_thread_id, parent_thread) = harness.start_thread().await;
let mut parent_config = harness.config.clone();
let _ = parent_config.features.enable(Feature::MultiAgentV2);
parent_config.multi_agent_v2.root_agent_usage_hint_text =
Some("Parent root guidance.".to_string());
parent_config.multi_agent_v2.subagent_usage_hint_text =
Some("Parent subagent guidance.".to_string());
let mut child_config = harness.config.clone();
let _ = child_config.features.enable(Feature::MultiAgentV2);
child_config.multi_agent_v2.root_agent_usage_hint_text =
Some("Child root guidance.".to_string());
child_config.multi_agent_v2.subagent_usage_hint_text =
Some("Child subagent guidance.".to_string());
let new_thread = harness
.manager
.start_thread(parent_config)
.await
.expect("start parent thread");
let parent_thread_id = new_thread.thread_id;
let parent_thread = new_thread.thread;
parent_thread
.inject_user_message_without_turn("parent seed context".to_string())
.await;
Expand All @@ -614,6 +632,22 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
.record_conversation_items(
turn_context.as_ref(),
&[
ResponseItem::Message {
id: None,
role: "developer".to_string(),
content: vec![ContentItem::InputText {
text: "Parent root guidance.".to_string(),
}],
phase: None,
},
ResponseItem::Message {
id: None,
role: "developer".to_string(),
content: vec![ContentItem::InputText {
text: "Parent subagent guidance.".to_string(),
}],
phase: None,
},
assistant_message("parent commentary", Some(MessagePhase::Commentary)),
assistant_message("parent final answer", Some(MessagePhase::FinalAnswer)),
assistant_message("parent unknown phase", /*phase*/ None),
Expand Down Expand Up @@ -643,7 +677,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
let child_thread_id = harness
.control
.spawn_agent_with_metadata(
harness.config.clone(),
child_config,
text_input("child task"),
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id,
Expand Down
22 changes: 22 additions & 0 deletions codex-rs/core/src/config/config_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7444,6 +7444,8 @@ enabled = true
max_concurrent_threads_per_session = 5
usage_hint_enabled = false
usage_hint_text = "Custom delegation guidance."
root_agent_usage_hint_text = "Root guidance."
subagent_usage_hint_text = "Subagent guidance."
hide_spawn_agent_metadata = true
"#,
)?;
Expand All @@ -7462,6 +7464,14 @@ hide_spawn_agent_metadata = true
config.multi_agent_v2.usage_hint_text.as_deref(),
Some("Custom delegation guidance.")
);
assert_eq!(
config.multi_agent_v2.root_agent_usage_hint_text.as_deref(),
Some("Root guidance.")
);
assert_eq!(
config.multi_agent_v2.subagent_usage_hint_text.as_deref(),
Some("Subagent guidance.")
);
assert!(config.multi_agent_v2.hide_spawn_agent_metadata);

Ok(())
Expand All @@ -7478,12 +7488,16 @@ async fn profile_multi_agent_v2_config_overrides_base() -> std::io::Result<()> {
max_concurrent_threads_per_session = 4
usage_hint_enabled = true
usage_hint_text = "base hint"
root_agent_usage_hint_text = "base root hint"
subagent_usage_hint_text = "base subagent hint"
hide_spawn_agent_metadata = true

[profiles.no_hint.features.multi_agent_v2]
max_concurrent_threads_per_session = 6
usage_hint_enabled = false
usage_hint_text = "profile hint"
root_agent_usage_hint_text = "profile root hint"
subagent_usage_hint_text = "profile subagent hint"
hide_spawn_agent_metadata = false
"#,
)?;
Expand All @@ -7500,6 +7514,14 @@ hide_spawn_agent_metadata = false
config.multi_agent_v2.usage_hint_text.as_deref(),
Some("profile hint")
);
assert_eq!(
config.multi_agent_v2.root_agent_usage_hint_text.as_deref(),
Some("profile root hint")
);
assert_eq!(
config.multi_agent_v2.subagent_usage_hint_text.as_deref(),
Some("profile subagent hint")
);
assert!(!config.multi_agent_v2.hide_spawn_agent_metadata);

Ok(())
Expand Down
16 changes: 16 additions & 0 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,8 @@ pub struct MultiAgentV2Config {
pub max_concurrent_threads_per_session: usize,
pub usage_hint_enabled: bool,
pub usage_hint_text: Option<String>,
pub root_agent_usage_hint_text: Option<String>,
pub subagent_usage_hint_text: Option<String>,
pub hide_spawn_agent_metadata: bool,
}

Expand All @@ -741,6 +743,8 @@ impl Default for MultiAgentV2Config {
DEFAULT_MULTI_AGENT_V2_MAX_CONCURRENT_THREADS_PER_SESSION,
usage_hint_enabled: true,
usage_hint_text: None,
root_agent_usage_hint_text: None,
subagent_usage_hint_text: None,
hide_spawn_agent_metadata: false,
}
}
Expand Down Expand Up @@ -1619,6 +1623,16 @@ fn resolve_multi_agent_v2_config(
.or_else(|| base.and_then(|config| config.usage_hint_text.as_ref()))
.cloned()
.or(default.usage_hint_text);
let root_agent_usage_hint_text = profile
.and_then(|config| config.root_agent_usage_hint_text.as_ref())
.or_else(|| base.and_then(|config| config.root_agent_usage_hint_text.as_ref()))
.cloned()
.or(default.root_agent_usage_hint_text);
let subagent_usage_hint_text = profile
.and_then(|config| config.subagent_usage_hint_text.as_ref())
.or_else(|| base.and_then(|config| config.subagent_usage_hint_text.as_ref()))
.cloned()
.or(default.subagent_usage_hint_text);
let hide_spawn_agent_metadata = profile
.and_then(|config| config.hide_spawn_agent_metadata)
.or_else(|| base.and_then(|config| config.hide_spawn_agent_metadata))
Expand All @@ -1628,6 +1642,8 @@ fn resolve_multi_agent_v2_config(
max_concurrent_threads_per_session,
usage_hint_enabled,
usage_hint_text,
root_agent_usage_hint_text,
subagent_usage_hint_text,
hide_spawn_agent_metadata,
}
}
Expand Down
30 changes: 29 additions & 1 deletion codex-rs/core/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ use codex_protocol::exec_output::StreamOutput;

mod handlers;
mod mcp;
mod multi_agents;
mod review;
mod rollout_reconstruction;
#[allow(clippy::module_inception)]
Expand Down Expand Up @@ -763,6 +764,22 @@ impl Codex {
state.session_configuration.thread_config_snapshot()
}

pub(crate) async fn configured_multi_agent_v2_usage_hint_texts(&self) -> Vec<String> {
let state = self.session.state.lock().await;
let config = &state.session_configuration.original_config_do_not_use;
if !config.features.enabled(Feature::MultiAgentV2) {
return Vec::new();
Comment thread
jif-oai marked this conversation as resolved.
}

[
config.multi_agent_v2.root_agent_usage_hint_text.clone(),
config.multi_agent_v2.subagent_usage_hint_text.clone(),
]
.into_iter()
.flatten()
.collect()
}

pub(crate) fn state_db(&self) -> Option<state_db::StateDbHandle> {
self.session.state_db()
}
Expand Down Expand Up @@ -2654,12 +2671,23 @@ impl Session {
);
}

let mut items = Vec::with_capacity(3);
let multi_agent_v2_usage_hint_text =
multi_agents::usage_hint_text(turn_context, &session_source);

let mut items = Vec::with_capacity(4);
if let Some(developer_message) =
crate::context_manager::updates::build_developer_update_item(developer_sections)
{
items.push(developer_message);
}
if let Some(usage_hint_text) = multi_agent_v2_usage_hint_text
&& let Some(usage_hint_message) =
crate::context_manager::updates::build_developer_update_item(vec![
usage_hint_text.to_string(),
])
{
items.push(usage_hint_message);
}
if let Some(contextual_user_message) =
crate::context_manager::updates::build_contextual_user_message(contextual_user_sections)
{
Expand Down
18 changes: 18 additions & 0 deletions codex-rs/core/src/session/multi_agents.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::session::turn_context::TurnContext;
use codex_features::Feature;
use codex_protocol::protocol::SessionSource;

pub(super) fn usage_hint_text<'a>(
turn_context: &'a TurnContext,
session_source: &SessionSource,
) -> Option<&'a str> {
if !turn_context.features.enabled(Feature::MultiAgentV2) {
return None;
}

let multi_agent_v2 = &turn_context.config.multi_agent_v2;
match session_source {
SessionSource::SubAgent(_) => multi_agent_v2.subagent_usage_hint_text.as_deref(),
Comment thread
jif-oai marked this conversation as resolved.
_ => multi_agent_v2.root_agent_usage_hint_text.as_deref(),
}
}
Loading
Loading