Add subagent lineage metadata for responsesapi (#24161)

## Why

We recently added `forked_from_thread_id` which lets us trace where a
thread's _context_ comes from, but we also want to understand subagent
lineage (e.g. which parent thread spawned this subagent? what kind of
subagent is it?) which is orthogonal.

This PR adds `parent_thread_id` and `subagent_kind` to the
`x-codex-turn-metadata` header sent to ResponsesAPI.

## What changed

- Adds `parent_thread_id` and `subagent_kind` to core-owned
`x-codex-turn-metadata`.
- Restores persisted `SessionSource` and `ThreadSource` from resumed
session metadata so cold-resumed subagent threads keep their lineage on
later Responses API requests.
- Centralizes parent-thread extraction on `SessionSource` /
`SubAgentSource` and reuses it in the Responses client, analytics, agent
control, and state parsing paths.
- Extends reserved-key, git-enrichment, thread-spawn, and app-server v2
metadata coverage for the new lineage fields.

## Verification

- Not run locally per request.
- Added focused coverage in `core/src/turn_metadata_tests.rs` and
`app-server/tests/suite/v2/client_metadata.rs`.
This commit is contained in:
Owen Lin
2026-05-29 11:28:12 -07:00
committed by GitHub
Unverified
parent 62039e8d35
commit fc9cf62efb
11 changed files with 450 additions and 86 deletions
+20 -10
View File
@@ -589,12 +589,12 @@ impl ThreadManager {
options: StartThreadOptions,
forked_from_thread_id: Option<ThreadId>,
) -> CodexResult<NewThread> {
let session_source = options
.session_source
.unwrap_or_else(|| self.state.session_source.clone());
let thread_source = options
.thread_source
.or_else(|| options.initial_history.get_resumed_thread_source());
let (resumed_session_source, resumed_thread_source) = options
.initial_history
.get_resumed_session_sources()
.unwrap_or_else(|| (self.state.session_source.clone(), None));
let session_source = options.session_source.unwrap_or(resumed_session_source);
let thread_source = options.thread_source.or(resumed_thread_source);
Box::pin(self.state.spawn_thread_with_source(
options.config,
options.initial_history,
@@ -676,17 +676,22 @@ impl ThreadManager {
self.state.environment_manager.as_ref(),
&config.cwd,
);
let thread_source = initial_history.get_resumed_thread_source();
Box::pin(self.state.spawn_thread(
let (session_source, thread_source) = initial_history
.get_resumed_session_sources()
.unwrap_or_else(|| (self.state.session_source.clone(), None));
Box::pin(self.state.spawn_thread_with_source(
config,
initial_history,
auth_manager,
self.agent_control(),
session_source,
/*forked_from_thread_id*/ None,
thread_source,
Vec::new(),
persist_extended_history,
/*metrics_service_name*/ None,
/*inherited_shell_snapshot*/ None,
/*inherited_exec_policy*/ None,
parent_trace,
environments,
/*user_shell_override*/ None,
@@ -732,17 +737,22 @@ impl ThreadManager {
self.state.environment_manager.as_ref(),
&config.cwd,
);
let thread_source = initial_history.get_resumed_thread_source();
Box::pin(self.state.spawn_thread(
let (session_source, thread_source) = initial_history
.get_resumed_session_sources()
.unwrap_or_else(|| (self.state.session_source.clone(), None));
Box::pin(self.state.spawn_thread_with_source(
config,
initial_history,
auth_manager,
self.agent_control(),
session_source,
/*forked_from_thread_id*/ None,
thread_source,
Vec::new(),
/*persist_extended_history*/ false,
/*metrics_service_name*/ None,
/*inherited_shell_snapshot*/ None,
/*inherited_exec_policy*/ None,
/*parent_trace*/ None,
environments,
/*user_shell_override*/ Some(user_shell_override),