Preserve root authorization context in Guardian reviews (#42832)

## Why

Guardian reviews for delegated workers need the current root instructions and verified answers even after the parent context is compacted. Approvals must also become stale when that root authorization changes.

## What changed

- Build bounded root review evidence from retained context, preserving source order and answer scope while prioritizing user instructions over optional assistant context.
- Recover retained instructions from Guardian history after compaction and mark authorization incomplete when required instructions or answers are unavailable.
- Version root authorization in synchronous and reusable review sessions so an allow result is cancelled when its evidence changes.
- Strip parent-only Guardian approvals when forking worker history in retained-context mode.

## Testing

- Cover retained and legacy context modes, oversized evidence, message limits, parent compaction, and authorization changes during review.

GitOrigin-RevId: 658219b7cee08f2752adcea9966268fd21727976
This commit is contained in:
felixxia-oai authored and copyberry committed 2026-09-04 19:19:04 +00:00
1 parent de7874067f
commit 87628df77a
12 files changed
+588 -103

No files matched your search

+27 -4
View File
@@ -1941,10 +1941,18 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
.expect("parent shutdown should submit");
}
#[test_case::test_case(true; "thread context enabled")]
#[test_case::test_case(false; "thread context disabled")]
#[tokio::test]
async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history() {
async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history(
thread_context_enabled: bool,
) {
let harness = AgentControlHarness::new().await;
let mut parent_config = harness.config.clone();
parent_config
.features
.set_enabled(Feature::GuardianThreadContext, thread_context_enabled)
.expect("test context mode");
let _ = parent_config.features.enable(Feature::MultiAgentV2);
parent_config.developer_instructions = Some("Parent developer instructions.".to_string());
parent_config.multi_agent_v2.root_agent_usage_hint_text =
@@ -1952,6 +1960,10 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history() {
parent_config.multi_agent_v2.subagent_usage_hint_text =
Some("Parent subagent guidance.".to_string());
let mut child_config = harness.config.clone();
child_config
.features
.set_enabled(Feature::GuardianThreadContext, thread_context_enabled)
.expect("test context mode");
let _ = child_config.features.enable(Feature::MultiAgentV2);
child_config.developer_instructions = Some("Child developer instructions.".to_string());
child_config.multi_agent_v2.subagent_developer_instructions =
@@ -1977,6 +1989,7 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history() {
/*trigger_turn*/ true,
);
let replacement_history = vec![
ContextualUserFragment::into(crate::context::GuardianApprovedAction::new("parent-private-release".to_owned())),
ResponseItem::Message {
id: None,
role: "user".to_string(),
@@ -2101,9 +2114,19 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history() {
),
"a subagent must not inherit its parent review checkpoint",
);
let mut expected_retained_context = codex_history::RetainedContext::default();
expected_retained_context.mark_user_messages_incomplete();
assert_eq!(history.retained_context(), &expected_retained_context);
assert_eq!(
history_contains_text(history.raw_items(), "parent-private-release"),
!thread_context_enabled,
"only retained mode changes parent approval inheritance",
);
let mut inherited_context = codex_history::RetainedContext::default();
if thread_context_enabled {
inherited_context.restore(/*checkpoint*/ None);
inherited_context.reserve_order();
} else {
inherited_context.mark_user_messages_incomplete();
}
assert_eq!(history.retained_context(), &inherited_context);
assert!(
history_contains_text(history.raw_items(), "compacted parent summary"),
"forked child history should retain compacted non-hint content"