Warn when skill catalogs exceed their context budget (#34997)

## Why

Skill catalog rendering can shorten descriptions or omit enabled skills to fit
the model context budget. Surface these reductions so users know when the
model-visible catalog has lost detail.

## What changed

- Emit a warning with the omitted skill count whenever entries do not fit.
- Warn when description shortening averages more than 100 characters per skill.
- Deduplicate executor catalog warnings across repeated world-state builds in a
  turn.

## Testing

- Cover the description-shortening threshold and omission warning text.
- Verify warnings through extension and production-turn catalogs, including
  per-turn deduplication.

GitOrigin-RevId: bd7fc3482e5dfd8c79072f772a2f54aa502478d9
This commit is contained in:
felixxia-oai authored and copyberry committed 2026-07-23 19:44:12 +00:00
1 parent 7bafdada8b
commit 2c92af09cf
7 files changed
+283 -49

No files matched your search

+65
View File
@@ -310,6 +310,13 @@ fn catalog_emits_omission_marker_when_every_minimum_skill_line_exceeds_budget()
truncated_description_chars: MAX_CATALOG_SKILL_DESCRIPTION_CHARS,
truncated_description_count: 1,
};
assert_eq!(
expected_report.warning_message(),
Some(
"Exceeded skills context budget. All skill descriptions were removed and 1 additional skill was not included in the model-visible skills list."
.to_string()
)
);
let core_render = render_available_skills(
&catalog,
SkillCatalogRenderPolicy::CoreCompatible,
@@ -375,3 +382,61 @@ fn catalog_preserves_report_when_no_fragment_fits_budget() {
.is_none()
);
}
#[test]
fn substantial_description_shortening_emits_warning() {
let catalog = SkillCatalog {
entries: vec![
entry(
"long-skill",
&"a".repeat(250),
/*short_description*/ None,
),
entry("empty-skill", "", /*short_description*/ None),
],
warnings: Vec::new(),
};
let skill_lines = catalog
.entries
.iter()
.map(|entry| SkillLine::new(entry, SkillCatalogRenderPolicy::ExtensionCompatible))
.collect::<Vec<_>>();
let minimum_cost = skill_lines.iter().fold(0usize, |used, line| {
used.saturating_add(line.minimum_cost(SkillMetadataBudget::Characters(usize::MAX)))
});
let render = render_available_skills(
&catalog,
SkillCatalogRenderPolicy::ExtensionCompatible,
SkillMetadataBudget::Characters(minimum_cost + 49),
)
.expect("catalog should render");
assert_eq!(
render.report.warning_message(),
Some(
"Skill descriptions were shortened to fit the skills context budget. Codex can still see every skill, but some descriptions are shorter. Disable unused skills or plugins to leave more room for the rest."
.to_string()
)
);
}
#[test]
fn substantial_description_shortening_warning_starts_above_threshold() {
let report_at_threshold = SkillRenderReport {
total_count: 2,
included_count: 2,
omitted_count: 0,
truncated_description_chars: 200,
truncated_description_count: 2,
};
assert_eq!(report_at_threshold.warning_message(), None);
let report_above_threshold = SkillRenderReport {
truncated_description_chars: 201,
..report_at_threshold
};
assert_eq!(
report_above_threshold.warning_message(),
Some(SKILL_DESCRIPTION_TRUNCATED_WARNING.to_string())
);
}