Allow volatile World State sections to refresh after compaction (#58956)

## Why

Host-provided context such as budget reminders can become stale while compaction runs. Replaying a captured value or retaining its comparison snapshot can prevent the next step from emitting a fresh reminder, even when the remaining budget has changed.

## What changed

- Add `WorldStateSectionContribution::without_full_context()` to omit a section's fragment and comparison snapshot from initial and compacted context rebuilds while preserving ordinary step updates.
- Reset comparison state after prefix compaction for sections excluded at either capture or installation, including when their messages survive in the live suffix.
- Include step World State updates when building debug prompt input.

## Testing

Add regression coverage for budget reminders refreshing after remote compaction within the same threshold, unchanged request prefixes within a context window, refreshes with a retained live suffix, and step reminders in debug prompts.

GitOrigin-RevId: ea4456755e16c93b5d16e8c762b26a33f8571a86
This commit is contained in:
rhan-oai
2026-11-02 19:38:59 +00:00
committed by copyberry
parent 43ad1cbf30
commit 2fa67f03f3
10 changed files with 503 additions and 48 deletions
+34 -3
View File
@@ -140,6 +140,10 @@ pub(crate) fn split_prefix_updates(
}
trait ErasedWorldStateSection: Send + Sync {
fn include_in_full_context(&self) -> bool {
true
}
fn matches_legacy_fragment(&self, role: &str, text: &str) -> bool;
fn has_retained_fragment_matcher(&self) -> bool;
@@ -222,6 +226,10 @@ impl<S: WorldStateSection> ErasedWorldStateSection for S {
struct ExtensionWorldStateSection(WorldStateSectionContribution);
impl ErasedWorldStateSection for ExtensionWorldStateSection {
fn include_in_full_context(&self) -> bool {
self.0.include_in_full_context()
}
fn matches_legacy_fragment(&self, role: &str, text: &str) -> bool {
self.0.matches_legacy_fragment(role, text)
}
@@ -422,6 +430,10 @@ impl From<&Map<String, Value>> for WorldStateSnapshot {
}
impl WorldStateSnapshot {
pub(crate) fn remove_section(&mut self, id: &str) {
self.sections.remove(id);
}
pub(crate) fn into_object(self) -> Map<String, Value> {
self.sections.into_iter().collect()
}
@@ -499,7 +511,20 @@ impl fmt::Debug for WorldState {
}
}
enum RenderScope {
FullContext,
HistoryDiff,
}
impl WorldState {
pub(crate) fn full_context_excluded_section_ids(
&self,
) -> impl Iterator<Item = &'static str> + '_ {
self.sections
.iter()
.filter_map(|(id, section)| (!section.include_in_full_context()).then_some(*id))
}
pub(crate) fn add_section<S: WorldStateSection>(&mut self, section: S) {
let id = S::ID;
assert!(
@@ -525,9 +550,11 @@ impl WorldState {
}
}
/// Renders every section as new, preserving update order and placement.
/// Renders restorable sections as new, preserving update order and placement.
pub(crate) fn render_full(&self) -> (WorldStateSnapshot, Vec<WorldStateUpdate>) {
self.render_with(/*stored*/ None, |_, _| PreviousSectionState::Absent)
self.render_with(RenderScope::FullContext, /*stored*/ None, |_, _| {
PreviousSectionState::Absent
})
}
/// Falls back to retained model history when no exact persisted snapshot is available.
@@ -536,7 +563,7 @@ impl WorldState {
previous: Option<&WorldStateSnapshot>,
items: impl IntoIterator<Item = &'a ResponseItem> + Clone,
) -> (WorldStateSnapshot, Vec<WorldStateUpdate>) {
self.render_with(previous, |id, section| {
self.render_with(RenderScope::HistoryDiff, previous, |id, section| {
if let Some(previous) = previous.and_then(|previous| previous.section(id)) {
if section.has_retained_fragment_matcher()
&& !has_retained_fragment(items.clone(), section)
@@ -555,12 +582,16 @@ impl WorldState {
fn render_with<'a>(
&self,
scope: RenderScope,
stored: Option<&WorldStateSnapshot>,
mut previous: impl FnMut(&str, &dyn ErasedWorldStateSection) -> PreviousSectionState<'a, Value>,
) -> (WorldStateSnapshot, Vec<WorldStateUpdate>) {
let mut snapshot = WorldStateSnapshot::default();
let mut updates = Vec::new();
for (id, section) in &self.sections {
if matches!(scope, RenderScope::FullContext) && !section.include_in_full_context() {
continue;
}
let prior = previous(id, section.as_ref());
let (section_snapshot, section_updates) = section.render_diff(prior);
// A skipped snapshot retains stored state even if history required a fresh render.