Remove partial-history subagent forks (#51329)

## What changed

- Limit multi-agent V2 `fork_turns` guidance to `all` and `none`. Accept legacy positive integer strings as full-history forks instead of truncating parent history.
- Remove partial-history fork handling and telemetry, and direct model override usage to `fork_turns: "none"`.
- Strip inherited `multi_agent.role_instructions` and `multi_agent.usage_hint` content by kind, including persisted hints whose wording predates the current bundled instructions.

## Testing

Update regression coverage for legacy `"1"` and `"3"` values inheriting full history, invalid and zero values being rejected, and stale parent hints being removed from compacted history with both marked and unmarked instructions.

GitOrigin-RevId: 8f6017dc66ad8deefd7745399ef76e1bb1435bb2
This commit is contained in:
jif
2026-10-06 10:39:16 +00:00
committed by copyberry
parent 822e58cc3d
commit 6221a217e2
37 changed files with 125 additions and 1065 deletions
+18 -22
View File
@@ -7,7 +7,6 @@ use crate::agent::child_config::build_agent_resume_config;
use crate::agent::role::apply_role_to_config;
use crate::agent::types::AgentMetadata;
use crate::agent::types::LiveAgent;
use crate::agent::types::SpawnAgentForkMode;
use crate::agent::types::SpawnAgentOptions;
use crate::agents_md_manager::SessionInstructions;
use crate::codex_thread::CodexThread;
@@ -122,8 +121,7 @@ fn keep_forked_rollout_item(item: &RolloutItem, preserve_context_baselines: bool
| RolloutItem::RetainedContext(_)
| RolloutItem::SecurityRiskScore(_) => false,
// Full-history forks preserve the cached prompt prefix and can keep diffing
// from the parent's durable baseline. Truncated forks drop part of that prompt,
// so they must rebuild context on their first child turn.
// from the parent's durable baseline unless legacy compaction requires rebuilding it.
RolloutItem::TurnContext(_) | RolloutItem::WorldState(_) => preserve_context_baselines,
// Child threads inherit model context, not the parent's cumulative usage state.
RolloutItem::TokenUsageRecord(_) => false,
@@ -140,7 +138,11 @@ fn retain_forked_developer_message(item: &mut ResponseItem, usage_hint_texts: &[
return false;
};
content.retain(|content_item| {
if content_item.kind().0 == "guardian.approved_action" {
// Persisted role hints can predate the current bundled wording and lack markers.
if matches!(
content_item.kind().0.as_str(),
"guardian.approved_action" | "multi_agent.role_instructions" | "multi_agent.usage_hint"
) {
return false;
}
let ContentItem::InputText { text } = content_item.content() else {
@@ -990,11 +992,11 @@ impl LocalAgentControl {
"spawn_agent fork requires a parent spawn call id".to_string(),
));
}
let Some(fork_mode) = options.fork_mode.as_ref() else {
if options.fork_mode.is_none() {
return Err(CodexErr::Fatal(
"spawn_agent fork requires a fork mode".to_string(),
));
};
}
let SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id, ..
}) = &session_source
@@ -1062,10 +1064,6 @@ impl LocalAgentControl {
Some(meta_line.meta.selected_capability_roots.clone())
})
.unwrap_or_default();
if let SpawnAgentForkMode::LastNTurns(last_n_turns) = fork_mode {
forked_rollout_items =
truncate_rollout_to_last_n_fork_turns(forked_rollout_items, *last_n_turns);
}
let multi_agent_v2_usage_hint_texts_to_filter: Vec<String> =
if multi_agent_version == MultiAgentVersion::V2 {
let parent_config = parent_thread.session.get_config().await;
@@ -1082,19 +1080,17 @@ impl LocalAgentControl {
} else {
Vec::new()
};
let mut preserve_context_baselines = matches!(fork_mode, SpawnAgentForkMode::FullHistory);
if preserve_context_baselines {
for item in forked_rollout_items.iter().rev() {
let RolloutItem::Compacted(compacted) = item else {
continue;
};
// Legacy checkpoints force the child to rebuild context regardless of the
// live parent's reference baseline; an older superseded checkpoint does not.
if compacted.replacement_history.is_none() {
preserve_context_baselines = false;
}
break;
let mut preserve_context_baselines = true;
for item in forked_rollout_items.iter().rev() {
let RolloutItem::Compacted(compacted) = item else {
continue;
};
// Legacy checkpoints force the child to rebuild context regardless of the
// live parent's reference baseline; an older superseded checkpoint does not.
if compacted.replacement_history.is_none() {
preserve_context_baselines = false;
}
break;
}
let mut replaced_parent_developer_instructions = false;
// Scrub inherited hints and replace only the parent's developer-instruction fragment.
@@ -28,7 +28,6 @@ pub(super) fn record_spawn_success(
let fork_mode = match fork_mode {
None => "none",
Some(SpawnAgentForkMode::FullHistory) => "all",
Some(SpawnAgentForkMode::LastNTurns(_)) => "last_n",
};
let history_mode = match measurements.history_mode {
ThreadHistoryMode::Legacy => "legacy",
@@ -39,7 +39,7 @@ fn records_bounded_spawn_phase_metrics() {
record_spawn_success(
&telemetry,
Some(&SpawnAgentForkMode::LastNTurns(3)),
Some(&SpawnAgentForkMode::FullHistory),
MultiAgentVersion::V2,
SpawnMeasurements {
history_mode: ThreadHistoryMode::Paginated,
@@ -74,7 +74,7 @@ fn records_bounded_spawn_phase_metrics() {
})
.collect::<BTreeMap<_, _>>();
assert_eq!(attributes.len(), 5);
assert_eq!(attributes["fork_mode"], "last_n");
assert_eq!(attributes["fork_mode"], "all");
assert_eq!(attributes["history_mode"], "paginated");
assert_eq!(attributes["multi_agent_version"], "v2");
assert_eq!(attributes["product_sku"], "codex");