This commit is contained in:
jif-oai
2026-06-01 23:53:45 +01:00
parent f8aa631734
commit 6666a89354
26 changed files with 75 additions and 277 deletions
@@ -180,6 +180,7 @@ fn turn_context_item_filesystem_uses_workspace_roots_instead_of_cwd() {
model: "gpt-5".to_string(),
personality: None,
collaboration_mode: None,
multi_agent_version: None,
realtime_active: None,
effort: None,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
@@ -132,6 +132,7 @@ fn reference_context_item() -> TurnContextItem {
model: "gpt-test".to_string(),
personality: None,
collaboration_mode: None,
multi_agent_version: None,
realtime_active: Some(false),
effort: None,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
+3 -27
View File
@@ -138,7 +138,6 @@ use codex_thread_store::LiveThreadInitGuard;
use codex_thread_store::LocalThreadStore;
use codex_thread_store::ReadThreadParams;
use codex_thread_store::ResumeThreadParams;
use codex_thread_store::SetMultiAgentVersionIfUnsetParams;
use codex_thread_store::ThreadEventPersistenceMode;
use codex_thread_store::ThreadPersistenceMetadata;
use codex_thread_store::ThreadStore;
@@ -1670,7 +1669,7 @@ impl Session {
turn_context: &TurnContext,
msg: &EventMsg,
) {
if turn_context.multi_agent_version != Some(MultiAgentVersion::V2) {
if turn_context.multi_agent_version != MultiAgentVersion::V2 {
return;
}
@@ -2652,7 +2651,7 @@ impl Session {
*self.multi_agent_version.get_or_init(|| multi_agent_version)
}
pub(crate) async fn resolve_multi_agent_version_for_model(
pub(crate) fn resolve_multi_agent_version_for_model(
&self,
model_info: &ModelInfo,
config: &Config,
@@ -2665,30 +2664,7 @@ impl Session {
.multi_agent_version
.unwrap_or_else(|| config.multi_agent_version_from_features());
let stored = if config.ephemeral {
selected
} else {
match self
.services
.thread_store
.set_multi_agent_version_if_unset(SetMultiAgentVersionIfUnsetParams {
thread_id: self.thread_id(),
multi_agent_version: selected,
})
.await
{
Ok(multi_agent_version) => multi_agent_version,
Err(err) => {
warn!(
"failed to set multi-agent version for thread {}: {err}",
self.thread_id()
);
selected
}
}
};
self.set_multi_agent_version_if_unset(stored)
self.set_multi_agent_version_if_unset(selected)
}
async fn send_raw_response_items(&self, turn_context: &TurnContext, items: &[ResponseItem]) {
+1 -1
View File
@@ -7,7 +7,7 @@ pub(super) fn usage_hint_text<'a>(
turn_context: &'a TurnContext,
session_source: &SessionSource,
) -> Option<&'a str> {
if turn_context.multi_agent_version != Some(MultiAgentVersion::V2) {
if turn_context.multi_agent_version != MultiAgentVersion::V2 {
return None;
}
@@ -72,6 +72,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
model: previous_model.to_string(),
personality: turn_context.personality,
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
multi_agent_version: None,
realtime_active: Some(turn_context.realtime_active),
effort: turn_context.reasoning_effort,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
@@ -110,6 +111,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
model: previous_model.to_string(),
personality: turn_context.personality,
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
multi_agent_version: None,
realtime_active: Some(turn_context.realtime_active),
effort: turn_context.reasoning_effort,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
@@ -959,6 +961,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
model: previous_model.to_string(),
personality: turn_context.personality,
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
multi_agent_version: None,
realtime_active: Some(turn_context.realtime_active),
effort: turn_context.reasoning_effort,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
@@ -1037,6 +1040,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
model: previous_model.to_string(),
personality: turn_context.personality,
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
multi_agent_version: None,
realtime_active: Some(turn_context.realtime_active),
effort: turn_context.reasoning_effort,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
@@ -1065,6 +1069,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
model: previous_model.to_string(),
personality: turn_context.personality,
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
multi_agent_version: None,
realtime_active: Some(turn_context.realtime_active),
effort: turn_context.reasoning_effort,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
@@ -1183,6 +1188,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
model: current_model.to_string(),
personality: turn_context.personality,
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
multi_agent_version: None,
realtime_active: Some(turn_context.realtime_active),
effort: turn_context.reasoning_effort,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
@@ -1300,6 +1306,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
model: previous_model.to_string(),
personality: turn_context.personality,
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
multi_agent_version: None,
realtime_active: Some(turn_context.realtime_active),
effort: turn_context.reasoning_effort,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
@@ -1458,6 +1465,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
model: previous_model.to_string(),
personality: turn_context.personality,
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
multi_agent_version: None,
realtime_active: Some(turn_context.realtime_active),
effort: turn_context.reasoning_effort,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
+3 -2
View File
@@ -2540,6 +2540,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
model: previous_model.to_string(),
personality: turn_context.personality,
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
multi_agent_version: None,
realtime_active: Some(turn_context.realtime_active),
effort: turn_context.reasoning_effort,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
@@ -4787,7 +4788,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
&session_telemetry,
session_configuration.provider.clone(),
&session_configuration,
Some(config.multi_agent_version_from_features()),
config.multi_agent_version_from_features(),
services.user_shell.as_ref(),
services.shell_zsh_path.as_ref(),
services.main_execve_wrapper_exe.as_ref(),
@@ -6640,7 +6641,7 @@ where
&session_telemetry,
session_configuration.provider.clone(),
&session_configuration,
Some(config.multi_agent_version_from_features()),
config.multi_agent_version_from_features(),
services.user_shell.as_ref(),
services.shell_zsh_path.as_ref(),
services.main_execve_wrapper_exe.as_ref(),
+12 -10
View File
@@ -80,7 +80,7 @@ pub struct TurnContext {
pub(crate) compact_prompt: Option<String>,
pub(crate) user_instructions: Option<String>,
pub(crate) collaboration_mode: CollaborationMode,
pub(crate) multi_agent_version: Option<MultiAgentVersion>,
pub(crate) multi_agent_version: MultiAgentVersion,
pub(crate) personality: Option<Personality>,
pub(crate) approval_policy: Constrained<AskForApproval>,
pub(crate) permission_profile: PermissionProfile,
@@ -106,8 +106,8 @@ pub struct TurnContext {
}
enum TurnMultiAgentRuntime {
Resolve,
Unset,
ResolveAndStore,
Preview,
}
impl TurnContext {
@@ -363,6 +363,7 @@ impl TurnContext {
model: self.model_info.slug.clone(),
personality: self.personality,
collaboration_mode: Some(self.collaboration_mode.clone()),
multi_agent_version: Some(self.multi_agent_version),
realtime_active: Some(self.realtime_active),
effort: self.reasoning_effort,
summary: ReasoningSummaryConfig::Auto,
@@ -464,7 +465,7 @@ impl Session {
session_telemetry: &SessionTelemetry,
provider: ModelProviderInfo,
session_configuration: &SessionConfiguration,
multi_agent_version: Option<MultiAgentVersion>,
multi_agent_version: MultiAgentVersion,
user_shell: &shell::Shell,
shell_zsh_path: Option<&PathBuf>,
main_execve_wrapper_exe: Option<&PathBuf>,
@@ -701,7 +702,7 @@ impl Session {
session_configuration,
final_output_json_schema,
turn_environments,
TurnMultiAgentRuntime::Resolve,
TurnMultiAgentRuntime::ResolveAndStore,
)
.await
}
@@ -717,7 +718,7 @@ impl Session {
session_configuration,
/*final_output_json_schema*/ None,
turn_environments,
TurnMultiAgentRuntime::Unset,
TurnMultiAgentRuntime::Preview,
)
.await
}
@@ -753,11 +754,12 @@ impl Session {
.await;
let multi_agent_version = match multi_agent_runtime {
TurnMultiAgentRuntime::Resolve => Some(
TurnMultiAgentRuntime::ResolveAndStore => {
self.resolve_multi_agent_version_for_model(&model_info, &per_turn_config)
.await,
),
TurnMultiAgentRuntime::Unset => None,
}
TurnMultiAgentRuntime::Preview => model_info
.multi_agent_version
.unwrap_or_else(|| per_turn_config.multi_agent_version_from_features()),
};
let plugin_outcome = self
.services
+2 -2
View File
@@ -73,12 +73,12 @@ pub(crate) enum InterruptedTurnHistoryMarker {
impl InterruptedTurnHistoryMarker {
pub(crate) fn from_config_and_version(
config: &Config,
multi_agent_version: Option<MultiAgentVersion>,
multi_agent_version: MultiAgentVersion,
) -> Self {
if !config.agent_interrupt_message_enabled {
return Self::Disabled;
}
if multi_agent_version == Some(MultiAgentVersion::V2) {
if multi_agent_version == MultiAgentVersion::V2 {
Self::Developer
} else {
Self::ContextualUser
+3 -2
View File
@@ -664,7 +664,7 @@ impl ThreadManager {
history,
InterruptedTurnHistoryMarker::from_config_and_version(
&options.config,
Some(inherited_multi_agent_version),
inherited_multi_agent_version,
),
);
self.start_thread_with_options_and_fork_source(
@@ -942,7 +942,8 @@ impl ThreadManager {
};
let interrupted_marker = InterruptedTurnHistoryMarker::from_config_and_version(
&config,
resolve_multi_agent_version(&history, /*inherited_multi_agent_version*/ None),
resolve_multi_agent_version(&history, /*inherited_multi_agent_version*/ None)
.unwrap_or(MultiAgentVersion::V1),
);
let history = fork_history_from_snapshot(snapshot, history, interrupted_marker);
let environments = default_thread_environment_selections(
@@ -111,11 +111,7 @@ async fn build_runner_options(
turn: &Arc<TurnContext>,
requested_concurrency: Option<usize>,
) -> Result<JobRunnerOptions, FunctionCallError> {
let multi_agent_version = turn.multi_agent_version.ok_or_else(|| {
FunctionCallError::RespondToModel(
"multi-agent version is unresolved; this session cannot spawn workers".to_string(),
)
})?;
let multi_agent_version = turn.multi_agent_version;
if multi_agent_version == MultiAgentVersion::Disabled {
return Err(FunctionCallError::RespondToModel(
"multi-agent runtime is disabled; this session cannot spawn workers".to_string(),
@@ -109,9 +109,7 @@ async fn handle_spawn_agent(
)
.await?;
apply_spawn_agent_runtime_overrides(&mut config, turn.as_ref())?;
let multi_agent_version = turn
.multi_agent_version
.ok_or_else(|| FunctionCallError::Fatal("multi-agent version is unresolved".to_string()))?;
let multi_agent_version = turn.multi_agent_version;
let result = Box::pin(session.services.agent_control.spawn_agent_with_metadata(
config,
@@ -135,7 +135,7 @@ model_reasoning_effort = "minimal"
}
fn set_turn_config(turn: &mut TurnContext, config: crate::config::Config) {
turn.multi_agent_version = Some(config.multi_agent_version_from_features());
turn.multi_agent_version = config.multi_agent_version_from_features();
turn.config = Arc::new(config);
}
@@ -108,9 +108,7 @@ async fn handle_spawn_agent(
)
.await?;
apply_spawn_agent_runtime_overrides(&mut config, turn.as_ref())?;
let multi_agent_version = turn
.multi_agent_version
.ok_or_else(|| FunctionCallError::Fatal("multi-agent version is unresolved".to_string()))?;
let multi_agent_version = turn.multi_agent_version;
let spawn_source = thread_spawn_source(
session.conversation_id,
+4 -4
View File
@@ -290,17 +290,17 @@ fn namespace_tools_enabled(turn_context: &TurnContext) -> bool {
}
fn multi_agent_v2_enabled(turn_context: &TurnContext) -> bool {
turn_context.multi_agent_version == Some(MultiAgentVersion::V2)
turn_context.multi_agent_version == MultiAgentVersion::V2
}
fn collab_tools_enabled(turn_context: &TurnContext) -> bool {
match turn_context.multi_agent_version {
None | Some(MultiAgentVersion::Disabled) => false,
Some(MultiAgentVersion::V1) => !exceeds_thread_spawn_depth_limit(
MultiAgentVersion::Disabled => false,
MultiAgentVersion::V1 => !exceeds_thread_spawn_depth_limit(
next_thread_spawn_depth(&turn_context.session_source),
turn_context.config.agent_max_depth,
),
Some(MultiAgentVersion::V2) => true,
MultiAgentVersion::V2 => true,
}
}
+1 -1
View File
@@ -215,7 +215,7 @@ fn set_feature(turn: &mut TurnContext, feature: Feature, enabled: bool) {
.disable(feature)
.expect("test feature should be disableable in config");
}
turn.multi_agent_version = Some(config.multi_agent_version_from_features());
turn.multi_agent_version = config.multi_agent_version_from_features();
turn.config = Arc::new(config);
turn.tool_mode = turn.model_info.tool_mode.unwrap_or_else(|| {
if turn.config.features.enabled(Feature::CodeModeOnly) {
@@ -39,6 +39,7 @@ fn resume_history(
model: previous_model.to_string(),
personality: None,
collaboration_mode: None,
multi_agent_version: None,
realtime_active: None,
effort: config.model_reasoning_effort,
summary: config
+16 -2
View File
@@ -2727,7 +2727,7 @@ fn multi_agent_version_from_items(
items: &[RolloutItem],
thread_id: Option<ThreadId>,
) -> Option<MultiAgentVersion> {
items
let session_meta_version = items
.iter()
.rev()
.find_map(|item| match item {
@@ -2738,11 +2738,22 @@ fn multi_agent_version_from_items(
}
_ => None,
})
.and_then(|meta_line| meta_line.meta.multi_agent_version)
.and_then(|meta_line| meta_line.meta.multi_agent_version);
session_meta_version.or_else(|| {
items.iter().rev().find_map(|item| match item {
RolloutItem::TurnContext(turn_context) => turn_context.multi_agent_version,
RolloutItem::SessionMeta(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::EventMsg(_) => None,
})
})
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
pub enum MultiAgentVersion {
Disabled,
V1,
@@ -2891,6 +2902,8 @@ pub struct TurnContextItem {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub collaboration_mode: Option<CollaborationMode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub multi_agent_version: Option<MultiAgentVersion>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub realtime_active: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub effort: Option<ReasoningEffortConfig>,
@@ -5338,6 +5351,7 @@ mod tests {
model: "gpt-5".to_string(),
personality: None,
collaboration_mode: None,
multi_agent_version: None,
realtime_active: None,
effort: None,
summary: ReasoningSummaryConfig::Auto,
+10
View File
@@ -1534,6 +1534,16 @@ impl RolloutWriterState {
}
async fn write_session_meta_if_needed(&mut self) -> std::io::Result<()> {
if let Some(session_meta) = self.meta.as_mut()
&& session_meta.multi_agent_version.is_none()
{
session_meta.multi_agent_version = self.pending_items.iter().find_map(|item| {
let RolloutItem::TurnContext(turn_context) = item else {
return None;
};
turn_context.multi_agent_version
});
}
let Some(session_meta) = self.meta.as_ref().cloned() else {
return Ok(());
};
+1
View File
@@ -1146,6 +1146,7 @@ async fn resume_candidate_matches_cwd_reads_latest_turn_context() -> std::io::Re
model: "test-model".to_string(),
personality: None,
collaboration_mode: None,
multi_agent_version: None,
realtime_active: None,
effort: None,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
+4
View File
@@ -358,6 +358,7 @@ mod tests {
model: "gpt-5".to_string(),
personality: None,
collaboration_mode: None,
multi_agent_version: None,
realtime_active: None,
effort: None,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
@@ -395,6 +396,7 @@ mod tests {
model: "gpt-5".to_string(),
personality: None,
collaboration_mode: None,
multi_agent_version: None,
realtime_active: None,
effort: None,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
@@ -429,6 +431,7 @@ mod tests {
model: "gpt-5".to_string(),
personality: None,
collaboration_mode: None,
multi_agent_version: None,
realtime_active: None,
effort: Some(ReasoningEffort::High),
summary: codex_protocol::config_types::ReasoningSummary::Auto,
@@ -459,6 +462,7 @@ mod tests {
model: "gpt-5".to_string(),
personality: None,
collaboration_mode: None,
multi_agent_version: None,
realtime_active: None,
effort: Some(ReasoningEffort::High),
summary: codex_protocol::config_types::ReasoningSummary::Auto,
-35
View File
@@ -10,7 +10,6 @@ use chrono::Utc;
use codex_protocol::ThreadId;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::MultiAgentVersion;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::SessionMeta;
use codex_protocol::protocol::SessionMetaLine;
@@ -24,7 +23,6 @@ use crate::LoadThreadHistoryParams;
use crate::ReadThreadByRolloutPathParams;
use crate::ReadThreadParams;
use crate::ResumeThreadParams;
use crate::SetMultiAgentVersionIfUnsetParams;
use crate::StoredThread;
use crate::StoredThreadHistory;
use crate::ThreadMetadataPatch;
@@ -212,39 +210,6 @@ impl ThreadStore for InMemoryThreadStore {
Ok(())
}
async fn set_multi_agent_version_if_unset(
&self,
params: SetMultiAgentVersionIfUnsetParams,
) -> ThreadStoreResult<MultiAgentVersion> {
let mut state = self.state.lock().await;
let history =
state
.histories
.get_mut(&params.thread_id)
.ok_or(ThreadStoreError::ThreadNotFound {
thread_id: params.thread_id,
})?;
let mut session_meta = history
.iter()
.rev()
.find_map(|item| match item {
RolloutItem::SessionMeta(meta_line) if meta_line.meta.id == params.thread_id => {
Some(meta_line.clone())
}
_ => None,
})
.ok_or_else(|| ThreadStoreError::InvalidRequest {
message: format!("thread {} does not have session metadata", params.thread_id),
})?;
if let Some(multi_agent_version) = session_meta.meta.multi_agent_version {
return Ok(multi_agent_version);
}
session_meta.git = None;
session_meta.meta.multi_agent_version = Some(params.multi_agent_version);
history.push(RolloutItem::SessionMeta(session_meta));
Ok(params.multi_agent_version)
}
async fn append_items(&self, params: AppendThreadItemsParams) -> ThreadStoreResult<()> {
let mut state = self.state.lock().await;
state.calls.append_items += 1;
-1
View File
@@ -35,7 +35,6 @@ pub use types::ReadThreadByRolloutPathParams;
pub use types::ReadThreadParams;
pub use types::ResumeThreadParams;
pub use types::SearchThreadsParams;
pub use types::SetMultiAgentVersionIfUnsetParams;
pub use types::SortDirection;
pub use types::StoredThread;
pub use types::StoredThreadHistory;
-73
View File
@@ -5,7 +5,6 @@ mod list_threads;
mod live_writer;
mod read_thread;
mod search_threads;
mod set_multi_agent_version_if_unset;
mod unarchive_thread;
mod update_thread_metadata;
@@ -21,7 +20,6 @@ use std::collections::hash_map::Entry;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::sync::Semaphore;
use crate::AppendThreadItemsParams;
use crate::ArchiveThreadParams;
@@ -32,7 +30,6 @@ use crate::ReadThreadByRolloutPathParams;
use crate::ReadThreadParams;
use crate::ResumeThreadParams;
use crate::SearchThreadsParams;
use crate::SetMultiAgentVersionIfUnsetParams;
use crate::StoredThread;
use crate::StoredThreadHistory;
use crate::ThreadPage;
@@ -41,7 +38,6 @@ use crate::ThreadStore;
use crate::ThreadStoreError;
use crate::ThreadStoreResult;
use crate::UpdateThreadMetadataParams;
use codex_protocol::protocol::MultiAgentVersion;
/// Local filesystem/SQLite-backed implementation of [`ThreadStore`].
///
@@ -60,7 +56,6 @@ use codex_protocol::protocol::MultiAgentVersion;
pub struct LocalThreadStore {
pub(super) config: LocalThreadStoreConfig,
live_recorders: Arc<Mutex<HashMap<ThreadId, RolloutRecorder>>>,
multi_agent_version_update_gate: Arc<Semaphore>,
state_db: Option<StateDbHandle>,
}
@@ -100,7 +95,6 @@ impl LocalThreadStore {
Self {
config,
live_recorders: Arc::new(Mutex::new(HashMap::new())),
multi_agent_version_update_gate: Arc::new(Semaphore::new(1)),
state_db,
}
}
@@ -186,13 +180,6 @@ impl ThreadStore for LocalThreadStore {
live_writer::resume_thread(self, params).await
}
async fn set_multi_agent_version_if_unset(
&self,
params: SetMultiAgentVersionIfUnsetParams,
) -> ThreadStoreResult<MultiAgentVersion> {
set_multi_agent_version_if_unset::set_multi_agent_version_if_unset(self, params).await
}
async fn append_items(&self, params: AppendThreadItemsParams) -> ThreadStoreResult<()> {
live_writer::append_items(self, params).await
}
@@ -773,44 +760,6 @@ mod tests {
assert!(err.to_string().contains("already has a live local writer"));
}
#[tokio::test]
async fn set_multi_agent_version_if_unset_appends_once_and_returns_existing_value() {
let home = TempDir::new().expect("temp dir");
let store = LocalThreadStore::new(test_config(home.path()), /*state_db*/ None);
let thread_id = ThreadId::default();
store
.create_thread(create_thread_params(thread_id))
.await
.expect("create thread");
let first = store
.set_multi_agent_version_if_unset(SetMultiAgentVersionIfUnsetParams {
thread_id,
multi_agent_version: MultiAgentVersion::V2,
})
.await
.expect("set missing runtime");
assert_eq!(first, MultiAgentVersion::V2);
assert_eq!(
session_meta_versions(&store, thread_id).await,
vec![None, Some(MultiAgentVersion::V2)]
);
let second = store
.set_multi_agent_version_if_unset(SetMultiAgentVersionIfUnsetParams {
thread_id,
multi_agent_version: MultiAgentVersion::V1,
})
.await
.expect("return existing runtime");
assert_eq!(second, MultiAgentVersion::V2);
assert_eq!(
session_meta_versions(&store, thread_id).await,
vec![None, Some(MultiAgentVersion::V2)]
);
}
#[tokio::test]
async fn resume_thread_rejects_duplicate_live_writer() {
let home = TempDir::new().expect("temp dir");
@@ -1104,28 +1053,6 @@ mod tests {
}))
}
async fn session_meta_versions(
store: &LocalThreadStore,
thread_id: ThreadId,
) -> Vec<Option<MultiAgentVersion>> {
store
.load_history(LoadThreadHistoryParams {
thread_id,
include_archived: true,
})
.await
.expect("load history")
.items
.into_iter()
.filter_map(|item| match item {
RolloutItem::SessionMeta(meta_line) if meta_line.meta.id == thread_id => {
Some(meta_line.meta.multi_agent_version)
}
_ => None,
})
.collect()
}
async fn assert_rollout_contains_message(path: &std::path::Path, expected: &str) {
let (items, _, _) = RolloutRecorder::load_rollout_items(path)
.await
@@ -1,88 +0,0 @@
use codex_protocol::protocol::MultiAgentVersion;
use codex_protocol::protocol::RolloutItem;
use codex_rollout::append_rollout_item_to_path;
use super::LocalThreadStore;
use super::live_writer;
use super::read_thread;
use crate::AppendThreadItemsParams;
use crate::ReadThreadParams;
use crate::SetMultiAgentVersionIfUnsetParams;
use crate::ThreadStoreError;
use crate::ThreadStoreResult;
pub(super) async fn set_multi_agent_version_if_unset(
store: &LocalThreadStore,
params: SetMultiAgentVersionIfUnsetParams,
) -> ThreadStoreResult<MultiAgentVersion> {
let _permit = store
.multi_agent_version_update_gate
.acquire()
.await
.map_err(|err| ThreadStoreError::Internal {
message: format!("failed to set thread multi-agent version: {err}"),
})?;
let has_live_writer = live_writer::rollout_path(store, params.thread_id)
.await
.is_ok();
if has_live_writer {
live_writer::persist_thread(store, params.thread_id).await?;
live_writer::flush_thread(store, params.thread_id).await?;
}
let thread = read_thread::read_thread(
store,
ReadThreadParams {
thread_id: params.thread_id,
include_archived: true,
include_history: true,
},
)
.await?;
let rollout_path = thread
.rollout_path
.ok_or_else(|| ThreadStoreError::Internal {
message: format!("thread {} does not have a rollout path", params.thread_id),
})?;
let history = thread.history.ok_or_else(|| ThreadStoreError::Internal {
message: format!("failed to load history for thread {}", params.thread_id),
})?;
let mut session_meta = history
.items
.iter()
.rev()
.find_map(|item| match item {
RolloutItem::SessionMeta(meta_line) if meta_line.meta.id == params.thread_id => {
Some(meta_line.clone())
}
_ => None,
})
.ok_or_else(|| ThreadStoreError::InvalidRequest {
message: format!("thread {} does not have session metadata", params.thread_id),
})?;
if let Some(multi_agent_version) = session_meta.meta.multi_agent_version {
return Ok(multi_agent_version);
}
session_meta.git = None;
session_meta.meta.multi_agent_version = Some(params.multi_agent_version);
let item = RolloutItem::SessionMeta(session_meta);
if has_live_writer {
live_writer::append_items(
store,
AppendThreadItemsParams {
thread_id: params.thread_id,
items: vec![item],
},
)
.await?;
} else {
append_rollout_item_to_path(&rollout_path, &item)
.await
.map_err(|err| ThreadStoreError::Internal {
message: format!("failed to set thread multi-agent version: {err}"),
})?;
}
Ok(params.multi_agent_version)
}
-8
View File
@@ -14,7 +14,6 @@ use crate::ReadThreadByRolloutPathParams;
use crate::ReadThreadParams;
use crate::ResumeThreadParams;
use crate::SearchThreadsParams;
use crate::SetMultiAgentVersionIfUnsetParams;
use crate::StoredThread;
use crate::StoredThreadHistory;
use crate::ThreadPage;
@@ -23,7 +22,6 @@ use crate::ThreadStoreError;
use crate::ThreadStoreResult;
use crate::TurnPage;
use crate::UpdateThreadMetadataParams;
use codex_protocol::protocol::MultiAgentVersion;
/// Storage-neutral thread persistence boundary.
#[async_trait]
@@ -37,12 +35,6 @@ pub trait ThreadStore: Any + Send + Sync {
/// Reopens an existing thread for live appends.
async fn resume_thread(&self, params: ResumeThreadParams) -> ThreadStoreResult<()>;
/// Sets a thread's multi-agent runtime if it is unset, returning the stored value.
async fn set_multi_agent_version_if_unset(
&self,
params: SetMultiAgentVersionIfUnsetParams,
) -> ThreadStoreResult<MultiAgentVersion>;
/// Appends canonical rollout items to a live thread.
///
/// This is the raw history API. It does not infer metadata from item contents. Callers that
-9
View File
@@ -66,15 +66,6 @@ pub struct ThreadPersistenceMetadata {
pub memory_mode: MemoryMode,
}
/// Parameters for setting a thread's multi-agent runtime when it has not been recorded yet.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SetMultiAgentVersionIfUnsetParams {
/// Thread id whose metadata should be initialized.
pub thread_id: ThreadId,
/// Version to persist if the thread does not already have one.
pub multi_agent_version: MultiAgentVersion,
}
/// Parameters required to create a persisted thread.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CreateThreadParams {