Track parent turns for nested Codex requests (#35835)

## What changed

- Propagate the initiating turn ID through agent spawns, follow-up tasks, reviews, and delegated Codex sessions.
- Add `parent_turn_id` to Responses client and turn metadata while keeping it out of external MCP metadata.
- Preserve parent-turn provenance across queued agent messages when their triggering parent is unambiguous.

## Testing

- Cover spawned, resumed, nested, reviewed, delegated, and WebSocket request metadata.
- Verify queued messages do not claim ambiguous or queue-only parent turns.

GitOrigin-RevId: 481fdebbe7df2031880fe259509273cce50b20a8
This commit is contained in:
Eddie Chen authored and copyberry committed 2026-07-28 21:58:33 +00:00
1 parent 12b3e88028
commit 1def0a8925
34 files changed
+635 -165

No files matched your search

+22 -4
View File
@@ -69,6 +69,11 @@ const TASK_COMPACT_METRIC: &str = "codex.task.compact";
pub(crate) type SessionTaskResult = CodexResult<Option<String>>;
pub(crate) enum MailboxParentProvenance {
Ignore,
Attribute,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum InterruptedTurnHistoryMarker {
Disabled,
@@ -319,7 +324,8 @@ impl Session {
) {
self.abort_all_tasks(TurnAbortReason::Replaced).await;
self.clear_connector_selection().await;
self.start_task(turn_context, input, task).await;
self.start_task(turn_context, input, task, MailboxParentProvenance::Ignore)
.await;
}
pub(crate) async fn start_task<T: SessionTask>(
@@ -327,6 +333,7 @@ impl Session {
turn_context: Arc<TurnContext>,
input: Vec<TurnInput>,
task: T,
mailbox_parent_provenance: MailboxParentProvenance,
) {
let task: Arc<dyn AnySessionTask> = Arc::new(task);
let task_kind = task.kind();
@@ -350,7 +357,13 @@ impl Session {
.await
.clear_turn(&turn_context.sub_id);
let pending_items = self.input_queue.get_pending_input(&self.active_turn).await;
let (pending_items, parent_turn_id) =
self.input_queue.get_pending_input(&self.active_turn).await;
if let (MailboxParentProvenance::Attribute, Some(id)) =
(mailbox_parent_provenance, parent_turn_id)
{
turn_context.turn_metadata_state.set_parent_turn_id(id);
}
let turn_state = {
let mut active = self.active_turn.lock().await;
let turn = active.get_or_insert_with(ActiveTurn::default);
@@ -502,8 +515,13 @@ impl Session {
let turn_context = self.new_default_turn_with_sub_id(sub_id).await;
self.maybe_emit_model_warnings_for_turn(turn_context.as_ref())
.await;
self.start_task(turn_context, Vec::new(), RegularTask::new())
.await;
self.start_task(
turn_context,
Vec::new(),
RegularTask::new(),
MailboxParentProvenance::Attribute,
)
.await;
}
pub async fn abort_all_tasks(self: &Arc<Self>, reason: TurnAbortReason) {