mirror of
https://github.com/openai/codex.git
synced 2026-11-03 16:23:51 +00:00
Retain user instructions in guardian thread context (#42762)
## Why Guardian review needs the original user instructions alongside verified answers, even after compaction, resume, or rollback. A summarized or legacy checkpoint cannot establish a complete instruction history. ## What changed - Capture text user messages in retained context when `guardian_thread_context` is enabled, preserving their message identity through compaction and replay. - Keep retained instructions and verified answers in arrival order, with independent size and completeness tracking for each family. - Roll back retained evidence at the exact user-message boundary, including steers that share a turn ID, and apply the same semantics during rollout migration. - Treat checkpoints without retained user messages as incomplete while preserving their verified answers. ## Testing - Cover capture, compaction, resume, rollback, child forks, legacy checkpoints, storage bounds, and rollout migration. GitOrigin-RevId: 72706731b54e5ac8645db61b446d2401afb42235
This commit is contained in:
1 parent
cc4b8bdeb8
commit
80d7ca34bc
18 files changed
+1058
-103
No files matched your search
@@ -32,8 +32,10 @@ use codex_analytics::CompactionTrigger;
|
||||
use codex_analytics::now_unix_seconds;
|
||||
use codex_context_fragments::AnnotatedContent;
|
||||
use codex_context_fragments::set_annotated_content;
|
||||
use codex_features::Feature;
|
||||
use codex_history::CodexHarnessMetadata;
|
||||
use codex_history::ResponseItemEnvelope;
|
||||
use codex_protocol::ResponseItemId;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::CodexErrorDetails;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
@@ -355,7 +357,12 @@ async fn run_compact_task_inner_impl(
|
||||
let summary_suffix =
|
||||
get_last_assistant_message_from_turn(history_snapshot.raw_items()).unwrap_or_default();
|
||||
let summary_text = format!("{SUMMARY_PREFIX}\n{summary_suffix}");
|
||||
let user_messages = collect_annotated_user_messages(history_items);
|
||||
let identity = if sess.enabled(Feature::GuardianThreadContext) {
|
||||
CompactedMessageIdentity::Preserve
|
||||
} else {
|
||||
CompactedMessageIdentity::Regenerate
|
||||
};
|
||||
let user_messages = collect_annotated_user_messages(history_items, identity);
|
||||
|
||||
let mut new_history = build_compacted_history(Vec::new(), &user_messages, &summary_text);
|
||||
if let Some(summary_item) = new_history.last_mut() {
|
||||
@@ -527,6 +534,9 @@ pub fn content_items_to_text(content: &[ContentItem]) -> Option<String> {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) struct CompactedUserMessage {
|
||||
// Keep source identity even when compaction shortens the text, so rollback can
|
||||
// correlate the rebuilt message with thread-owned retained evidence.
|
||||
id: Option<ResponseItemId>,
|
||||
message: String,
|
||||
internal_chat_message_metadata_passthrough: Option<InternalChatMessageMetadataPassthrough>,
|
||||
harness_metadata: Option<CodexHarnessMetadata>,
|
||||
@@ -540,12 +550,24 @@ pub(crate) fn collect_user_messages(items: &[ResponseItem]) -> Vec<CompactedUser
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) enum CompactedMessageIdentity {
|
||||
Preserve,
|
||||
Regenerate,
|
||||
}
|
||||
|
||||
pub(crate) fn collect_annotated_user_messages(
|
||||
items: &[ResponseItemEnvelope],
|
||||
identity: CompactedMessageIdentity,
|
||||
) -> Vec<CompactedUserMessage> {
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|envelope| compacted_user_message(&envelope.item, envelope.metadata.clone()))
|
||||
.map(|mut message| {
|
||||
if matches!(identity, CompactedMessageIdentity::Regenerate) {
|
||||
message.id = None;
|
||||
}
|
||||
message
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -560,6 +582,7 @@ fn compacted_user_message(
|
||||
return None;
|
||||
}
|
||||
Some(CompactedUserMessage {
|
||||
id: item.id().cloned(),
|
||||
message: user.message(),
|
||||
internal_chat_message_metadata_passthrough: match item {
|
||||
ResponseItem::Message {
|
||||
@@ -678,6 +701,7 @@ fn build_compacted_history_with_limit(
|
||||
let truncated =
|
||||
truncate_text(&message.message, TruncationPolicy::Tokens(remaining));
|
||||
selected_messages.push(CompactedUserMessage {
|
||||
id: message.id.clone(),
|
||||
message: truncated,
|
||||
internal_chat_message_metadata_passthrough: message
|
||||
.internal_chat_message_metadata_passthrough
|
||||
@@ -692,7 +716,7 @@ fn build_compacted_history_with_limit(
|
||||
|
||||
for message in &selected_messages {
|
||||
let mut item = ResponseItem::Message {
|
||||
id: None,
|
||||
id: message.id.clone(),
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: message.message.clone(),
|
||||
|
||||
Reference in new issue
Block a user