From 01316f9410e27ce8a407820fb804193853e6ad65 Mon Sep 17 00:00:00 2001 From: Andrei Hasna Date: Thu, 13 Aug 2026 14:57:21 +0300 Subject: [PATCH 1/6] fix(core): retain delegated messages in v2 compaction Agent: Hortensia-404 --- codex-rs/core/src/client_common.rs | 10 ++++++- codex-rs/core/src/compact_remote_v2.rs | 39 ++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs index 48d0523df4..ac285dd16f 100644 --- a/codex-rs/core/src/client_common.rs +++ b/codex-rs/core/src/client_common.rs @@ -54,7 +54,15 @@ impl Default for Prompt { impl Prompt { pub(crate) fn get_formatted_input(&self) -> Vec { - self.input + Self::format_input(&self.input) + } + + /// Converts persisted assistant envelopes into the model-native input items + /// used by the Responses API. Callers preparing history for retention must + /// use the same conversion as the request builder so encrypted delegated + /// messages are not filtered as ordinary assistant text. + pub(crate) fn format_input(input: &[ResponseItem]) -> Vec { + input .iter() .cloned() .map(|item| { diff --git a/codex-rs/core/src/compact_remote_v2.rs b/codex-rs/core/src/compact_remote_v2.rs index 0c04d2d185..f18c3e2809 100644 --- a/codex-rs/core/src/compact_remote_v2.rs +++ b/codex-rs/core/src/compact_remote_v2.rs @@ -392,7 +392,11 @@ async fn run_remote_compact_v2_attempt( let prompt_input = history .clone() .for_prompt(&turn_context.model_info.input_modalities); - let mut input = prompt_input.clone(); + // Apply the same persisted-envelope conversion used by the Responses request + // builder before retention. Otherwise encrypted delegated messages are still + // assistant envelopes here and the retention role filter drops them. + let formatted_prompt_input = Prompt::format_input(&prompt_input); + let mut input = formatted_prompt_input.clone(); input.push(ResponseItem::CompactionTrigger); let prompt = Prompt { input, @@ -450,7 +454,7 @@ async fn run_remote_compact_v2_attempt( .await; Ok(RemoteCompactV2Attempt { trace_input_history, - prompt_input, + prompt_input: formatted_prompt_input, compaction_output, token_usage, owned_client_session, @@ -718,8 +722,10 @@ fn truncate_message_text_to_token_budget( #[cfg(test)] mod tests { use super::*; + use codex_protocol::AgentPath; use codex_protocol::models::ContentItem; use codex_protocol::models::MessagePhase; + use codex_protocol::protocol::InterAgentCommunication; use pretty_assertions::assert_eq; use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; @@ -780,6 +786,35 @@ mod tests { ); } + #[test] + fn build_v2_compacted_history_retains_encrypted_delegated_message_after_formatting() { + let communication = InterAgentCommunication::new_encrypted( + AgentPath::root(), + AgentPath::root().join("worker").expect("valid worker path"), + Vec::new(), + "encrypted delegated task".to_string(), + true, + ); + let persisted_envelope: ResponseItem = communication.to_response_input_item().into(); + assert!(matches!( + persisted_envelope, + ResponseItem::Message { ref role, .. } if role == "assistant" + )); + + let formatted = Prompt::format_input(&[persisted_envelope]); + assert!(matches!( + formatted.first(), + Some(ResponseItem::AgentMessage { .. }) + )); + + let output = ResponseItem::Compaction { + encrypted_content: "new".to_string(), + }; + let history = build_v2_compacted_history(&formatted, output.clone()); + + assert_eq!(history, vec![formatted[0].clone(), output]); + } + #[test] fn build_v2_compacted_history_discards_messages_before_truncating() { let old = message("user", "old", /*phase*/ None); From 5c55c3e6d25e8fa3d8c31eba57f20db9f915d084 Mon Sep 17 00:00:00 2001 From: Andrei Hasna Date: Thu, 13 Aug 2026 15:05:49 +0300 Subject: [PATCH 2/6] fix(core): retain delegated messages during v2 compaction Agent: Hostus --- codex-rs/core/src/compact_remote_v2.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/codex-rs/core/src/compact_remote_v2.rs b/codex-rs/core/src/compact_remote_v2.rs index f18c3e2809..48c7230701 100644 --- a/codex-rs/core/src/compact_remote_v2.rs +++ b/codex-rs/core/src/compact_remote_v2.rs @@ -620,11 +620,13 @@ fn build_v2_compacted_history( } fn is_retained_for_remote_compaction_v2(item: &ResponseItem) -> bool { - let ResponseItem::Message { role, .. } = item else { - return false; - }; - - matches!(role.as_str(), "user" | "developer" | "system") + match item { + ResponseItem::Message { role, .. } => { + matches!(role.as_str(), "user" | "developer" | "system") + } + ResponseItem::AgentMessage { .. } => true, + _ => false, + } } fn truncate_retained_messages_for_remote_compaction( From eb8fb1a26c54d0ad4effb57ceaed0ab4e6aa3d3c Mon Sep 17 00:00:00 2001 From: Andrei Hasna Date: Thu, 13 Aug 2026 15:08:13 +0300 Subject: [PATCH 3/6] fix(core): retain formatted delegated messages Agent: Hortensia-404 --- codex-rs/core/src/compact_remote_v2.rs | 19 +++++++++++-------- codex-rs/core/src/context_manager/history.rs | 2 +- codex-rs/core/src/context_manager/mod.rs | 1 + 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/codex-rs/core/src/compact_remote_v2.rs b/codex-rs/core/src/compact_remote_v2.rs index 48c7230701..811947fbef 100644 --- a/codex-rs/core/src/compact_remote_v2.rs +++ b/codex-rs/core/src/compact_remote_v2.rs @@ -16,6 +16,7 @@ use crate::compact_remote::log_remote_compact_failure; use crate::compact_remote::process_compacted_history; use crate::compact_remote::should_keep_compacted_history_item; use crate::compact_remote::trim_function_call_history_to_fit_context_window; +use crate::context_manager::estimate_item_token_count; use crate::hook_runtime::PostCompactHookOutcome; use crate::hook_runtime::PreCompactHookOutcome; use crate::hook_runtime::run_post_compact_hooks; @@ -620,13 +621,15 @@ fn build_v2_compacted_history( } fn is_retained_for_remote_compaction_v2(item: &ResponseItem) -> bool { - match item { - ResponseItem::Message { role, .. } => { - matches!(role.as_str(), "user" | "developer" | "system") - } - ResponseItem::AgentMessage { .. } => true, - _ => false, + if matches!(item, ResponseItem::AgentMessage { .. }) { + return true; } + + let ResponseItem::Message { role, .. } = item else { + return false; + }; + + matches!(role.as_str(), "user" | "developer" | "system") } fn truncate_retained_messages_for_remote_compaction( @@ -657,7 +660,7 @@ fn truncate_retained_messages_for_remote_compaction( fn message_text_token_count(item: &ResponseItem) -> usize { let ResponseItem::Message { content, .. } = item else { - return 0; + return usize::try_from(estimate_item_token_count(item)).unwrap_or(usize::MAX); }; content @@ -682,7 +685,7 @@ fn truncate_message_text_to_token_budget( phase, } = item else { - return Some(item); + return None; }; let mut remaining = max_tokens; diff --git a/codex-rs/core/src/context_manager/history.rs b/codex-rs/core/src/context_manager/history.rs index 6086fbcd8b..fd8f24b5b9 100644 --- a/codex-rs/core/src/context_manager/history.rs +++ b/codex-rs/core/src/context_manager/history.rs @@ -612,7 +612,7 @@ fn estimate_encrypted_function_output_length(encoded_len: usize) -> usize { encoded_len.saturating_mul(9).div_ceil(16) } -fn estimate_item_token_count(item: &ResponseItem) -> i64 { +pub(crate) fn estimate_item_token_count(item: &ResponseItem) -> i64 { let model_visible_bytes = estimate_response_item_model_visible_bytes(item); approx_tokens_from_byte_count_i64(model_visible_bytes) } diff --git a/codex-rs/core/src/context_manager/mod.rs b/codex-rs/core/src/context_manager/mod.rs index 2295c49df3..38436c32d4 100644 --- a/codex-rs/core/src/context_manager/mod.rs +++ b/codex-rs/core/src/context_manager/mod.rs @@ -5,5 +5,6 @@ pub(crate) mod updates; pub(crate) use history::ContextManager; pub(crate) use history::TotalTokenUsageBreakdown; pub(crate) use history::estimate_response_item_model_visible_bytes; +pub(crate) use history::estimate_item_token_count; pub(crate) use history::is_user_turn_boundary; pub(crate) use history::truncate_function_output_payload; From 9000cde54f911af08773b68eb829ffb182c49fe5 Mon Sep 17 00:00:00 2001 From: Andrei Hasna Date: Thu, 13 Aug 2026 15:12:35 +0300 Subject: [PATCH 4/6] fix(core): bound retained delegated messages Agent: Hortensia-404 --- codex-rs/core/src/compact_remote_v2.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/compact_remote_v2.rs b/codex-rs/core/src/compact_remote_v2.rs index 811947fbef..c0907516ec 100644 --- a/codex-rs/core/src/compact_remote_v2.rs +++ b/codex-rs/core/src/compact_remote_v2.rs @@ -56,6 +56,7 @@ use tracing::info; // Mirror the current /responses/compact retained-message default while the // server-side path remains the reference implementation. const RETAINED_MESSAGE_TOKEN_BUDGET: usize = 64_000; +const MAX_RETAINED_AGENT_MESSAGE_TOKENS: i64 = 10_000; // Compact attempts can run much longer than normal turns, so keep the per-transport // retry budget smaller than the general Responses stream retry budget. const MAX_REMOTE_COMPACTION_V2_STREAM_RETRIES: u64 = 2; @@ -622,7 +623,7 @@ fn build_v2_compacted_history( fn is_retained_for_remote_compaction_v2(item: &ResponseItem) -> bool { if matches!(item, ResponseItem::AgentMessage { .. }) { - return true; + return estimate_item_token_count(item) <= MAX_RETAINED_AGENT_MESSAGE_TOKENS; } let ResponseItem::Message { role, .. } = item else { From 41f949ad96d5a714ad8186ad78446646a84c6be1 Mon Sep 17 00:00:00 2001 From: Andrei Hasna Date: Thu, 13 Aug 2026 15:14:59 +0300 Subject: [PATCH 5/6] fix(core): format context manager exports Agent: Hostus --- codex-rs/core/src/context_manager/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex-rs/core/src/context_manager/mod.rs b/codex-rs/core/src/context_manager/mod.rs index 38436c32d4..8c12ed939f 100644 --- a/codex-rs/core/src/context_manager/mod.rs +++ b/codex-rs/core/src/context_manager/mod.rs @@ -4,7 +4,7 @@ pub(crate) mod updates; pub(crate) use history::ContextManager; pub(crate) use history::TotalTokenUsageBreakdown; -pub(crate) use history::estimate_response_item_model_visible_bytes; pub(crate) use history::estimate_item_token_count; +pub(crate) use history::estimate_response_item_model_visible_bytes; pub(crate) use history::is_user_turn_boundary; pub(crate) use history::truncate_function_output_payload; From 20ed973bd24f697fbc99d40589f0855e6d236909 Mon Sep 17 00:00:00 2001 From: Andrei Hasna Date: Thu, 13 Aug 2026 15:27:55 +0300 Subject: [PATCH 6/6] test(core): annotate delegated message trigger Agent: Hortensia-404 --- codex-rs/core/src/compact_remote_v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex-rs/core/src/compact_remote_v2.rs b/codex-rs/core/src/compact_remote_v2.rs index c0907516ec..be0c0122d7 100644 --- a/codex-rs/core/src/compact_remote_v2.rs +++ b/codex-rs/core/src/compact_remote_v2.rs @@ -799,7 +799,7 @@ mod tests { AgentPath::root().join("worker").expect("valid worker path"), Vec::new(), "encrypted delegated task".to_string(), - true, + /*trigger_turn*/ true, ); let persisted_envelope: ResponseItem = communication.to_response_input_item().into(); assert!(matches!(