[codex] Move pending input into input queue (#22728)

## Why

Pending model input was split across `Session`, `TurnState`, and the
agent mailbox. That made it easy for new paths to manage queued user
input or mailbox delivery outside the intended ownership boundary.

This PR consolidates the model-facing input lifecycle behind the session
input queue so turn-local pending input, next-turn queued items, and
mailbox delivery coordination are owned in one place.

## What Changed

- Added `session/input_queue.rs` to own pending input queues and mailbox
delivery coordination.
- Removed the standalone `agent/mailbox.rs` channel wrapper and store
mailbox items directly in the input queue.
- Moved pending-input mutations off `TurnState`; `TurnState` now exposes
the queue-owned storage directly for now.
- Routed abort cleanup, mailbox delivery phase changes, next-turn queued
items, and active-turn pending input through `InputQueue`.
- Boxed stack-heavy agent resume/fork startup futures that the refactor
pushed over the default test stack.
- Updated session, task, goal, stream-event, and multi-agent call sites
and tests to use the new queue ownership.

## Verification

- `cargo test -p codex-core --lib agent::control::tests`
- `cargo test -p codex-core --lib
agent::control::tests::resume_closed_child_reopens_open_descendants --
--exact`
- `cargo test -p codex-core --lib
agent::control::tests::spawn_agent_fork_last_n_turns_keeps_only_recent_turns
-- --exact`
- `cargo test -p codex-core --lib
agent::control::tests::resume_thread_subagent_restores_stored_nickname_and_role
-- --exact`
- `cargo test -p codex-core` was also run; it completed with 1814
passed, 4 ignored, and one timeout in
`agent::control::tests::resume_thread_subagent_restores_stored_nickname_and_role`,
which passed when rerun in isolation.
This commit is contained in:
pakrym-oai
2026-05-18 15:43:01 -07:00
committed by GitHub
Unverified
parent a66e0e9c4b
commit afa0101ae2
18 changed files with 623 additions and 524 deletions
@@ -2791,13 +2791,16 @@ async fn multi_agent_v2_wait_agent_accepts_timeout_only_argument() {
});
tokio::task::yield_now().await;
session.enqueue_mailbox_communication(InterAgentCommunication::new(
worker_path,
AgentPath::root(),
Vec::new(),
"hello from worker".to_string(),
/*trigger_turn*/ false,
));
session
.input_queue
.enqueue_mailbox_communication(InterAgentCommunication::new(
worker_path,
AgentPath::root(),
Vec::new(),
"hello from worker".to_string(),
/*trigger_turn*/ false,
))
.await;
let output = wait_task
.await
@@ -3276,13 +3279,16 @@ async fn multi_agent_v2_wait_agent_returns_summary_for_mailbox_activity() {
});
tokio::task::yield_now().await;
session.enqueue_mailbox_communication(InterAgentCommunication::new(
worker_path,
AgentPath::root(),
Vec::new(),
"completed".to_string(),
/*trigger_turn*/ false,
));
session
.input_queue
.enqueue_mailbox_communication(InterAgentCommunication::new(
worker_path,
AgentPath::root(),
Vec::new(),
"completed".to_string(),
/*trigger_turn*/ false,
))
.await;
let wait_output = wait_task
.await
@@ -3346,13 +3352,16 @@ async fn multi_agent_v2_wait_agent_returns_for_already_queued_mail() {
.agent_path
.expect("worker path");
session.enqueue_mailbox_communication(InterAgentCommunication::new(
worker_path,
AgentPath::root(),
Vec::new(),
"already queued".to_string(),
/*trigger_turn*/ false,
));
session
.input_queue
.enqueue_mailbox_communication(InterAgentCommunication::new(
worker_path,
AgentPath::root(),
Vec::new(),
"already queued".to_string(),
/*trigger_turn*/ false,
))
.await;
let output = timeout(
Duration::from_millis(500),
@@ -3442,13 +3451,16 @@ async fn multi_agent_v2_wait_agent_wakes_on_any_mailbox_notification() {
});
tokio::task::yield_now().await;
session.enqueue_mailbox_communication(InterAgentCommunication::new(
worker_b_path,
AgentPath::root(),
Vec::new(),
"from worker b".to_string(),
/*trigger_turn*/ false,
));
session
.input_queue
.enqueue_mailbox_communication(InterAgentCommunication::new(
worker_b_path,
AgentPath::root(),
Vec::new(),
"from worker b".to_string(),
/*trigger_turn*/ false,
))
.await;
let output = wait_task
.await
@@ -3527,13 +3539,16 @@ async fn multi_agent_v2_wait_agent_does_not_return_completed_content() {
});
tokio::task::yield_now().await;
session.enqueue_mailbox_communication(InterAgentCommunication::new(
worker_path,
AgentPath::root(),
Vec::new(),
"sensitive child output".to_string(),
/*trigger_turn*/ false,
));
session
.input_queue
.enqueue_mailbox_communication(InterAgentCommunication::new(
worker_path,
AgentPath::root(),
Vec::new(),
"sensitive child output".to_string(),
/*trigger_turn*/ false,
))
.await;
let output = wait_task
.await
@@ -60,7 +60,7 @@ impl ToolExecutor<ToolInvocation> for Handler {
None => default_timeout_ms,
};
let mut mailbox_seq_rx = session.subscribe_mailbox_seq();
let mut mailbox_rx = session.input_queue.subscribe_mailbox().await;
session
.send_event(
@@ -76,12 +76,8 @@ impl ToolExecutor<ToolInvocation> for Handler {
)
.await;
let timed_out = if session.has_pending_mailbox_items().await {
false
} else {
let deadline = Instant::now() + Duration::from_millis(timeout_ms as u64);
!wait_for_mailbox_change(&mut mailbox_seq_rx, deadline).await
};
let deadline = Instant::now() + Duration::from_millis(timeout_ms as u64);
let timed_out = !wait_for_mailbox_change(&mut mailbox_rx, deadline).await;
let result = WaitAgentResult::from_timed_out(timed_out);
session
@@ -153,10 +149,10 @@ impl ToolOutput for WaitAgentResult {
}
async fn wait_for_mailbox_change(
mailbox_seq_rx: &mut tokio::sync::watch::Receiver<u64>,
mailbox_rx: &mut tokio::sync::watch::Receiver<()>,
deadline: Instant,
) -> bool {
match timeout_at(deadline, mailbox_seq_rx.changed()).await {
match timeout_at(deadline, mailbox_rx.changed()).await {
Ok(Ok(())) => true,
Ok(Err(_)) | Err(_) => false,
}