mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
Preserve paginated history for spawned subagents (#33432)
## What changed - Inherit paginated history mode when spawning or forking a subagent from a paginated parent. - Load the parent's model context when forking and persist it as an inherited rollout prefix, while excluding that prefix from the child's projected turns, items, and metadata. - Record the boundary between inherited context and child history, and reject partially initialized paginated subagent rollouts on resume. - Resume paginated subagents from their stored model context instead of legacy rollout history. ## Testing Added coverage for fresh and forked paginated subagents, compacted-history forks, model-context resume, inherited-prefix projection, metadata isolation, and incomplete-prefix detection. GitOrigin-RevId: e57dc37c29aa4aaaf57b052b991be591d730e5ba
This commit is contained in:
@@ -1242,12 +1242,19 @@ impl Session {
|
||||
}
|
||||
|
||||
async fn record_initial_history(&self, conversation_history: InitialHistory) {
|
||||
let is_subagent = {
|
||||
let (is_subagent, is_paginated_subagent) = {
|
||||
let state = self.state.lock().await;
|
||||
state
|
||||
.session_configuration
|
||||
.session_source
|
||||
.is_non_root_agent()
|
||||
let session_configuration = &state.session_configuration;
|
||||
(
|
||||
session_configuration.session_source.is_non_root_agent(),
|
||||
matches!(
|
||||
session_configuration.history_mode,
|
||||
ThreadHistoryMode::Paginated
|
||||
) && matches!(
|
||||
session_configuration.thread_source.as_ref(),
|
||||
Some(ThreadSource::Subagent)
|
||||
),
|
||||
)
|
||||
};
|
||||
let has_prior_user_turns = initial_history_has_prior_user_turns(&conversation_history);
|
||||
{
|
||||
@@ -1304,11 +1311,7 @@ impl Session {
|
||||
InitialHistory::Forked(mut rollout_items) => {
|
||||
let turn_context = self.new_default_turn().await;
|
||||
if turn_context.item_ids_enabled() {
|
||||
for rollout_item in &mut rollout_items {
|
||||
if let RolloutItem::ResponseItem(response_item) = rollout_item {
|
||||
Self::assign_missing_response_item_id(response_item);
|
||||
}
|
||||
}
|
||||
Self::assign_missing_rollout_response_item_ids(&mut rollout_items);
|
||||
}
|
||||
self.apply_rollout_reconstruction(&turn_context, &rollout_items)
|
||||
.await;
|
||||
@@ -1320,8 +1323,9 @@ impl Session {
|
||||
state.set_token_info(Some(info));
|
||||
}
|
||||
|
||||
// If persisting, persist all rollout items as-is (the store filters).
|
||||
if !rollout_items.is_empty() {
|
||||
// Paginated subagents persist inherited model context while creating the live
|
||||
// thread so the copied prefix is not observed as child-owned metadata.
|
||||
if !rollout_items.is_empty() && !is_paginated_subagent {
|
||||
self.persist_rollout_items(&rollout_items).await;
|
||||
}
|
||||
|
||||
@@ -2772,6 +2776,14 @@ impl Session {
|
||||
item.set_id(Some(ResponseItemId::new(prefix)));
|
||||
}
|
||||
|
||||
fn assign_missing_rollout_response_item_ids(items: &mut [RolloutItem]) {
|
||||
for item in items {
|
||||
if let RolloutItem::ResponseItem(response_item) = item {
|
||||
Self::assign_missing_response_item_id(response_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn response_item_from_user_input(&self, input: Vec<UserInput>) -> ResponseItem {
|
||||
ResponseItem::from(ResponseInputItem::from_user_input(
|
||||
input,
|
||||
|
||||
@@ -483,7 +483,7 @@ impl Session {
|
||||
exec_policy: Arc<ExecPolicyManager>,
|
||||
tx_event: Sender<Event>,
|
||||
agent_status: watch::Sender<AgentStatus>,
|
||||
initial_history: InitialHistory,
|
||||
mut initial_history: InitialHistory,
|
||||
session_source: SessionSource,
|
||||
skills_service: Arc<SkillsService>,
|
||||
plugins_manager: Arc<PluginsManager>,
|
||||
@@ -515,6 +515,22 @@ impl Session {
|
||||
.parent_thread_id
|
||||
.or_else(|| initial_history.get_resumed_parent_thread_id());
|
||||
session_configuration.parent_thread_id = parent_thread_id;
|
||||
let is_paginated_subagent = matches!(
|
||||
session_configuration.history_mode,
|
||||
ThreadHistoryMode::Paginated
|
||||
) && matches!(
|
||||
session_configuration.thread_source.as_ref(),
|
||||
Some(ThreadSource::Subagent)
|
||||
);
|
||||
if (config.features.enabled(Feature::ItemIds)
|
||||
|| matches!(
|
||||
session_configuration.history_mode,
|
||||
ThreadHistoryMode::Paginated
|
||||
))
|
||||
&& let InitialHistory::Forked(items) = &mut initial_history
|
||||
{
|
||||
Self::assign_missing_rollout_response_item_ids(items);
|
||||
}
|
||||
let multi_agent_version = multi_agent_version.map(OnceLock::from).unwrap_or_default();
|
||||
let initial_multi_agent_version = multi_agent_version.get().copied();
|
||||
|
||||
@@ -599,6 +615,7 @@ impl Session {
|
||||
selected_capability_roots: selected_capability_roots.clone(),
|
||||
multi_agent_version: initial_multi_agent_version,
|
||||
history_mode: session_configuration.history_mode,
|
||||
subagent_history_start_ordinal: None,
|
||||
initial_window_id: initial_auto_compact_window_ids
|
||||
.window_id
|
||||
.to_string(),
|
||||
@@ -612,7 +629,18 @@ impl Session {
|
||||
},
|
||||
},
|
||||
};
|
||||
LiveThread::create(Arc::clone(&thread_store), params).await?
|
||||
if is_paginated_subagent
|
||||
&& let InitialHistory::Forked(items) = &initial_history
|
||||
{
|
||||
LiveThread::create_with_inherited_model_context(
|
||||
Arc::clone(&thread_store),
|
||||
params,
|
||||
items,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
LiveThread::create(Arc::clone(&thread_store), params).await?
|
||||
}
|
||||
}
|
||||
InitialHistory::Resumed(resumed_history) => {
|
||||
let params = ResumeThreadParams {
|
||||
|
||||
@@ -4142,6 +4142,7 @@ async fn attach_thread_persistence(session: &mut Session) -> PathBuf {
|
||||
selected_capability_roots: Vec::new(),
|
||||
multi_agent_version: None,
|
||||
history_mode: Default::default(),
|
||||
subagent_history_start_ordinal: None,
|
||||
initial_window_id: Uuid::now_v7().to_string(),
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: Some(config.cwd.to_path_buf()),
|
||||
@@ -6903,6 +6904,7 @@ async fn shutdown_complete_does_not_append_to_thread_store_after_shutdown() {
|
||||
selected_capability_roots: Vec::new(),
|
||||
multi_agent_version: None,
|
||||
history_mode: Default::default(),
|
||||
subagent_history_start_ordinal: None,
|
||||
initial_window_id: Uuid::now_v7().to_string(),
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: Some(config.cwd.to_path_buf()),
|
||||
@@ -6980,6 +6982,7 @@ async fn submission_loop_channel_close_runs_full_thread_teardown() {
|
||||
selected_capability_roots: Vec::new(),
|
||||
multi_agent_version: None,
|
||||
history_mode: Default::default(),
|
||||
subagent_history_start_ordinal: None,
|
||||
initial_window_id: Uuid::now_v7().to_string(),
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: Some(config.cwd.to_path_buf()),
|
||||
@@ -9210,6 +9213,7 @@ async fn attach_in_memory_thread_store(
|
||||
selected_capability_roots: Vec::new(),
|
||||
multi_agent_version: None,
|
||||
history_mode: Default::default(),
|
||||
subagent_history_start_ordinal: None,
|
||||
initial_window_id: Uuid::now_v7().to_string(),
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: Some(config.cwd.to_path_buf()),
|
||||
|
||||
Reference in New Issue
Block a user