mirror of
https://github.com/openai/codex.git
synced 2026-11-13 22:47:20 +00:00
Preserve the resolved multi-agent version when reverting threads (#43000)
## Why Reverting a paginated thread can remove the turn context that records its resolved multi-agent version. Preserve that version so a model-selected V2 thread keeps its collaboration tools after reload or a server restart. ## What changed Pass the live thread's `multi_agent_version` into the thread store and persist it in the replacement rollout's session metadata. When no live version is supplied, retain the existing metadata value. Document this guarantee for `thread/revert`. ## Testing Add regression tests for model-selected V2 across live reload and cold resume, plus metadata preservation for `V1`, `V2`, and `Disabled` across repeated reverts. GitOrigin-RevId: bd7d4d76a50d23da16caa2a0631acf10bfa49629
This commit is contained in:
1 parent
531f3836a1
commit
a7a4321593
6 files changed
+186
-2
No files matched your search
@@ -23,6 +23,7 @@ pub(super) async fn revert(
|
||||
let RevertThreadParams {
|
||||
thread_id,
|
||||
before_turn_id,
|
||||
multi_agent_version,
|
||||
} = params;
|
||||
let state_db = store
|
||||
.state_db()
|
||||
@@ -48,7 +49,7 @@ pub(super) async fn revert(
|
||||
.await?
|
||||
.ok_or(ThreadStoreError::ThreadNotFound { thread_id })?;
|
||||
let source_path = current_rollout.path;
|
||||
let source_meta = codex_rollout::read_session_meta_line(source_path.as_path())
|
||||
let mut source_meta = codex_rollout::read_session_meta_line(source_path.as_path())
|
||||
.await
|
||||
.map_err(|err| ThreadStoreError::Internal {
|
||||
message: format!(
|
||||
@@ -106,6 +107,7 @@ pub(super) async fn revert(
|
||||
cutoff.min(history_base.map_or(0, |base| base.end_ordinal_exclusive))
|
||||
});
|
||||
|
||||
source_meta.multi_agent_version = multi_agent_version.or(source_meta.multi_agent_version);
|
||||
let rollout_id = ThreadId::new();
|
||||
let recorder = create_replacement_recorder(
|
||||
store,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::MultiAgentVersion;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_protocol::protocol::ThreadMemoryMode;
|
||||
@@ -72,6 +73,7 @@ async fn revert_keeps_thread_id_and_hides_suffix_across_repeated_reverts() {
|
||||
.revert_thread(RevertThreadParams {
|
||||
thread_id,
|
||||
before_turn_id: "turn-2".to_string(),
|
||||
multi_agent_version: None,
|
||||
})
|
||||
.await
|
||||
.expect("revert before second turn");
|
||||
@@ -98,6 +100,7 @@ async fn revert_keeps_thread_id_and_hides_suffix_across_repeated_reverts() {
|
||||
.revert_thread(RevertThreadParams {
|
||||
thread_id,
|
||||
before_turn_id: "turn-1".to_string(),
|
||||
multi_agent_version: None,
|
||||
})
|
||||
.await
|
||||
.expect("revert before first turn");
|
||||
@@ -134,6 +137,80 @@ async fn revert_keeps_thread_id_and_hides_suffix_across_repeated_reverts() {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn revert_preserves_resolved_multi_agent_version_in_session_metadata() {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let config = test_config(home.path());
|
||||
let state_db = codex_state::StateRuntime::init(
|
||||
config.sqlite.clone(),
|
||||
config.default_model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
.expect("initialize state database");
|
||||
let store = LocalThreadStore::new(config, Some(state_db.clone()));
|
||||
|
||||
for version in [
|
||||
MultiAgentVersion::V1,
|
||||
MultiAgentVersion::V2,
|
||||
MultiAgentVersion::Disabled,
|
||||
] {
|
||||
let thread_id = ThreadId::new();
|
||||
create_paginated_thread(&store, thread_id).await;
|
||||
store
|
||||
.append_items(AppendThreadItemsParams {
|
||||
thread_id,
|
||||
items: vec![
|
||||
turn_started("turn-1"),
|
||||
turn_completed("turn-1"),
|
||||
turn_started("turn-2"),
|
||||
turn_completed("turn-2"),
|
||||
],
|
||||
})
|
||||
.await
|
||||
.expect("append turns");
|
||||
let path = store
|
||||
.live_rollout_path(thread_id)
|
||||
.await
|
||||
.expect("source rollout path");
|
||||
store
|
||||
.shutdown_thread(thread_id)
|
||||
.await
|
||||
.expect("close source writer");
|
||||
codex_rollout::state_db::reconcile_rollout(
|
||||
Some(state_db.as_ref()),
|
||||
path.as_path(),
|
||||
"test-provider",
|
||||
/*builder*/ None,
|
||||
&[],
|
||||
/*archived_only*/ Some(false),
|
||||
/*new_thread_memory_mode*/ None,
|
||||
)
|
||||
.await;
|
||||
|
||||
// The second revert has no live version to supply and must keep the persisted one.
|
||||
for (before_turn_id, multi_agent_version) in [("turn-2", Some(version)), ("turn-1", None)] {
|
||||
store
|
||||
.revert_thread(RevertThreadParams {
|
||||
thread_id,
|
||||
before_turn_id: before_turn_id.to_string(),
|
||||
multi_agent_version,
|
||||
})
|
||||
.await
|
||||
.expect("revert thread");
|
||||
let path = state_db
|
||||
.get_thread(thread_id)
|
||||
.await
|
||||
.expect("read metadata")
|
||||
.expect("thread metadata")
|
||||
.rollout_path;
|
||||
let meta = codex_rollout::read_session_meta_line(&path)
|
||||
.await
|
||||
.expect("read replacement metadata");
|
||||
assert_eq!(meta.meta.multi_agent_version, Some(version));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn rollout_paths_for_thread(
|
||||
home: &std::path::Path,
|
||||
thread_id: ThreadId,
|
||||
|
||||
Reference in new issue
Block a user