mirror of
https://github.com/openai/codex.git
synced 2026-11-14 21:40:39 +00:00
Add opt-in elastic budgeting for asynchronous Guardian requests (#61345)
## What changed - Add `async_elastic_budgeting` and `async_elastic_sections` under `[features.guardianv2]`, independently of synchronous budgeting and disabled by default. - Apply section limits and elastic allocation to new classifier evidence after accounting for setup and retained conversation history. Preserve the configured elastic policy when applying model defaults. - Keep permission context whole during elastic allocation and record truncation observations when image preparation rejects an image. ## Testing Add coverage for configuration validation, fixed and elastic conversation budgeting, retained history and cursor preservation, and complete classifier requests that retain evidence beyond the fixed entry limit. GitOrigin-RevId: 2955f0482f7c3e18325e296fc2ccb6592ffcb73a
This commit is contained in:
@@ -8960,8 +8960,10 @@ async fn load_config_uses_auto_review_guardian_policy_config(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case::test_case("sync")]
|
||||
#[test_case::test_case("async")]
|
||||
#[tokio::test]
|
||||
async fn sync_elastic_budget_is_opt_in_and_validated() {
|
||||
async fn elastic_budget_is_opt_in_and_validated(target: &str) {
|
||||
let excessive_minimum = codex_guardian_context::DEFAULT_MAX_INPUT_TOKENS + 1;
|
||||
let minimum_mismatch = format!(
|
||||
r#"[features.guardianv2.sync_elastic_sections.root_conversation_messages]
|
||||
@@ -8980,6 +8982,7 @@ max_total_section_tokens = 0
|
||||
min_total_section_tokens = 1
|
||||
max_section_entry_tokens = 0
|
||||
"#,
|
||||
"",
|
||||
maximum_below_minimum,
|
||||
minimum_mismatch.as_str(),
|
||||
r#"[features.guardianv2.sync_elastic_sections.root_messages]
|
||||
@@ -8990,10 +8993,11 @@ min_total_section_tokens = 1
|
||||
max_section_entry_tokens = 1
|
||||
"#,
|
||||
] {
|
||||
let sections = sections.replace("sync_", &format!("{target}_"));
|
||||
for enabled in [false, true] {
|
||||
let result = Config::load_from_base_config_with_overrides(
|
||||
toml::from_str(&format!(
|
||||
"[features.guardianv2]\nenabled = false\nsync_elastic_budgeting = {enabled}\n{sections}"
|
||||
"[features.guardianv2]\nenabled = false\n{target}_elastic_budgeting = {enabled}\n{sections}"
|
||||
))
|
||||
.unwrap(),
|
||||
ConfigOverrides {
|
||||
@@ -9008,6 +9012,17 @@ max_section_entry_tokens = 1
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn async_elastic_opt_in_preserves_fixed_capture() {
|
||||
let mut config = super::test_config().await;
|
||||
config.guardian_async_elastic_budget =
|
||||
Some(core_test_support::guardian::elastic_budget_config());
|
||||
assert_eq!(
|
||||
config.guardian_context_budgeting(),
|
||||
codex_guardian_context::ContextBudgeting::Fixed,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn requirements_guardian_policy_beats_auto_review() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
@@ -748,6 +748,8 @@ pub struct Config {
|
||||
pub guardian_transcript_mode: codex_protocol::TranscriptFormat,
|
||||
/// Frozen section policy for sync Guardian; absence keeps fixed budgeting.
|
||||
pub guardian_sync_elastic_budget: Option<codex_guardian_context::ElasticBudgetConfig>,
|
||||
/// Frozen section policy for async Guardian; absence keeps fixed budgeting.
|
||||
pub guardian_async_elastic_budget: Option<codex_guardian_context::ElasticBudgetConfig>,
|
||||
|
||||
/// Optional replacement for the gated history-retrieval instructions.
|
||||
/// Blank config values are treated as unset, like other Guardian policy overrides.
|
||||
@@ -4298,6 +4300,9 @@ impl Config {
|
||||
let section = codex_guardian_context::ElasticSection::deserialize(name.as_str().into_deserializer())
|
||||
.map_err(|error: serde::de::value::Error| std::io::Error::new(std::io::ErrorKind::InvalidData, error))?;
|
||||
if section == codex_guardian_context::ElasticSection::TranscriptSelectedExecResults {
|
||||
if prefix == "async" {
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "transcript_selected_exec_results is only supported for sync elastic budgeting"));
|
||||
}
|
||||
if limits.nested_tool_names.is_empty() || limits.nested_tool_names.iter().any(|name| name.trim().is_empty()) {
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
|
||||
"transcript_selected_exec_results requires nonempty nested_tool_names"));
|
||||
@@ -4333,6 +4338,10 @@ impl Config {
|
||||
.filter(|guardian| guardian.sync_elastic_budgeting == Some(true))
|
||||
.map(|guardian| resolve_elastic_budget(guardian.sync_elastic_sections.as_ref(), "sync"))
|
||||
.transpose()?;
|
||||
let guardian_async_elastic_budget = guardian
|
||||
.filter(|guardian| guardian.async_elastic_budgeting == Some(true))
|
||||
.map(|guardian| resolve_elastic_budget(guardian.async_elastic_sections.as_ref(), "async"))
|
||||
.transpose()?;
|
||||
let guardian_conversation_history_prompt = cfg.auto_review.as_ref().and_then(|auto_review| {
|
||||
normalize_guardian_policy_config(
|
||||
auto_review.experimental_conversation_history_prompt.as_deref(),
|
||||
@@ -4712,6 +4721,7 @@ impl Config {
|
||||
guardian_policy_template: None,
|
||||
guardian_transcript_mode,
|
||||
guardian_sync_elastic_budget,
|
||||
guardian_async_elastic_budget,
|
||||
guardian_conversation_history_prompt,
|
||||
guardian_conversation_history_max_output_tokens,
|
||||
guardian_review_timeout: cfg
|
||||
|
||||
@@ -55,7 +55,7 @@ impl<'a> PreparedReviewContext<'a> {
|
||||
Some(config) => Ok(Self::Elastic(
|
||||
context
|
||||
.prepare_elastic_entries(config)?
|
||||
.prepare_sync_delivery(history)?,
|
||||
.prepare_delivery(history)?,
|
||||
)),
|
||||
None => {
|
||||
codex_guardian_context::prepare_sync_delivery(&mut context, history)?;
|
||||
|
||||
Reference in New Issue
Block a user