From 711a5f8b3a6eb40134146ae9ec22fdcdda5e3170 Mon Sep 17 00:00:00 2001 From: jif Date: Tue, 18 Aug 2026 09:32:36 +0000 Subject: [PATCH] Drop descendant progress updates after remote compaction (#39176) ## What changed - Exclude descendant-authored `MESSAGE` progress updates from the history retained after remote compaction v2. - Continue retaining descendant-authored tasks while omitting progress updates and final answers from subsequent requests. ## Testing - Extend the remote compaction follow-up test to cover descendant progress updates and descendant-authored tasks. GitOrigin-RevId: 3e3e60bb108d27e99e2a4c222eed4b9d02fd3829 --- codex-rs/core/src/compact_remote_v2.rs | 19 ++++++- codex-rs/core/tests/suite/compact_remote.rs | 63 ++++++++++++++++++++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/codex-rs/core/src/compact_remote_v2.rs b/codex-rs/core/src/compact_remote_v2.rs index 89d5c5a1b1..02738a34a4 100644 --- a/codex-rs/core/src/compact_remote_v2.rs +++ b/codex-rs/core/src/compact_remote_v2.rs @@ -510,13 +510,28 @@ fn v2_history_item_groups( } fn is_retained_for_remote_compaction_v2(item: &ResponseItem) -> bool { - if let ResponseItem::AgentMessage { content, .. } = item { + if let ResponseItem::AgentMessage { + author, + recipient, + content, + .. + } = item + { + let is_descendant_progress = author + .strip_prefix(recipient) + .is_some_and(|suffix| suffix.starts_with('/')) + && matches!( + content.first(), + Some(AgentMessageInputContent::InputText { text }) + if text.starts_with("Message Type: MESSAGE\n") + ); let is_completion = matches!( content.first(), Some(AgentMessageInputContent::InputText { text }) if text.starts_with("Message Type: FINAL_ANSWER\n") ); - return !is_completion + return !is_descendant_progress + && !is_completion && estimate_item_token_count(item) <= MAX_RETAINED_AGENT_MESSAGE_TOKENS; } diff --git a/codex-rs/core/tests/suite/compact_remote.rs b/codex-rs/core/tests/suite/compact_remote.rs index 451ec20959..b6ab9b19ab 100644 --- a/codex-rs/core/tests/suite/compact_remote.rs +++ b/codex-rs/core/tests/suite/compact_remote.rs @@ -1355,6 +1355,10 @@ async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<( responses::ev_assistant_message("m-agent", "DELEGATED_TASK_REPLY"), responses::ev_completed("resp-agent"), ]), + responses::sse(vec![ + responses::ev_assistant_message("m-descendant", "DESCENDANT_FOLLOWUP_REPLY"), + responses::ev_completed("resp-descendant"), + ]), responses::sse(vec![ serde_json::json!({ "type": "response.output_item.done", @@ -1381,6 +1385,18 @@ async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<( .await?; wait_for_turn_complete(&codex).await; + codex + .submit(Op::InterAgentCommunication { + communication: InterAgentCommunication::new( + AgentPath::root().join("child").expect("valid child path"), + AgentPath::root(), + Vec::new(), + "Message Type: MESSAGE\nTask name: /root\nSender: /root/child\nPayload:\nchild progress" + .to_string(), + /*trigger_turn*/ false, + ), + }) + .await?; codex .submit(Op::InterAgentCommunication { communication: InterAgentCommunication::new( @@ -1406,6 +1422,21 @@ async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<( .await?; wait_for_turn_complete(&codex).await; + let descendant_followup_ciphertext = "descendant follow-up task"; + let worker_path = AgentPath::root().join("worker").expect("valid worker path"); + codex + .submit(Op::InterAgentCommunication { + communication: InterAgentCommunication::new_encrypted( + worker_path.join("child").expect("valid grandchild path"), + worker_path, + Vec::new(), + descendant_followup_ciphertext.to_string(), + /*trigger_turn*/ true, + ), + }) + .await?; + wait_for_turn_complete(&codex).await; + codex.submit(Op::Compact).await?; wait_for_turn_complete(&codex).await; @@ -1418,7 +1449,7 @@ async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<( wait_for_turn_complete(&codex).await; let response_requests = responses_mock.requests(); - let compact_request = &response_requests[2]; + let compact_request = &response_requests[3]; let item_create_time = |request: &responses::ResponsesRequest, text: &str| { request .input() @@ -1448,6 +1479,21 @@ async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<( == Some(delegated_task_ciphertext.as_str())), "expected v2 compaction input to include the encrypted delegated task" ); + assert!( + compact_request + .inputs_of_type("agent_message") + .iter() + .any(|item| item["content"][1]["encrypted_content"].as_str() + == Some(descendant_followup_ciphertext)), + "expected v2 compaction input to include the descendant-authored follow-up task" + ); + assert!( + compact_request + .inputs_of_type("agent_message") + .iter() + .any(|item| item.to_string().contains("child progress")), + "expected v2 compaction input to include the child progress update" + ); assert!( compact_request .inputs_of_type("agent_message") @@ -1525,6 +1571,21 @@ async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<( == Some(delegated_task_ciphertext.as_str())), "expected v2 follow-up request to retain the encrypted delegated task" ); + assert!( + follow_up_request + .inputs_of_type("agent_message") + .iter() + .any(|item| item["content"][1]["encrypted_content"].as_str() + == Some(descendant_followup_ciphertext)), + "expected v2 follow-up request to retain the descendant-authored follow-up task" + ); + assert!( + follow_up_request + .inputs_of_type("agent_message") + .iter() + .all(|item| !item.to_string().contains("child progress")), + "expected v2 follow-up request to omit the child progress update" + ); assert!( follow_up_request .inputs_of_type("agent_message")