Skip to content

Commit 3cc3763

Browse files
authored
core: box multi-agent wrapper futures (#19059)
## Why While debugging the Windows stack overflows we saw in [#13429](#13429) and then again in [#18893](#18893), I hit another overflow in `tools::handlers::multi_agents::tests::tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed`. That test drives the legacy multi-agent spawn / close / resume path. The behavior was fine, but several thin async wrappers were still inlining much larger `AgentControl` futures into their callers, which was enough to overflow the default Windows stack. ## What - Box the thin `AgentControl` wrappers around `spawn_agent_internal`, `resume_single_agent_from_rollout`, and `shutdown_agent_tree`. - Box the corresponding legacy `multi_agents` handler calls in `spawn`, `resume_agent`, and `close_agent`. - Keep behavior unchanged while reducing future size on this call path so the Windows test no longer overflows its stack. ## Testing - `cargo test -p codex-core --lib tools::handlers::multi_agents::tests::tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed -- --exact --nocapture` - `cargo test -p codex-core` (this still hit unrelated local integration-test failures because `codex.exe` / `test_stdio_server.exe` were not present in this shell; the relevant unit tests passed)
1 parent 0e78ce8 commit 3cc3763

4 files changed

Lines changed: 56 additions & 56 deletions

File tree

codex-rs/core/src/agent/control.rs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,14 @@ impl AgentControl {
163163
initial_operation: Op,
164164
session_source: Option<SessionSource>,
165165
) -> CodexResult<ThreadId> {
166-
Ok(self
167-
.spawn_agent_internal(
168-
config,
169-
initial_operation,
170-
session_source,
171-
SpawnAgentOptions::default(),
172-
)
173-
.await?
174-
.thread_id)
166+
let spawned_agent = Box::pin(self.spawn_agent_internal(
167+
config,
168+
initial_operation,
169+
session_source,
170+
SpawnAgentOptions::default(),
171+
))
172+
.await?;
173+
Ok(spawned_agent.thread_id)
175174
}
176175

177176
/// Spawn an agent thread with some metadata.
@@ -182,7 +181,7 @@ impl AgentControl {
182181
session_source: Option<SessionSource>,
183182
options: SpawnAgentOptions, // TODO(jif) drop with new fork.
184183
) -> CodexResult<LiveAgent> {
185-
self.spawn_agent_internal(config, initial_operation, session_source, options)
184+
Box::pin(self.spawn_agent_internal(config, initial_operation, session_source, options))
186185
.await
187186
}
188187

@@ -418,9 +417,12 @@ impl AgentControl {
418417
session_source: SessionSource,
419418
) -> CodexResult<ThreadId> {
420419
let root_depth = thread_spawn_depth(&session_source).unwrap_or(0);
421-
let resumed_thread_id = self
422-
.resume_single_agent_from_rollout(config.clone(), thread_id, session_source)
423-
.await?;
420+
let resumed_thread_id = Box::pin(self.resume_single_agent_from_rollout(
421+
config.clone(),
422+
thread_id,
423+
session_source,
424+
))
425+
.await?;
424426
let state = self.upgrade()?;
425427
let Ok(resumed_thread) = state.get_thread(resumed_thread_id).await else {
426428
return Ok(resumed_thread_id);
@@ -698,7 +700,7 @@ impl AgentControl {
698700
{
699701
warn!("failed to persist thread-spawn edge status for {agent_id}: {err}");
700702
}
701-
self.shutdown_agent_tree(agent_id).await
703+
Box::pin(self.shutdown_agent_tree(agent_id)).await
702704
}
703705

704706
/// Shut down `agent_id` and any live descendants reachable from the in-memory spawn tree.

codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,7 @@ impl ToolHandler for Handler {
6666
return Err(collab_agent_error(agent_id, err));
6767
}
6868
};
69-
let result = session
70-
.services
71-
.agent_control
72-
.close_agent(agent_id)
69+
let result = Box::pin(session.services.agent_control.close_agent(agent_id))
7370
.await
7471
.map_err(|err| collab_agent_error(agent_id, err))
7572
.map(|_| ());

codex-rs/core/src/tools/handlers/multi_agents/resume_agent.rs

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,14 @@ impl ToolHandler for Handler {
6161
.get_status(receiver_thread_id)
6262
.await;
6363
let (receiver_agent, error) = if matches!(status, AgentStatus::NotFound) {
64-
match try_resume_closed_agent(&session, &turn, receiver_thread_id, child_depth).await {
64+
match Box::pin(try_resume_closed_agent(
65+
&session,
66+
&turn,
67+
receiver_thread_id,
68+
child_depth,
69+
))
70+
.await
71+
{
6572
Ok(()) => {
6673
status = session
6774
.services
@@ -149,21 +156,18 @@ async fn try_resume_closed_agent(
149156
child_depth: i32,
150157
) -> Result<(), FunctionCallError> {
151158
let config = build_agent_resume_config(turn.as_ref(), child_depth)?;
152-
session
153-
.services
154-
.agent_control
155-
.resume_agent_from_rollout(
156-
config,
157-
receiver_thread_id,
158-
thread_spawn_source(
159-
session.conversation_id,
160-
&turn.session_source,
161-
child_depth,
162-
/*agent_role*/ None,
163-
/*task_name*/ None,
164-
)?,
165-
)
166-
.await
167-
.map(|_| ())
168-
.map_err(|err| collab_agent_error(receiver_thread_id, err))
159+
Box::pin(session.services.agent_control.resume_agent_from_rollout(
160+
config,
161+
receiver_thread_id,
162+
thread_spawn_source(
163+
session.conversation_id,
164+
&turn.session_source,
165+
child_depth,
166+
/*agent_role*/ None,
167+
/*task_name*/ None,
168+
)?,
169+
))
170+
.await
171+
.map(|_| ())
172+
.map_err(|err| collab_agent_error(receiver_thread_id, err))
169173
}

codex-rs/core/src/tools/handlers/multi_agents/spawn.rs

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -82,26 +82,23 @@ impl ToolHandler for Handler {
8282
apply_spawn_agent_runtime_overrides(&mut config, turn.as_ref())?;
8383
apply_spawn_agent_overrides(&mut config, child_depth);
8484

85-
let result = session
86-
.services
87-
.agent_control
88-
.spawn_agent_with_metadata(
89-
config,
90-
input_items,
91-
Some(thread_spawn_source(
92-
session.conversation_id,
93-
&turn.session_source,
94-
child_depth,
95-
role_name,
96-
/*task_name*/ None,
97-
)?),
98-
SpawnAgentOptions {
99-
fork_parent_spawn_call_id: args.fork_context.then(|| call_id.clone()),
100-
fork_mode: args.fork_context.then_some(SpawnAgentForkMode::FullHistory),
101-
},
102-
)
103-
.await
104-
.map_err(collab_spawn_error);
85+
let result = Box::pin(session.services.agent_control.spawn_agent_with_metadata(
86+
config,
87+
input_items,
88+
Some(thread_spawn_source(
89+
session.conversation_id,
90+
&turn.session_source,
91+
child_depth,
92+
role_name,
93+
/*task_name*/ None,
94+
)?),
95+
SpawnAgentOptions {
96+
fork_parent_spawn_call_id: args.fork_context.then(|| call_id.clone()),
97+
fork_mode: args.fork_context.then_some(SpawnAgentForkMode::FullHistory),
98+
},
99+
))
100+
.await
101+
.map_err(collab_spawn_error);
105102
let (new_thread_id, new_agent_metadata, status) = match &result {
106103
Ok(spawned_agent) => (
107104
Some(spawned_agent.thread_id),

0 commit comments

Comments
 (0)