Drop parent setup context from forked agents

This commit is contained in:
jif-oai
2026-05-17 14:13:59 +02:00
parent 4c89772314
commit a926bb004a
2 changed files with 65 additions and 17 deletions

View File

@@ -127,6 +127,47 @@ fn keep_forked_rollout_item(item: &RolloutItem) -> bool {
}
}
fn strip_parent_context_updates_from_forked_rollout(items: &mut Vec<RolloutItem>) {
let mut drop_item = vec![false; items.len()];
for idx in 0..items.len() {
if !matches!(items[idx], RolloutItem::TurnContext(_)) {
continue;
}
drop_item[idx] = true;
let mut context_idx = idx;
while context_idx > 0 {
let should_drop = match &items[context_idx - 1] {
RolloutItem::ResponseItem(ResponseItem::Message { role, content, .. })
if role == "developer" =>
{
true
}
RolloutItem::ResponseItem(ResponseItem::Message { role, content, .. })
if role == "user"
&& crate::event_mapping::is_contextual_user_message_content(content) =>
{
true
}
_ => false,
};
if !should_drop {
break;
}
context_idx -= 1;
drop_item[context_idx] = true;
}
}
let mut idx = 0;
items.retain(|_| {
let keep = !drop_item[idx];
idx += 1;
keep
});
}
/// Control-plane handle for multi-agent operations.
/// `AgentControl` is held by each session (via `SessionServices`). It provides capability to
/// spawn new agents and the inter-agent communication layer.
@@ -396,6 +437,10 @@ impl AgentControl {
forked_rollout_items =
truncate_rollout_to_last_n_fork_turns(&forked_rollout_items, *last_n_turns);
}
// Parent context updates are keyed by parent TurnContext snapshots. Forked children drop
// those snapshots and build their own startup context, so inherited context messages would
// otherwise be duplicated or stale in the child prompt.
strip_parent_context_updates_from_forked_rollout(&mut forked_rollout_items);
// 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.

View File

@@ -608,6 +608,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
Some("Parent root guidance.".to_string());
parent_config.multi_agent_v2.subagent_usage_hint_text =
Some("Parent subagent guidance.".to_string());
parent_config.developer_instructions = Some("Parent developer instructions.".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 =
@@ -621,10 +622,15 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
.expect("start parent thread");
let parent_thread_id = new_thread.thread_id;
let parent_thread = new_thread.thread;
let turn_context = parent_thread.codex.session.new_default_turn().await;
parent_thread
.codex
.session
.record_context_updates_and_set_reference_context_item(turn_context.as_ref())
.await;
parent_thread
.inject_user_message_without_turn("parent seed context".to_string())
.await;
let turn_context = parent_thread.codex.session.new_default_turn().await;
let parent_spawn_call_id = "spawn-call-history".to_string();
let trigger_message = InterAgentCommunication::new(
AgentPath::root(),
@@ -639,22 +645,6 @@ 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),
@@ -726,6 +716,19 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
&expected_history,
"forked child history should keep only parent user messages and assistant final answers"
);
let child_rollout_path = child_thread
.rollout_path()
.expect("forked child rollout path");
let child_rollout = std::fs::read_to_string(&child_rollout_path)
.expect("forked child rollout should be readable");
assert!(
!child_rollout.contains("Parent developer instructions."),
"forked child rollout should not retain parent developer instructions from setup context"
);
assert!(
!child_rollout.contains("Parent root guidance."),
"forked child rollout should not retain parent multi-agent setup guidance"
);
let expected = (
child_thread_id,