Render host-owned V2 subagent rosters in environment context (#59531)

## What changed

- Expose `AgentSessionReader` and `AgentRef` for read-only access to host-owned agent session membership.
- Read direct children when rendering V2 environment context, using the existing roster size limits. Refresh membership when rebuilding context after compaction, and omit the roster if the provider is absent or the read fails.
- Preserve unrecorded initial input across suspension or interruption while waiting for a roster read. Resolve the roster before advancing the compaction window.

## Testing

Add coverage for roster refreshes after compaction, unchanged environments avoiding extra reads, unavailable rosters, recoverable input during blocked reads, and stale compaction results rejected after a concurrent history replacement.

GitOrigin-RevId: 3c7e3822af09e9295d405e53dabb87b020c8f00c
This commit is contained in:
rhan-oai
2026-11-05 00:39:39 +00:00
committed by copyberry
parent 9a7213ca37
commit f3074e35c0
33 changed files with 642 additions and 210 deletions
@@ -556,17 +556,6 @@ impl ContextManager {
self.reference_context_item.clone()
}
pub(crate) fn update_world_state(
&mut self,
world_state: &WorldState,
) -> (Vec<WorldStateUpdate>, Option<WorldStateItem>) {
let (snapshot, fragments) =
world_state.render_history_diff(self.world_state_baseline.as_ref(), self.raw_items());
let rollout_item = self.world_state_item(&snapshot);
self.world_state_baseline = Some(snapshot);
(fragments, rollout_item)
}
pub(crate) fn render_step_world_state(
&self,
world_state: &WorldState,
@@ -180,6 +180,17 @@ fn estimate_response_item_model_visible_bytes(item: &ResponseItem) -> i64 {
super::estimate_response_item_model_visible_bytes(item, &FileAudioDurations::default())
}
impl ContextManager {
fn update_world_state(
&mut self,
world_state: &WorldState,
) -> (Vec<WorldStateUpdate>, Option<WorldStateItem>) {
let (snapshot, updates, item) = self.render_step_world_state(world_state);
self.set_world_state_baseline(snapshot);
(updates, item)
}
}
#[test]
fn delivered_assistant_context_invalidates_reviews_without_changing_authorization() {
let revisions = |history: &ContextManager| {
@@ -765,6 +776,7 @@ fn world_state_reconciles_matching_legacy_history_once() {
.into_iter()
.map(|update| match update.content {
WorldStateUpdateContent::Fragment(fragment) => fragment.body().expect_text(),
WorldStateUpdateContent::Environment(_) => panic!("unexpected environment"),
WorldStateUpdateContent::Item(item) =>
panic!("expected a text fragment, got {item:?}"),
})
+3 -4
View File
@@ -2,7 +2,6 @@
use crate::context::world_state::Placement;
use crate::context::world_state::WorldStateUpdate;
use crate::context::world_state::WorldStateUpdateContent;
use codex_context_fragments::RenderedFragment;
use codex_context_fragments::message_from_parts;
use codex_protocol::models::ResponseItem;
@@ -22,9 +21,9 @@ pub(crate) fn merge_world_state_updates(updates: Vec<WorldStateUpdate>) -> Vec<R
let mut items = Vec::new();
let mut pending = Vec::<RenderedFragment>::new();
for update in updates {
let fragment = match update.content {
WorldStateUpdateContent::Fragment(fragment) => fragment,
WorldStateUpdateContent::Item(item) => {
let fragment = match update.content.into_fragment() {
Ok(fragment) => fragment,
Err(item) => {
items.extend(build_rendered_message(std::mem::take(&mut pending)));
items.push(*item);
continue;
@@ -2,6 +2,7 @@
use super::*;
use crate::context::ContextualUserFragment;
use crate::context::world_state::WorldStateUpdateContent;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ContentItemKind;
use codex_protocol::models::InternalChatMessageMetadataPassthrough;