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:
felixxia-oai authored and copyberry committed 2026-09-04 13:24:42 +00:00
1 parent cc4b8bdeb8
commit 80d7ca34bc
18 files changed
+1058 -103

No files matched your search

+3 -1
View File
@@ -1590,7 +1590,9 @@ impl Session {
state
.history
.restore_guardian_history(guardian_history.as_ref());
state.history.restore_retained_context(&retained_context);
state
.history
.restore_retained_context(Some(&retained_context));
if let Some(world_state) = world_state_baseline {
state.history.set_world_state_baseline(world_state);
}
@@ -339,15 +339,16 @@ impl Session {
.unwrap_or(u64::MAX);
let mut history = ContextManager::new();
if self.enabled(Feature::GuardianThreadContext) {
history.enable_user_message_retention();
}
let mut saw_legacy_compaction_without_replacement_history = false;
if let Some(checkpoint) = base_compaction
&& let Some(items) = &checkpoint.compacted.replacement_history
{
history.replace_annotated(items.clone());
history.restore_guardian_history(checkpoint.compacted.guardian_history.as_ref());
if let Some(retained) = &checkpoint.compacted.retained_context {
history.restore_retained_context(retained);
}
history.restore_retained_context(checkpoint.compacted.retained_context.as_ref());
}
// Materialize exact history semantics from the replay-derived suffix. The eventual lazy
// design should keep this same replay shape, but drive it from a resumable reverse source
@@ -386,8 +387,15 @@ impl Session {
// prompt shape.
// TODO(ccunningham): if we drop support for None replacement_history compaction items,
// we can get rid of this second loop entirely and just build `history` directly in the first loop.
let user_messages =
compact::collect_annotated_user_messages(history.annotated_items());
let identity = if self.enabled(Feature::GuardianThreadContext) {
compact::CompactedMessageIdentity::Preserve
} else {
compact::CompactedMessageIdentity::Regenerate
};
let user_messages = compact::collect_annotated_user_messages(
history.annotated_items(),
identity,
);
let rebuilt = compact::build_compacted_history(
Vec::new(),
&user_messages,
@@ -395,7 +403,7 @@ impl Session {
);
let retained_context = history.retained_context().clone();
history.replace_annotated(rebuilt);
history.restore_retained_context(&retained_context);
history.restore_retained_context(Some(&retained_context));
}
}
RolloutItem::EventMsg(EventMsg::ThreadRolledBack(rollback)) => {
@@ -1488,6 +1488,7 @@ async fn reconstruct_history_legacy_compaction_without_replacement_history_does_
}],
});
let mut retained = codex_history::RetainedContext::default();
retained.mark_user_messages_incomplete();
retained.record(&answer);
let rollout_items = vec![
RolloutItem::ResponseItem(user_message("before compact").into()),
+3
View File
@@ -1266,6 +1266,9 @@ impl Session {
session_configuration.clone(),
initial_auto_compact_window_ids,
);
if config.features.enabled(Feature::GuardianThreadContext) {
state.history.enable_user_message_retention();
}
state.base_instructions_provenance = base_instructions_provenance.clone();
let managed_network_requirements_configured = config
.config_layer_stack