Add ThreadManager sample crate (#20141)

Summary:
- Add codex-thread-manager-sample, a one-shot binary that starts a
ThreadManager thread, submits a prompt, and prints the final assistant
output.
- Pass ThreadStore into ThreadManager::new and expose
thread_store_from_config for existing callsites.
- Build the sample Config directly with only --model and prompt inputs.

Verification:
- just fmt
- cargo check -p codex-thread-manager-sample -p codex-app-server -p
codex-mcp-server
- git diff --check

Tests: Not run per request.
This commit is contained in:
pakrym-oai
2026-04-29 11:21:06 -07:00
committed by GitHub
parent 47fba5df4a
commit 8356806fc9
33 changed files with 737 additions and 94 deletions
+20 -7
View File
@@ -12,7 +12,9 @@ use crate::session::emit_subagent_session_started;
use crate::session_prefix::format_subagent_context_line;
use crate::session_prefix::format_subagent_notification_message;
use crate::shell_snapshot::ShellSnapshot;
use crate::thread_manager::ResumeThreadFromRolloutOptions;
use crate::thread_manager::ThreadManagerState;
use crate::thread_manager::thread_store_from_config;
use crate::thread_rollout_truncation::truncate_rollout_to_last_n_fork_turns;
use codex_features::Feature;
use codex_protocol::AgentPath;
@@ -232,7 +234,8 @@ impl AgentControl {
(Some(session_source), None) => {
state
.spawn_new_thread_with_source(
config,
config.clone(),
thread_store_from_config(&config),
self.clone(),
session_source,
/*persist_extended_history*/ false,
@@ -243,7 +246,15 @@ impl AgentControl {
)
.await?
}
(None, _) => state.spawn_new_thread(config, self.clone()).await?,
(None, _) => {
state
.spawn_new_thread(
config.clone(),
thread_store_from_config(&config),
self.clone(),
)
.await?
}
};
agent_metadata.agent_id = Some(new_thread.thread_id);
reservation.commit(agent_metadata.clone());
@@ -424,7 +435,8 @@ impl AgentControl {
state
.fork_thread_with_source(
config,
config.clone(),
thread_store_from_config(&config),
InitialHistory::Forked(forked_rollout_items),
self.clone(),
session_source,
@@ -578,14 +590,15 @@ impl AgentControl {
};
let resumed_thread = state
.resume_thread_from_rollout_with_source(
config,
.resume_thread_from_rollout_with_source(ResumeThreadFromRolloutOptions {
config: config.clone(),
thread_store: thread_store_from_config(&config),
rollout_path,
self.clone(),
agent_control: self.clone(),
session_source,
inherited_shell_snapshot,
inherited_exec_policy,
)
})
.await?;
let mut agent_metadata = agent_metadata;
agent_metadata.agent_id = Some(resumed_thread.thread_id);
+11 -4
View File
@@ -7,6 +7,7 @@ use crate::config::Config;
use crate::config::ConfigBuilder;
use crate::context::ContextualUserFragment;
use crate::context::SubagentNotification;
use crate::thread_manager::thread_store_from_config;
use assert_matches::assert_matches;
use codex_features::Feature;
use codex_login::CodexAuth;
@@ -108,7 +109,7 @@ impl AgentControlHarness {
async fn start_thread(&self) -> (ThreadId, Arc<CodexThread>) {
let new_thread = self
.manager
.start_thread(self.config.clone())
.start_thread(self.config.clone(), thread_store_from_config(&self.config))
.await
.expect("start thread");
(new_thread.thread_id, new_thread.thread)
@@ -609,7 +610,10 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
Some("Child subagent guidance.".to_string());
let new_thread = harness
.manager
.start_thread(parent_config)
.start_thread(
parent_config.clone(),
thread_store_from_config(&parent_config),
)
.await
.expect("start parent thread");
let parent_thread_id = new_thread.thread_id;
@@ -952,7 +956,7 @@ async fn spawn_agent_respects_max_threads_limit() {
let control = manager.agent_control();
let _ = manager
.start_thread(config.clone())
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("start thread");
@@ -1309,7 +1313,10 @@ async fn multi_agent_v2_completion_queues_message_for_direct_parent() {
let _ = tester_config.features.enable(Feature::MultiAgentV2);
let tester_thread_id = harness
.manager
.start_thread(tester_config)
.start_thread(
tester_config.clone(),
thread_store_from_config(&tester_config),
)
.await
.expect("tester thread should start")
.thread_id;
@@ -27,6 +27,18 @@ pub struct ManagedFeatures {
pinned_features: BTreeMap<Feature, bool>,
}
impl Default for ManagedFeatures {
fn default() -> Self {
Self {
value: ConstrainedWithSource::new(
Constrained::allow_any(Features::default()),
/*source*/ None,
),
pinned_features: BTreeMap::new(),
}
}
}
impl ManagedFeatures {
pub(crate) fn from_configured(
configured_features: Features,
+1
View File
@@ -120,6 +120,7 @@ pub use thread_manager::NewThread;
pub use thread_manager::StartThreadOptions;
pub use thread_manager::ThreadManager;
pub use thread_manager::build_models_manager;
pub use thread_manager::thread_store_from_config;
pub use web_search::web_search_action_detail;
pub use web_search::web_search_detail;
pub use windows_sandbox_read_grants::grant_read_root_non_elevated;
+3 -1
View File
@@ -19,6 +19,7 @@ use crate::session::session::Session;
use crate::session::turn::build_prompt;
use crate::session::turn::built_tools;
use crate::thread_manager::ThreadManager;
use crate::thread_manager::thread_store_from_config;
/// Build the model-visible `input` list for a single debug turn.
#[doc(hidden)]
@@ -48,7 +49,8 @@ pub async fn build_prompt_input(
Arc::new(EnvironmentManager::new(EnvironmentManagerArgs::new(local_runtime_paths)).await),
/*analytics_events_client*/ None,
);
let thread = thread_manager.start_thread(config).await?;
let thread_store = thread_store_from_config(&config);
let thread = thread_manager.start_thread(config, thread_store).await?;
let output = build_prompt_input_from_session(thread.thread.codex.session.as_ref(), input).await;
let shutdown = thread.thread.shutdown_and_wait().await;
+4 -1
View File
@@ -1643,7 +1643,10 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
.thread_manager
.fork_thread(
usize::MAX,
fork_config,
fork_config.clone(),
std::sync::Arc::new(codex_thread_store::LocalThreadStore::new(
codex_rollout::RolloutConfig::from_view(&fork_config),
)),
rollout_path,
/*persist_extended_history*/ false,
/*parent_trace*/ None,
+5 -1
View File
@@ -20,6 +20,7 @@ use codex_models_manager::test_support::get_model_offline_for_tests;
use codex_protocol::config_types::CollaborationModeMask;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ModelPreset;
use codex_thread_store::ThreadStore;
use once_cell::sync::Lazy;
use crate::ThreadManager;
@@ -76,16 +77,18 @@ pub fn thread_manager_with_models_provider_and_home(
pub async fn start_thread_with_user_shell_override(
thread_manager: &ThreadManager,
config: Config,
thread_store: Arc<dyn ThreadStore>,
user_shell_override: crate::shell::Shell,
) -> codex_protocol::error::Result<crate::NewThread> {
thread_manager
.start_thread_with_user_shell_override_for_tests(config, user_shell_override)
.start_thread_with_user_shell_override_for_tests(config, thread_store, user_shell_override)
.await
}
pub async fn resume_thread_from_rollout_with_user_shell_override(
thread_manager: &ThreadManager,
config: Config,
thread_store: Arc<dyn ThreadStore>,
rollout_path: PathBuf,
auth_manager: Arc<AuthManager>,
user_shell_override: crate::shell::Shell,
@@ -93,6 +96,7 @@ pub async fn resume_thread_from_rollout_with_user_shell_override(
thread_manager
.resume_thread_from_rollout_with_user_shell_override_for_tests(
config,
thread_store,
rollout_path,
auth_manager,
user_shell_override,
+45 -17
View File
@@ -213,6 +213,7 @@ pub struct ThreadManager {
pub struct StartThreadOptions {
pub config: Config,
pub thread_store: Arc<dyn ThreadStore>,
pub initial_history: InitialHistory,
pub session_source: Option<SessionSource>,
pub dynamic_tools: Vec<codex_protocol::dynamic_tools::DynamicToolSpec>,
@@ -222,6 +223,16 @@ pub struct StartThreadOptions {
pub environments: Vec<TurnEnvironmentSelection>,
}
pub(crate) struct ResumeThreadFromRolloutOptions {
pub(crate) config: Config,
pub(crate) thread_store: Arc<dyn ThreadStore>,
pub(crate) rollout_path: PathBuf,
pub(crate) agent_control: AgentControl,
pub(crate) session_source: SessionSource,
pub(crate) inherited_shell_snapshot: Option<Arc<ShellSnapshot>>,
pub(crate) inherited_exec_policy: Option<Arc<crate::exec_policy::ExecPolicyManager>>,
}
/// Shared, `Arc`-owned state for [`ThreadManager`]. This `Arc` is required to have a single
/// `Arc` reference that can be downgraded to by `AgentControl` while preventing every single
/// function to require an `Arc<&Self>`.
@@ -254,7 +265,7 @@ pub fn build_models_manager(
)
}
fn configured_thread_store(config: &Config) -> Arc<dyn ThreadStore> {
pub fn thread_store_from_config(config: &Config) -> Arc<dyn ThreadStore> {
match &config.experimental_thread_store {
ThreadStoreConfig::Local => {
Arc::new(LocalThreadStore::new(RolloutConfig::from_view(config)))
@@ -520,11 +531,16 @@ impl ThreadManager {
Ok(subtree_thread_ids)
}
pub async fn start_thread(&self, config: Config) -> CodexResult<NewThread> {
pub async fn start_thread(
&self,
config: Config,
thread_store: Arc<dyn ThreadStore>,
) -> CodexResult<NewThread> {
// Box delegated thread-spawn futures so these convenience wrappers do
// not inline the full spawn path into every caller's async state.
Box::pin(self.start_thread_with_tools(
config,
thread_store,
Vec::new(),
/*persist_extended_history*/ false,
))
@@ -534,6 +550,7 @@ impl ThreadManager {
pub async fn start_thread_with_tools(
&self,
config: Config,
thread_store: Arc<dyn ThreadStore>,
dynamic_tools: Vec<codex_protocol::dynamic_tools::DynamicToolSpec>,
persist_extended_history: bool,
) -> CodexResult<NewThread> {
@@ -543,6 +560,7 @@ impl ThreadManager {
);
Box::pin(self.start_thread_with_options(StartThreadOptions {
config,
thread_store,
initial_history: InitialHistory::New,
session_source: None,
dynamic_tools,
@@ -558,13 +576,12 @@ impl ThreadManager {
&self,
options: StartThreadOptions,
) -> CodexResult<NewThread> {
let thread_store = configured_thread_store(&options.config);
let session_source = options
.session_source
.unwrap_or_else(|| self.state.session_source.clone());
Box::pin(self.state.spawn_thread_with_source(
options.config,
thread_store,
options.thread_store,
options.initial_history,
Arc::clone(&self.state.auth_manager),
self.agent_control(),
@@ -584,6 +601,7 @@ impl ThreadManager {
pub async fn resume_thread_from_rollout(
&self,
config: Config,
thread_store: Arc<dyn ThreadStore>,
rollout_path: PathBuf,
auth_manager: Arc<AuthManager>,
parent_trace: Option<W3cTraceContext>,
@@ -591,6 +609,7 @@ impl ThreadManager {
let initial_history = RolloutRecorder::get_rollout_history(&rollout_path).await?;
Box::pin(self.resume_thread_with_history(
config,
thread_store,
initial_history,
auth_manager,
/*persist_extended_history*/ false,
@@ -602,12 +621,12 @@ impl ThreadManager {
pub async fn resume_thread_with_history(
&self,
config: Config,
thread_store: Arc<dyn ThreadStore>,
initial_history: InitialHistory,
auth_manager: Arc<AuthManager>,
persist_extended_history: bool,
parent_trace: Option<W3cTraceContext>,
) -> CodexResult<NewThread> {
let thread_store = configured_thread_store(&config);
let environments = default_thread_environment_selections(
self.state.environment_manager.as_ref(),
&config.cwd,
@@ -631,9 +650,9 @@ impl ThreadManager {
pub(crate) async fn start_thread_with_user_shell_override_for_tests(
&self,
config: Config,
thread_store: Arc<dyn ThreadStore>,
user_shell_override: crate::shell::Shell,
) -> CodexResult<NewThread> {
let thread_store = configured_thread_store(&config);
let environments = default_thread_environment_selections(
self.state.environment_manager.as_ref(),
&config.cwd,
@@ -657,12 +676,12 @@ impl ThreadManager {
pub(crate) async fn resume_thread_from_rollout_with_user_shell_override_for_tests(
&self,
config: Config,
thread_store: Arc<dyn ThreadStore>,
rollout_path: PathBuf,
auth_manager: Arc<AuthManager>,
user_shell_override: crate::shell::Shell,
) -> CodexResult<NewThread> {
let initial_history = RolloutRecorder::get_rollout_history(&rollout_path).await?;
let thread_store = configured_thread_store(&config);
let environments = default_thread_environment_selections(
self.state.environment_manager.as_ref(),
&config.cwd,
@@ -749,6 +768,7 @@ impl ThreadManager {
&self,
snapshot: S,
config: Config,
thread_store: Arc<dyn ThreadStore>,
path: PathBuf,
persist_extended_history: bool,
parent_trace: Option<W3cTraceContext>,
@@ -761,6 +781,7 @@ impl ThreadManager {
self.fork_thread_from_history(
snapshot,
config,
thread_store,
history,
persist_extended_history,
parent_trace,
@@ -773,6 +794,7 @@ impl ThreadManager {
&self,
snapshot: S,
config: Config,
thread_store: Arc<dyn ThreadStore>,
history: InitialHistory,
persist_extended_history: bool,
parent_trace: Option<W3cTraceContext>,
@@ -783,6 +805,7 @@ impl ThreadManager {
self.fork_thread_with_initial_history(
snapshot.into(),
config,
thread_store,
history,
persist_extended_history,
parent_trace,
@@ -794,13 +817,13 @@ impl ThreadManager {
&self,
snapshot: ForkSnapshot,
config: Config,
thread_store: Arc<dyn ThreadStore>,
history: InitialHistory,
persist_extended_history: bool,
parent_trace: Option<W3cTraceContext>,
) -> CodexResult<NewThread> {
let interrupted_marker = InterruptedTurnHistoryMarker::from_config(&config);
let history = fork_history_from_snapshot(snapshot, history, interrupted_marker);
let thread_store = configured_thread_store(&config);
let environments = default_thread_environment_selections(
self.state.environment_manager.as_ref(),
&config.cwd,
@@ -887,10 +910,12 @@ impl ThreadManagerState {
pub(crate) async fn spawn_new_thread(
&self,
config: Config,
thread_store: Arc<dyn ThreadStore>,
agent_control: AgentControl,
) -> CodexResult<NewThread> {
Box::pin(self.spawn_new_thread_with_source(
config,
thread_store,
agent_control,
self.session_source.clone(),
/*persist_extended_history*/ false,
@@ -906,6 +931,7 @@ impl ThreadManagerState {
pub(crate) async fn spawn_new_thread_with_source(
&self,
config: Config,
thread_store: Arc<dyn ThreadStore>,
agent_control: AgentControl,
session_source: SessionSource,
persist_extended_history: bool,
@@ -914,7 +940,6 @@ impl ThreadManagerState {
inherited_exec_policy: Option<Arc<crate::exec_policy::ExecPolicyManager>>,
environments: Option<Vec<TurnEnvironmentSelection>>,
) -> CodexResult<NewThread> {
let thread_store = configured_thread_store(&config);
let environments = environments.unwrap_or_else(|| {
default_thread_environment_selections(self.environment_manager.as_ref(), &config.cwd)
});
@@ -939,15 +964,18 @@ impl ThreadManagerState {
pub(crate) async fn resume_thread_from_rollout_with_source(
&self,
config: Config,
rollout_path: PathBuf,
agent_control: AgentControl,
session_source: SessionSource,
inherited_shell_snapshot: Option<Arc<ShellSnapshot>>,
inherited_exec_policy: Option<Arc<crate::exec_policy::ExecPolicyManager>>,
options: ResumeThreadFromRolloutOptions,
) -> CodexResult<NewThread> {
let ResumeThreadFromRolloutOptions {
config,
thread_store,
rollout_path,
agent_control,
session_source,
inherited_shell_snapshot,
inherited_exec_policy,
} = options;
let initial_history = RolloutRecorder::get_rollout_history(&rollout_path).await?;
let thread_store = configured_thread_store(&config);
let environments =
default_thread_environment_selections(self.environment_manager.as_ref(), &config.cwd);
Box::pin(self.spawn_thread_with_source(
@@ -973,6 +1001,7 @@ impl ThreadManagerState {
pub(crate) async fn fork_thread_with_source(
&self,
config: Config,
thread_store: Arc<dyn ThreadStore>,
initial_history: InitialHistory,
agent_control: AgentControl,
session_source: SessionSource,
@@ -981,7 +1010,6 @@ impl ThreadManagerState {
inherited_exec_policy: Option<Arc<crate::exec_policy::ExecPolicyManager>>,
environments: Option<Vec<TurnEnvironmentSelection>>,
) -> CodexResult<NewThread> {
let thread_store = configured_thread_store(&config);
let environments = environments.unwrap_or_else(|| {
default_thread_environment_selections(self.environment_manager.as_ref(), &config.cwd)
});
+30 -7
View File
@@ -162,7 +162,8 @@ fn fork_thread_accepts_legacy_usize_snapshot_argument() {
) {
let _future = manager.fork_thread(
usize::MAX,
config,
config.clone(),
thread_store_from_config(&config),
path,
/*persist_extended_history*/ false,
/*parent_trace*/ None,
@@ -263,12 +264,12 @@ async fn shutdown_all_threads_bounded_submits_shutdown_to_every_thread() {
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
);
let thread_1 = manager
.start_thread(config.clone())
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("start first thread")
.thread_id;
let thread_2 = manager
.start_thread(config)
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("start second thread")
.thread_id;
@@ -314,6 +315,7 @@ async fn start_thread_accepts_explicit_environment_when_default_environment_is_d
let thread = manager
.start_thread_with_options(StartThreadOptions {
thread_store: thread_store_from_config(&config),
config: config.clone(),
initial_history: InitialHistory::New,
session_source: None,
@@ -346,8 +348,10 @@ async fn start_thread_keeps_internal_threads_hidden_from_normal_lookups() {
config.codex_home.to_path_buf(),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
);
let thread_store = thread_store_from_config(&config);
let thread = manager
.start_thread_with_options(StartThreadOptions {
thread_store,
config,
initial_history: InitialHistory::New,
session_source: Some(SessionSource::Internal(
@@ -399,9 +403,11 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
cwd: selected_cwd.clone(),
}];
let default_cwd = config.cwd.clone();
let thread_store = thread_store_from_config(&config);
let source = manager
.start_thread_with_options(StartThreadOptions {
thread_store: Arc::clone(&thread_store),
config: config.clone(),
initial_history: InitialHistory::New,
session_source: None,
@@ -423,10 +429,17 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
.thread
.rollout_path()
.expect("source rollout path should exist");
source
.thread
.shutdown_and_wait()
.await
.expect("shutdown source thread before resume");
let _ = manager.remove_thread(&source.thread_id).await;
let resumed = manager
.resume_thread_from_rollout(
config.clone(),
Arc::clone(&thread_store),
rollout_path.clone(),
auth_manager,
/*parent_trace*/ None,
@@ -448,6 +461,7 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
.fork_thread(
ForkSnapshot::Interrupted,
config,
thread_store,
rollout_path,
/*persist_extended_history*/ false,
/*parent_trace*/ None,
@@ -704,6 +718,7 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
let source = manager
.resume_thread_with_history(
config.clone(),
thread_store_from_config(&config),
InitialHistory::Forked(vec![
RolloutItem::ResponseItem(user_msg("hello")),
RolloutItem::ResponseItem(assistant_msg("partial")),
@@ -729,7 +744,8 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
let forked = manager
.fork_thread(
ForkSnapshot::Interrupted,
config,
config.clone(),
thread_store_from_config(&config),
source_path,
/*persist_extended_history*/ false,
/*parent_trace*/ None,
@@ -806,6 +822,7 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
let source = manager
.resume_thread_with_history(
config.clone(),
thread_store_from_config(&config),
InitialHistory::Forked(vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-explicit".to_string(),
@@ -842,7 +859,8 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
let forked = manager
.fork_thread(
ForkSnapshot::Interrupted,
config,
config.clone(),
thread_store_from_config(&config),
source_path,
/*persist_extended_history*/ false,
/*parent_trace*/ None,
@@ -897,6 +915,7 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
let source = manager
.resume_thread_with_history(
config.clone(),
thread_store_from_config(&config),
InitialHistory::Forked(vec![
RolloutItem::ResponseItem(user_msg("hello")),
RolloutItem::ResponseItem(assistant_msg("partial")),
@@ -921,6 +940,7 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
.fork_thread(
ForkSnapshot::Interrupted,
config.clone(),
thread_store_from_config(&config),
source_path,
/*persist_extended_history*/ false,
/*parent_trace*/ None,
@@ -960,7 +980,8 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
let reforked = manager
.fork_thread(
ForkSnapshot::Interrupted,
config,
config.clone(),
thread_store_from_config(&config),
forked_path,
/*persist_extended_history*/ false,
/*parent_trace*/ None,
@@ -1033,6 +1054,7 @@ async fn resumed_thread_activates_paused_goal_and_continues_on_request() -> anyh
let source = manager
.resume_thread_with_history(
config.clone(),
thread_store_from_config(&config),
InitialHistory::Forked(vec![RolloutItem::ResponseItem(user_msg("keep working"))]),
auth_manager.clone(),
/*persist_extended_history*/ false,
@@ -1061,7 +1083,8 @@ async fn resumed_thread_activates_paused_goal_and_continues_on_request() -> anyh
let resumed = manager
.resume_thread_from_rollout(
config,
config.clone(),
thread_store_from_config(&config),
source_path,
auth_manager,
/*parent_trace*/ None,
@@ -5,6 +5,7 @@ use crate::config::DEFAULT_AGENT_MAX_DEPTH;
use crate::function_tool::FunctionCallError;
use crate::session::tests::make_session_and_context;
use crate::session_prefix::format_subagent_notification_message;
use crate::thread_manager::thread_store_from_config;
use crate::tools::context::ToolOutput;
use crate::tools::handlers::multi_agents_v2::CloseAgentHandler as CloseAgentHandlerV2;
use crate::tools::handlers::multi_agents_v2::FollowupTaskHandler as FollowupTaskHandlerV2;
@@ -296,7 +297,10 @@ async fn spawn_agent_fork_context_rejects_agent_type_override() {
let role_name = install_role_with_model_override(&mut turn).await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -328,7 +332,10 @@ async fn spawn_agent_fork_context_rejects_child_model_overrides() {
let (mut session, turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -363,7 +370,10 @@ async fn multi_agent_v2_spawn_fork_turns_all_rejects_agent_type_override() {
let role_name = install_role_with_model_override(&mut turn).await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -406,7 +416,10 @@ async fn multi_agent_v2_spawn_defaults_to_full_fork_and_rejects_child_model_over
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -447,7 +460,10 @@ async fn multi_agent_v2_spawn_partial_fork_turns_allows_agent_type_override() {
let role_name = install_role_with_model_override(&mut turn).await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -530,7 +546,10 @@ async fn multi_agent_v2_spawn_requires_task_name() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -564,7 +583,10 @@ async fn multi_agent_v2_spawn_rejects_legacy_items_field() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -624,7 +646,10 @@ async fn multi_agent_v2_spawn_returns_path_and_send_message_accepts_relative_pat
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -721,7 +746,10 @@ async fn multi_agent_v2_spawn_rejects_legacy_fork_context() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -760,7 +788,10 @@ async fn multi_agent_v2_spawn_rejects_invalid_fork_turns_string() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -799,7 +830,10 @@ async fn multi_agent_v2_spawn_rejects_zero_fork_turns() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -838,7 +872,10 @@ async fn multi_agent_v2_send_message_accepts_root_target_from_child() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -914,7 +951,10 @@ async fn multi_agent_v2_followup_task_rejects_root_target_from_child() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -995,7 +1035,10 @@ async fn multi_agent_v2_list_agents_returns_completed_status_and_last_task_messa
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -1089,7 +1132,10 @@ async fn multi_agent_v2_list_agents_filters_by_relative_path_prefix() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -1176,7 +1222,10 @@ async fn multi_agent_v2_list_agents_omits_closed_agents() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -1240,7 +1289,10 @@ async fn multi_agent_v2_send_message_rejects_legacy_items_field() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -1296,7 +1348,10 @@ async fn multi_agent_v2_send_message_rejects_interrupt_parameter() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -1369,7 +1424,10 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn()
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -1504,7 +1562,10 @@ async fn multi_agent_v2_followup_task_rejects_legacy_items_field() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -1557,7 +1618,10 @@ async fn multi_agent_v2_interrupted_turn_does_not_notify_parent() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -1634,7 +1698,10 @@ async fn multi_agent_v2_spawn_omits_agent_id_when_named() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -1673,7 +1740,10 @@ async fn multi_agent_v2_spawn_surfaces_task_name_validation_errors() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -1887,7 +1957,7 @@ async fn multi_agent_v2_spawn_agent_ignores_configured_max_depth() {
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
let root = manager
.start_thread(config.clone())
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -2011,7 +2081,10 @@ async fn send_input_interrupts_before_prompt() {
let manager = thread_manager();
session.services.agent_control = manager.agent_control();
let config = turn.config.as_ref().clone();
let thread = manager.start_thread(config).await.expect("start thread");
let thread = manager
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("start thread");
let agent_id = thread.thread_id;
let invocation = invocation(
Arc::new(session),
@@ -2050,7 +2123,10 @@ async fn send_input_accepts_structured_items() {
let manager = thread_manager();
session.services.agent_control = manager.agent_control();
let config = turn.config.as_ref().clone();
let thread = manager.start_thread(config).await.expect("start thread");
let thread = manager
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("start thread");
let agent_id = thread.thread_id;
let invocation = invocation(
Arc::new(session),
@@ -2142,7 +2218,10 @@ async fn resume_agent_noops_for_active_agent() {
let manager = thread_manager();
session.services.agent_control = manager.agent_control();
let config = turn.config.as_ref().clone();
let thread = manager.start_thread(config).await.expect("start thread");
let thread = manager
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("start thread");
let agent_id = thread.thread_id;
let status_before = manager.agent_control().get_status(agent_id).await;
let invocation = invocation(
@@ -2180,7 +2259,8 @@ async fn resume_agent_restores_closed_agent_and_accepts_send_input() {
let config = turn.config.as_ref().clone();
let thread = manager
.resume_thread_with_history(
config,
config.clone(),
thread_store_from_config(&config),
InitialHistory::Forked(vec![RolloutItem::ResponseItem(ResponseItem::Message {
id: None,
role: "user".to_string(),
@@ -2345,7 +2425,10 @@ async fn multi_agent_v2_wait_agent_accepts_timeout_only_argument() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -2521,7 +2604,10 @@ async fn wait_agent_times_out_when_status_is_not_final() {
let manager = thread_manager();
session.services.agent_control = manager.agent_control();
let config = turn.config.as_ref().clone();
let thread = manager.start_thread(config).await.expect("start thread");
let thread = manager
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("start thread");
let agent_id = thread.thread_id;
let invocation = invocation(
Arc::new(session),
@@ -2561,7 +2647,10 @@ async fn wait_agent_clamps_short_timeouts_to_minimum() {
let manager = thread_manager();
session.services.agent_control = manager.agent_control();
let config = turn.config.as_ref().clone();
let thread = manager.start_thread(config).await.expect("start thread");
let thread = manager
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("start thread");
let agent_id = thread.thread_id;
let invocation = invocation(
Arc::new(session),
@@ -2596,7 +2685,10 @@ async fn wait_agent_returns_final_status_without_timeout() {
let manager = thread_manager();
session.services.agent_control = manager.agent_control();
let config = turn.config.as_ref().clone();
let thread = manager.start_thread(config).await.expect("start thread");
let thread = manager
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("start thread");
let agent_id = thread.thread_id;
let mut status_rx = manager
.agent_control()
@@ -2644,7 +2736,10 @@ async fn multi_agent_v2_wait_agent_returns_summary_for_mailbox_activity() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -2735,7 +2830,10 @@ async fn multi_agent_v2_wait_agent_returns_for_already_queued_mail() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -2813,7 +2911,10 @@ async fn multi_agent_v2_wait_agent_wakes_on_any_mailbox_notification() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -2901,7 +3002,10 @@ async fn multi_agent_v2_wait_agent_does_not_return_completed_content() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -2987,7 +3091,10 @@ async fn multi_agent_v2_close_agent_accepts_task_name_target() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -3046,7 +3153,10 @@ async fn multi_agent_v2_close_agent_rejects_root_target_and_id() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.start_thread(
(*turn.config).clone(),
thread_store_from_config(turn.config.as_ref()),
)
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
@@ -3095,7 +3205,10 @@ async fn close_agent_submits_shutdown_and_returns_previous_status() {
let manager = thread_manager();
session.services.agent_control = manager.agent_control();
let config = turn.config.as_ref().clone();
let thread = manager.start_thread(config).await.expect("start thread");
let thread = manager
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("start thread");
let agent_id = thread.thread_id;
let status_before = manager.agent_control().get_status(agent_id).await;
@@ -3137,7 +3250,7 @@ async fn tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtr
.expect("test config should allow sqlite");
let parent = manager
.start_thread(config.clone())
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("parent thread should start");
let parent_thread_id = parent.thread_id;
@@ -3268,7 +3381,7 @@ async fn tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtr
);
let operator = manager
.start_thread(config)
.start_thread(config.clone(), thread_store_from_config(&config))
.await
.expect("operator thread should start");
let operator_session = operator.thread.codex.session.clone();