mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
Drop parent spawn call from forked history
This commit is contained in:
@@ -18,12 +18,12 @@ use codex_protocol::SessionId;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
#[cfg(test)]
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::InitialHistory;
|
||||
use codex_protocol::protocol::InterAgentCommunication;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::ResumedHistory;
|
||||
use codex_protocol::protocol::RolloutItem;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_protocol::protocol::ThreadSource;
|
||||
@@ -316,11 +316,11 @@ impl AgentControl {
|
||||
inherited_shell_snapshot: Option<Arc<ShellSnapshot>>,
|
||||
inherited_exec_policy: Option<Arc<crate::exec_policy::ExecPolicyManager>>,
|
||||
) -> CodexResult<crate::thread_manager::NewThread> {
|
||||
if options.fork_parent_spawn_call_id.is_none() {
|
||||
let Some(parent_spawn_call_id) = options.fork_parent_spawn_call_id.as_deref() else {
|
||||
return Err(CodexErr::Fatal(
|
||||
"spawn_agent fork requires a parent spawn call id".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
let Some(fork_mode) = options.fork_mode.as_ref() else {
|
||||
return Err(CodexErr::Fatal(
|
||||
"spawn_agent fork requires a fork mode".to_string(),
|
||||
@@ -363,6 +363,13 @@ impl AgentControl {
|
||||
forked_rollout_items =
|
||||
truncate_rollout_to_last_n_fork_turns(&forked_rollout_items, *last_n_turns);
|
||||
}
|
||||
forked_rollout_items.retain(|item| {
|
||||
!matches!(
|
||||
item,
|
||||
RolloutItem::ResponseItem(ResponseItem::FunctionCall { name, call_id, .. })
|
||||
if name == "spawn_agent" && call_id == parent_spawn_call_id
|
||||
)
|
||||
});
|
||||
|
||||
state
|
||||
.fork_thread_with_source(
|
||||
|
||||
@@ -177,6 +177,15 @@ fn history_contains_text(history_items: &[ResponseItem], needle: &str) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
fn history_contains_function_call(history_items: &[ResponseItem], call_id: &str) -> bool {
|
||||
history_items.iter().any(|item| {
|
||||
matches!(
|
||||
item,
|
||||
ResponseItem::FunctionCall { call_id: item_call_id, .. } if item_call_id == call_id
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn history_contains_assistant_inter_agent_communication(
|
||||
history_items: &[ResponseItem],
|
||||
expected: &InterAgentCommunication,
|
||||
@@ -623,7 +632,7 @@ async fn spawn_agent_creates_thread_and_sends_prompt() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn spawn_agent_can_fork_parent_thread_history_without_rewriting_rollout() {
|
||||
async fn spawn_agent_can_fork_parent_thread_history_without_replaying_parent_spawn_call() {
|
||||
let harness = AgentControlHarness::new().await;
|
||||
let mut parent_config = harness.config.clone();
|
||||
let _ = parent_config.features.enable(Feature::MultiAgentV2);
|
||||
@@ -730,11 +739,16 @@ async fn spawn_agent_can_fork_parent_thread_history_without_rewriting_rollout()
|
||||
assert_ne!(child_thread_id, parent_thread_id);
|
||||
let history = child_thread.codex.session.clone_history().await;
|
||||
let mut expected_history = vec![user_message("parent seed context")];
|
||||
expected_history.extend(parent_items);
|
||||
let mut expected_parent_items = parent_items;
|
||||
assert_eq!(
|
||||
expected_parent_items.pop(),
|
||||
Some(spawn_agent_call(&parent_spawn_call_id))
|
||||
);
|
||||
expected_history.extend(expected_parent_items);
|
||||
assert_eq!(
|
||||
history.raw_items(),
|
||||
expected_history.as_slice(),
|
||||
"forked child history should replay the parent rollout without rewriting model-visible items"
|
||||
"forked child history should replay the parent rollout except for the trigger spawn call"
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(child_thread.codex.session.reference_context_item().await)
|
||||
@@ -824,6 +838,10 @@ async fn spawn_agent_fork_flushes_parent_rollout_before_loading_history() {
|
||||
history_contains_text(history.raw_items(), "unflushed final answer"),
|
||||
"forked child history should include unflushed assistant final answers after flushing the parent rollout"
|
||||
);
|
||||
assert!(
|
||||
!history_contains_function_call(history.raw_items(), &parent_spawn_call_id),
|
||||
"forked child history should drop the trigger spawn call after flushing the parent rollout"
|
||||
);
|
||||
|
||||
let _ = harness
|
||||
.control
|
||||
@@ -947,6 +965,10 @@ async fn spawn_agent_fork_last_n_turns_keeps_only_recent_turns() {
|
||||
history_contains_text(history.raw_items(), "current parent task"),
|
||||
"forked child history should keep the parent user message from the requested last-N turn window"
|
||||
);
|
||||
assert!(
|
||||
!history_contains_function_call(history.raw_items(), &parent_spawn_call_id),
|
||||
"forked child history should drop the trigger spawn call from the requested last-N turn window"
|
||||
);
|
||||
|
||||
let _ = harness
|
||||
.control
|
||||
|
||||
Reference in New Issue
Block a user