Track root turns across delegated Codex requests (#38232)

## Why

`parent_turn_id` identifies the immediate caller of a nested request, but not the
top-level turn that initiated a multi-level chain of work.

## What changed

- Add `root_turn_id` to turn state, submissions, and Responses API metadata.
- Propagate the root through spawned agents, delegated work, review sessions, and
  turn-triggering inter-agent messages.
- Omit the root when background work has no user turn or combined input makes the
  root ambiguous.

## Testing

Added coverage for nested agent resumes, reviews, mailbox delivery, steered input,
and reserved metadata handling.

GitOrigin-RevId: 60057db2ee81f4efefa946a9f63c74ff98b93929
This commit is contained in:
Eddie Chen
2026-08-12 18:11:13 +00:00
committed by copyberry
parent 9df9ff6ad9
commit 1f4ea79853
38 changed files with 470 additions and 65 deletions
@@ -86,7 +86,12 @@ impl Handler {
.await;
let agent_control = session.services.agent_control.clone();
let result = agent_control
.send_input(receiver_thread_id, input_items, Some(turn.sub_id.clone()))
.send_input(
receiver_thread_id,
input_items,
Some(turn.sub_id.clone()),
turn.turn_metadata_state.root_turn_id(),
)
.await
.map_err(|err| collab_agent_error(receiver_thread_id, err));
let status = session
@@ -136,6 +136,7 @@ async fn handle_spawn_agent(
fork_mode: args.fork_context.then_some(SpawnAgentForkMode::FullHistory),
parent_thread_id: Some(session.thread_id),
parent_turn_id: Some(turn.sub_id.clone()),
root_turn_id: turn.turn_metadata_state.root_turn_id(),
environments: Some(step_context.environments.to_selections()),
},
))
@@ -2976,6 +2976,7 @@ async fn multi_agent_v2_wait_agent_accepts_timeout_only_argument() {
/*trigger_turn*/ false,
),
/*parent_turn_id*/ None,
/*root_turn_id*/ None,
)
.await;
@@ -3480,6 +3481,7 @@ async fn multi_agent_v2_wait_agent_returns_summary_for_mailbox_activity() {
/*trigger_turn*/ false,
),
/*parent_turn_id*/ None,
/*root_turn_id*/ None,
)
.await;
@@ -3556,6 +3558,7 @@ async fn multi_agent_v2_wait_agent_returns_for_already_queued_mail() {
/*trigger_turn*/ false,
),
/*parent_turn_id*/ None,
/*root_turn_id*/ None,
)
.await;
@@ -3658,6 +3661,7 @@ async fn multi_agent_v2_wait_agent_wakes_on_any_mailbox_notification() {
/*trigger_turn*/ false,
),
/*parent_turn_id*/ None,
/*root_turn_id*/ None,
)
.await;
@@ -3749,6 +3753,7 @@ async fn multi_agent_v2_wait_agent_does_not_return_completed_content() {
/*trigger_turn*/ false,
),
/*parent_turn_id*/ None,
/*root_turn_id*/ None,
)
.await;
@@ -112,7 +112,13 @@ pub(crate) async fn handle_message_string_tool(
let result = session
.services
.agent_control
.send_inter_agent_communication(receiver_thread_id, communication, context, parent_turn_id)
.send_inter_agent_communication(
receiver_thread_id,
communication,
context,
parent_turn_id,
turn.turn_metadata_state.root_turn_id(),
)
.await
.map_err(|err| collab_agent_error(receiver_thread_id, err));
result?;
@@ -136,6 +136,7 @@ async fn handle_spawn_agent(
fork_mode,
parent_thread_id: Some(session.thread_id),
parent_turn_id: Some(turn.sub_id.clone()),
root_turn_id: turn.turn_metadata_state.root_turn_id(),
environments: Some(step_context.environments.to_selections()),
},
),