mirror of
https://github.com/openai/codex.git
synced 2026-10-23 18:08:45 +00:00
Preserve the multi-agent version when forking at a turn cutoff (#43540)
## Why A fork cutoff can remove the only `TurnContext` recording the source thread's selected multi-agent version. Forking before the first turn must preserve that version, including when the child is resumed before its first turn. ## What changed Recover the version from the untrimmed source history or loaded parent, independently of permission overrides, and carry it into the fork's session metadata for `last_turn_id` and `before_turn_id` cutoffs. ## Testing Add regression coverage for forks before the first turn across legacy and paginated history modes, with loaded and unloaded parents and explicit permission overrides. Verify that the child persists `MultiAgentVersion::V2` and still uses the `collaboration` tools after restarting and resuming. GitOrigin-RevId: d7e30cf8bfdeb4f755fefab3d1d0884964d7c977
This commit is contained in:
@@ -4825,7 +4825,7 @@ impl ThreadRequestProcessor {
|
||||
.name
|
||||
.as_deref()
|
||||
.and_then(codex_core::util::normalize_thread_name);
|
||||
let prepared_fork = if paginated_source {
|
||||
let mut prepared_fork = if paginated_source {
|
||||
let boundary = match (last_turn_id.as_deref(), before_turn_id.as_deref()) {
|
||||
(Some(turn_id), None) => {
|
||||
codex_thread_store::ForkBoundary::ThroughTurn(turn_id.to_string())
|
||||
@@ -4928,8 +4928,9 @@ impl ThreadRequestProcessor {
|
||||
!has_permission_override(request_overrides.as_ref(), &typesafe_overrides);
|
||||
let needs_latest_settings =
|
||||
restore_approval_policy || restore_approvals_reviewer || restore_permission_profile;
|
||||
let loaded_parent = self.thread_manager.get_thread(source_thread_id).await.ok();
|
||||
let loaded_parent_settings = if paginated_source && needs_latest_settings {
|
||||
if let Ok(parent) = self.thread_manager.get_thread(source_thread_id).await {
|
||||
if let Some(parent) = loaded_parent.as_ref() {
|
||||
let snapshot = parent.thread_settings_snapshot().await;
|
||||
Some(PersistedResumeSettings {
|
||||
approval_policy: snapshot.approval_policy,
|
||||
@@ -4943,11 +4944,10 @@ impl ThreadRequestProcessor {
|
||||
None
|
||||
};
|
||||
let latest_context = if paginated_source
|
||||
&& needs_latest_settings
|
||||
&& loaded_parent_settings.is_none()
|
||||
&& loaded_parent.is_none()
|
||||
&& (last_turn_id.is_some() || before_turn_id.is_some())
|
||||
{
|
||||
Some(
|
||||
Some(Arc::new(
|
||||
self.thread_store
|
||||
.load_latest_model_context(StoreLoadThreadHistoryParams {
|
||||
thread_id: source_thread_id,
|
||||
@@ -4956,10 +4956,23 @@ impl ThreadRequestProcessor {
|
||||
.await
|
||||
.map_err(thread_store_resume_read_error)?
|
||||
.items,
|
||||
)
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// The fork cutoff can remove the only TurnContext that records the selected version.
|
||||
// Recover it from the untrimmed source or live parent, independently of permission overrides.
|
||||
let source_multi_agent_version = InitialHistory::Resumed(ResumedHistory {
|
||||
conversation_id: source_thread_id,
|
||||
history: Arc::clone(latest_context.as_ref().unwrap_or(&source_history_items)),
|
||||
rollout_path: source_thread.rollout_path.clone(),
|
||||
})
|
||||
.get_multi_agent_version()
|
||||
.or_else(|| {
|
||||
loaded_parent
|
||||
.as_ref()
|
||||
.and_then(|parent| parent.multi_agent_version())
|
||||
});
|
||||
let persisted_settings = loaded_parent_settings.or_else(|| {
|
||||
latest_persisted_resume_settings(
|
||||
latest_context
|
||||
@@ -4992,7 +5005,7 @@ impl ThreadRequestProcessor {
|
||||
let parent_trace = self.request_trace_context(&request_id).await;
|
||||
let thread_source = thread_source.map(Into::into);
|
||||
|
||||
let history_items = if prepared_fork.is_some() {
|
||||
let mut history_items = if prepared_fork.is_some() {
|
||||
source_history_items
|
||||
} else {
|
||||
let source_history_items = Arc::unwrap_or_clone(source_history_items);
|
||||
@@ -5010,6 +5023,21 @@ impl ThreadRequestProcessor {
|
||||
};
|
||||
Arc::new(history_items)
|
||||
};
|
||||
if let Some(multi_agent_version) = source_multi_agent_version
|
||||
&& (last_turn_id.is_some() || before_turn_id.is_some())
|
||||
{
|
||||
// Update only the fork's in-memory metadata; the source rollout stays unchanged.
|
||||
for item in Arc::make_mut(&mut history_items) {
|
||||
if let RolloutItem::SessionMeta(meta) = item
|
||||
&& meta.meta.id == source_thread_id
|
||||
{
|
||||
meta.meta.multi_agent_version = Some(multi_agent_version);
|
||||
}
|
||||
}
|
||||
if let Some(prepared_fork) = prepared_fork.as_mut() {
|
||||
prepared_fork.model_context = Arc::clone(&history_items);
|
||||
}
|
||||
}
|
||||
|
||||
let ephemeral_preview = if ephemeral {
|
||||
if paginated_source && last_turn_id.is_none() && before_turn_id.is_none() {
|
||||
|
||||
Reference in New Issue
Block a user