Preserve parent tool declarations in prediction forks (#57287)

## What changed

Keep inherited incremental tool declarations in MCP-disabled prediction forks and `thread/sample` requests, and append a developer notice that MCP tools are unavailable. Skip tool declaration updates within the inherited context window; new windows still declare the current tools.

Add the default-enabled `prediction_preserve_parent_tools` feature flag and its configuration schema entries.

## Testing

Update core snapshots and app-server tests to cover inherited declarations, the MCP-unavailable notice, preservation across MCP refresh, and continued parent tool availability.

GitOrigin-RevId: 3adf96c7d0e0093986dbdbd540bd47674a2c105c
This commit is contained in:
Keyan Zhang
2026-10-26 22:05:23 +00:00
committed by copyberry
parent 136d891a94
commit b46a3801e4
9 changed files with 174 additions and 150 deletions
+22
View File
@@ -1802,6 +1802,18 @@ impl Session {
}
}
self.persist_rollout_items(&rollout_items).await;
if self.preserve_parent_tool_declarations() {
let notice = DeveloperInstructions::new(
"<mcp_tools>\nMCP tools are unavailable for this request.\n</mcp_tools>",
)
.with_metadata(ContentItemMetadata::tool(/*namespace*/ None));
self.record_conversation_items(
&turn_context,
turn_context.model_info(),
&[ContextualUserFragment::into(notice)],
)
.await;
}
// Agent spawn owns its final durability barrier so it can overlap the edge write.
let persist_context = match self.fork_persistence {
@@ -4572,6 +4584,16 @@ impl Session {
true
}
fn preserve_parent_tool_declarations(&self) -> bool {
// Only prediction forks and thread/sample set this marker when they disable MCP.
self.enabled(Feature::PredictionPreserveParentTools)
&& self
.services
.thread_extension_data
.get::<crate::McpServersDisabled>()
.is_some()
}
pub fn enabled(&self, feature: Feature) -> bool {
self.features.enabled(feature)
}
+3 -1
View File
@@ -165,7 +165,9 @@ impl Session {
let extension_metrics = super::extension_metrics::from_session_telemetry(
step_context.session_telemetry.clone(),
);
if incremental_tools {
let preserve_parent_tool_declarations =
!new_window && self.preserve_parent_tool_declarations();
if incremental_tools && !preserve_parent_tool_declarations {
let specs = step_context.tool_router.model_visible_specs();
let definitions = codex_tools::create_tools_json_for_responses_lite(&specs)?;
world_state.add_section(TopLevelToolsState::new(