Add opt-in elastic budgeting for synchronous Guardian reviews (#59444)

## What changed

- Add `features.guardianv2.sync_elastic_budgeting` and `sync_elastic_sections` to configure section minimums, per-entry token caps, and optional section maximums. Require and validate section limits when enabled.
- Use the selected budgeting policy consistently for history capture, resume, prompt preparation, and review input budgeting. Keep fixed budgeting as the default.

## Testing

Add configuration validation tests, capture-and-resume coverage for both budgeting modes, and a request-history snapshot verifying retained instructions and transcript section limits.

GitOrigin-RevId: c7b0bdfd3e0e008d75c4d8a204959bcc58eb1bf8
This commit is contained in:
ioanbudea-OAI
2026-11-04 16:33:22 +00:00
committed by copyberry
parent 48524f337a
commit 4d62359072
19 changed files with 491 additions and 5 deletions
+9 -3
View File
@@ -108,6 +108,8 @@ pub(crate) struct ContextManager {
items: Arc<Vec<ResponseItemEnvelope>>,
/// Compatibility history for legacy review and missing root instructions.
review_history: Option<TranscriptHistory>,
/// Captured before recording; replay and legacy backups keep the same storage policy.
review_budgeting: ContextBudgeting,
/// Host facts independent of the model window; snapshots share immutable state.
retained_context: Arc<RetainedContext>,
/// File IDs rejected by Responses. Retained history stays append-only; only
@@ -317,6 +319,7 @@ impl ContextManager {
sampling: Default::default(),
items: Arc::new(Vec::new()),
review_history: None,
review_budgeting: ContextBudgeting::Fixed,
retained_context: Arc::default(),
rejected_audio_file_ids: Arc::default(),
file_audio_durations: Arc::default(),
@@ -356,6 +359,7 @@ impl ContextManager {
pub(crate) fn for_session(
root_capabilities_restricted: bool,
features: &ManagedFeatures,
budgeting: ContextBudgeting,
item_id_generator: ItemIdGenerator,
) -> Self {
let independent = !features.enabled(Feature::GuardianReuseParentCompaction);
@@ -368,7 +372,9 @@ impl ContextManager {
} else {
GuardianContextMode::ThreadOwned
},
review_history: independent.then(TranscriptHistory::default),
review_budgeting: budgeting,
review_history: independent
.then(|| TranscriptHistory::new(/*generation*/ 0, budgeting)),
..Self::new(item_id_generator)
}
}
@@ -503,7 +509,7 @@ impl ContextManager {
.as_ref()
.map_or(self.history_version, TranscriptHistory::generation)
.saturating_add(1);
let mut history = TranscriptHistory::new(generation, ContextBudgeting::Fixed);
let mut history = TranscriptHistory::new(generation, self.review_budgeting);
if let Some(checkpoint) = checkpoint {
history.reset(checkpoint.0.iter());
} else {
@@ -919,7 +925,7 @@ impl ContextManager {
{
let mut retained = TranscriptHistory::new(
self.history_version.saturating_add(1),
ContextBudgeting::Fixed,
self.review_budgeting,
);
for item in self
.annotated_items()
@@ -159,6 +159,7 @@ fn root_history() -> ContextManager {
ContextManager::for_session(
/*root_capabilities_restricted*/ false,
&ManagedFeatures::from(codex_features::Features::with_defaults()),
codex_guardian_context::ContextBudgeting::Fixed,
codex_protocol_test_support::uuid_v7_item_id_generator_for_test(),
)
}
@@ -579,6 +580,7 @@ fn guardian_checkpoint_applies_additional_context_policy_after_resume(include: b
let mut history = ContextManager::for_session(
/*root_capabilities_restricted*/ false,
&features,
codex_guardian_context::ContextBudgeting::Fixed,
codex_protocol_test_support::uuid_v7_item_id_generator_for_test(),
);
let additional_context: ResponseItem = crate::context::AdditionalContextToolOutput::new(
@@ -621,6 +623,7 @@ fn guardian_checkpoint_applies_additional_context_policy_after_resume(include: b
let mut resumed = ContextManager::for_session(
/*root_capabilities_restricted*/ false,
&features,
codex_guardian_context::ContextBudgeting::Fixed,
codex_protocol_test_support::uuid_v7_item_id_generator_for_test(),
);
resumed.replace_annotated(compacted);