diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 73ea42d760..b44a360364 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -454,6 +454,7 @@ fn subagent_thread_started_review_serializes_expected_shape() { let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request( SubAgentThreadStartedInput { thread_id: "thread-review".to_string(), + parent_thread_id: None, product_client_id: "codex-tui".to_string(), client_name: "codex-tui".to_string(), client_version: "1.0.0".to_string(), @@ -496,6 +497,7 @@ fn subagent_thread_started_thread_spawn_serializes_parent_thread_id() { let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request( SubAgentThreadStartedInput { thread_id: "thread-spawn".to_string(), + parent_thread_id: None, product_client_id: "codex-tui".to_string(), client_name: "codex-tui".to_string(), client_version: "1.0.0".to_string(), @@ -526,6 +528,7 @@ fn subagent_thread_started_memory_consolidation_serializes_expected_shape() { let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request( SubAgentThreadStartedInput { thread_id: "thread-memory".to_string(), + parent_thread_id: None, product_client_id: "codex-tui".to_string(), client_name: "codex-tui".to_string(), client_version: "1.0.0".to_string(), @@ -550,6 +553,7 @@ fn subagent_thread_started_other_serializes_expected_shape() { let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request( SubAgentThreadStartedInput { thread_id: "thread-guardian".to_string(), + parent_thread_id: None, product_client_id: "codex-tui".to_string(), client_name: "codex-tui".to_string(), client_version: "1.0.0".to_string(), @@ -562,6 +566,31 @@ fn subagent_thread_started_other_serializes_expected_shape() { let payload = serde_json::to_value(&event).expect("serialize other subagent event"); assert_eq!(payload["event_params"]["subagent_source"], "guardian"); + assert_eq!(payload["event_params"]["parent_thread_id"], json!(null)); +} + +#[test] +fn subagent_thread_started_other_serializes_explicit_parent_thread_id() { + let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request( + SubAgentThreadStartedInput { + thread_id: "thread-guardian".to_string(), + parent_thread_id: Some("parent-thread-guardian".to_string()), + product_client_id: "codex-tui".to_string(), + client_name: "codex-tui".to_string(), + client_version: "1.0.0".to_string(), + model: "gpt-5".to_string(), + ephemeral: false, + subagent_source: SubAgentSource::Other("guardian".to_string()), + created_at: 126, + }, + )); + + let payload = serde_json::to_value(&event).expect("serialize guardian subagent event"); + assert_eq!(payload["event_params"]["subagent_source"], "guardian"); + assert_eq!( + payload["event_params"]["parent_thread_id"], + "parent-thread-guardian" + ); } #[tokio::test] @@ -574,6 +603,7 @@ async fn subagent_thread_started_publishes_without_initialize() { AnalyticsFact::Custom(CustomAnalyticsFact::SubAgentThreadStarted( SubAgentThreadStartedInput { thread_id: "thread-review".to_string(), + parent_thread_id: None, product_client_id: "codex-tui".to_string(), client_name: "codex-tui".to_string(), client_version: "1.0.0".to_string(), diff --git a/codex-rs/analytics/src/events.rs b/codex-rs/analytics/src/events.rs index 885e93bbb9..d2e59f8608 100644 --- a/codex-rs/analytics/src/events.rs +++ b/codex-rs/analytics/src/events.rs @@ -249,7 +249,9 @@ pub(crate) fn subagent_thread_started_event_request( thread_source: Some("subagent"), initialization_mode: ThreadInitializationMode::New, subagent_source: Some(subagent_source_name(&input.subagent_source)), - parent_thread_id: subagent_parent_thread_id(&input.subagent_source), + parent_thread_id: input + .parent_thread_id + .or_else(|| subagent_parent_thread_id(&input.subagent_source)), created_at: input.created_at, }; ThreadInitializedEvent { diff --git a/codex-rs/analytics/src/facts.rs b/codex-rs/analytics/src/facts.rs index e19d15d847..e55309fe31 100644 --- a/codex-rs/analytics/src/facts.rs +++ b/codex-rs/analytics/src/facts.rs @@ -54,6 +54,7 @@ pub struct AppInvocation { #[derive(Clone)] pub struct SubAgentThreadStartedInput { pub thread_id: String, + pub parent_thread_id: Option, pub product_client_id: String, pub client_name: String, pub client_version: String, diff --git a/codex-rs/core/src/agent/control.rs b/codex-rs/core/src/agent/control.rs index 529615a7a5..d60d1a9fff 100644 --- a/codex-rs/core/src/agent/control.rs +++ b/codex-rs/core/src/agent/control.rs @@ -283,6 +283,7 @@ impl AgentControl { .analytics_events_client, client_metadata, new_thread.thread_id, + /*parent_thread_id*/ None, thread_config, subagent_source.clone(), ); diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 1d38280fa9..2dea5e3a3e 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -4582,6 +4582,7 @@ pub(crate) fn emit_subagent_session_started( analytics_events_client: &AnalyticsEventsClient, client_metadata: AppServerClientMetadata, thread_id: ThreadId, + parent_thread_id: Option, thread_config: ThreadConfigSnapshot, subagent_source: SubAgentSource, ) { @@ -4599,6 +4600,7 @@ pub(crate) fn emit_subagent_session_started( .as_secs(); analytics_events_client.track_subagent_thread_started(SubAgentThreadStartedInput { thread_id: thread_id.to_string(), + parent_thread_id: parent_thread_id.map(|thread_id| thread_id.to_string()), product_client_id: client_name.clone(), client_name, client_version, diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index 3d74773ba7..1e07c6d1ff 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -104,6 +104,7 @@ pub(crate) async fn run_codex_thread_interactive( &parent_session.services.analytics_events_client, client_metadata, codex.session.conversation_id, + Some(parent_session.conversation_id), thread_config, subagent_source, ); diff --git a/codex-rs/core/src/memories/phase2.rs b/codex-rs/core/src/memories/phase2.rs index fde135d5e2..203a19075c 100644 --- a/codex-rs/core/src/memories/phase2.rs +++ b/codex-rs/core/src/memories/phase2.rs @@ -156,6 +156,7 @@ pub(super) async fn run(session: &Arc, config: Arc) { &session.services.analytics_events_client, client_metadata, thread_id, + /*parent_thread_id*/ None, thread_config, SubAgentSource::MemoryConsolidation, );