From 16d3cb7fbe19e29a0fe3cfe21171db565b8aaa2a Mon Sep 17 00:00:00 2001 From: Michael Musson Date: Mon, 4 May 2026 05:34:41 +0000 Subject: [PATCH] Add session id to Codex analytics events Co-authored-by: Codex --- .../analytics/src/analytics_client_tests.rs | 17 ++++++++++++ codex-rs/analytics/src/events.rs | 8 ++++++ codex-rs/analytics/src/facts.rs | 1 + codex-rs/analytics/src/reducer.rs | 26 +++++++++++++++---- codex-rs/core/src/agent/control.rs | 1 + codex-rs/core/src/codex_delegate.rs | 1 + codex-rs/core/src/session/mod.rs | 2 ++ 7 files changed, 51 insertions(+), 5 deletions(-) diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index bba6e2014f..4bb5ed8c4e 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -746,6 +746,7 @@ fn compaction_event_serializes_expected_shape() { }, sample_app_server_client_metadata(), sample_runtime_metadata(), + "session-1".to_string(), Some("user"), /*subagent_source*/ None, /*parent_thread_id*/ None, @@ -759,6 +760,7 @@ fn compaction_event_serializes_expected_shape() { json!({ "event_type": "codex_compaction_event", "event_params": { + "session_id": "session-1", "thread_id": "thread-1", "turn_id": "turn-1", "app_server_client": { @@ -829,6 +831,7 @@ fn thread_initialized_event_serializes_expected_shape() { let event = TrackEventRequest::ThreadInitialized(ThreadInitializedEvent { event_type: "codex_thread_initialized", event_params: ThreadInitializedEventParams { + session_id: "session-0".to_string(), thread_id: "thread-0".to_string(), app_server_client: CodexAppServerClientMetadata { product_client_id: DEFAULT_ORIGINATOR.to_string(), @@ -860,6 +863,7 @@ fn thread_initialized_event_serializes_expected_shape() { json!({ "event_type": "codex_thread_initialized", "event_params": { + "session_id": "session-0", "thread_id": "thread-0", "app_server_client": { "product_client_id": DEFAULT_ORIGINATOR, @@ -1249,6 +1253,10 @@ async fn guardian_review_event_ingests_custom_fact_with_optional_target_item() { let payload = serde_json::to_value(&events).expect("serialize events"); assert_eq!(payload.as_array().expect("events array").len(), 1); assert_eq!(payload[0]["event_type"], "codex_guardian_review"); + assert_eq!( + payload[0]["event_params"]["session_id"], + "session-thread-guardian" + ); assert_eq!(payload[0]["event_params"]["thread_id"], "thread-guardian"); assert_eq!(payload[0]["event_params"]["turn_id"], "turn-guardian"); assert_eq!(payload[0]["event_params"]["review_id"], "review-guardian"); @@ -1295,6 +1303,7 @@ async fn guardian_review_event_ingests_custom_fact_with_optional_target_item() { fn subagent_thread_started_review_serializes_expected_shape() { let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request( SubAgentThreadStartedInput { + session_id: "session-root".to_string(), thread_id: "thread-review".to_string(), parent_thread_id: None, product_client_id: "codex-tui".to_string(), @@ -1338,6 +1347,7 @@ fn subagent_thread_started_thread_spawn_serializes_parent_thread_id() { .expect("valid thread id"); let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request( SubAgentThreadStartedInput { + session_id: "session-root".to_string(), thread_id: "thread-spawn".to_string(), parent_thread_id: None, product_client_id: "codex-tui".to_string(), @@ -1369,6 +1379,7 @@ fn subagent_thread_started_thread_spawn_serializes_parent_thread_id() { fn subagent_thread_started_memory_consolidation_serializes_expected_shape() { let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request( SubAgentThreadStartedInput { + session_id: "session-root".to_string(), thread_id: "thread-memory".to_string(), parent_thread_id: None, product_client_id: "codex-tui".to_string(), @@ -1394,6 +1405,7 @@ fn subagent_thread_started_memory_consolidation_serializes_expected_shape() { fn subagent_thread_started_other_serializes_expected_shape() { let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request( SubAgentThreadStartedInput { + session_id: "session-root".to_string(), thread_id: "thread-guardian".to_string(), parent_thread_id: None, product_client_id: "codex-tui".to_string(), @@ -1415,6 +1427,7 @@ fn subagent_thread_started_other_serializes_expected_shape() { fn subagent_thread_started_other_serializes_explicit_parent_thread_id() { let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request( SubAgentThreadStartedInput { + session_id: "session-root".to_string(), thread_id: "thread-guardian".to_string(), parent_thread_id: Some("parent-thread-guardian".to_string()), product_client_id: "codex-tui".to_string(), @@ -1444,6 +1457,7 @@ async fn subagent_thread_started_publishes_without_initialize() { .ingest( AnalyticsFact::Custom(CustomAnalyticsFact::SubAgentThreadStarted( SubAgentThreadStartedInput { + session_id: "session-root".to_string(), thread_id: "thread-review".to_string(), parent_thread_id: None, product_client_id: "codex-tui".to_string(), @@ -1517,6 +1531,7 @@ async fn subagent_thread_started_inherits_parent_connection_for_new_thread() { .ingest( AnalyticsFact::Custom(CustomAnalyticsFact::SubAgentThreadStarted( SubAgentThreadStartedInput { + session_id: "session-root".to_string(), thread_id: "thread-review".to_string(), parent_thread_id: None, product_client_id: "parent-client".to_string(), @@ -1968,6 +1983,7 @@ fn turn_event_serializes_expected_shape() { let event = TrackEventRequest::TurnEvent(Box::new(CodexTurnEventRequest { event_type: "codex_turn_event", event_params: crate::events::CodexTurnEventParams { + session_id: "session-2".to_string(), thread_id: "thread-2".to_string(), turn_id: "turn-2".to_string(), app_server_client: sample_app_server_client_metadata(), @@ -2018,6 +2034,7 @@ fn turn_event_serializes_expected_shape() { r#"{ "event_type": "codex_turn_event", "event_params": { + "session_id": "session-2", "thread_id": "thread-2", "turn_id": "turn-2", "submission_type": null, diff --git a/codex-rs/analytics/src/events.rs b/codex-rs/analytics/src/events.rs index 8bd9440299..b75c388863 100644 --- a/codex-rs/analytics/src/events.rs +++ b/codex-rs/analytics/src/events.rs @@ -105,6 +105,7 @@ pub(crate) struct CodexRuntimeMetadata { #[derive(Serialize)] pub(crate) struct ThreadInitializedEventParams { + pub(crate) session_id: String, pub(crate) thread_id: String, pub(crate) app_server_client: CodexAppServerClientMetadata, pub(crate) runtime: CodexRuntimeMetadata, @@ -378,6 +379,7 @@ impl GuardianReviewAnalyticsResult { #[derive(Serialize)] pub(crate) struct GuardianReviewEventPayload { + pub(crate) session_id: String, pub(crate) app_server_client: CodexAppServerClientMetadata, pub(crate) runtime: CodexRuntimeMetadata, #[serde(flatten)] @@ -425,6 +427,7 @@ pub(crate) struct CodexHookRunEventRequest { #[derive(Serialize)] pub(crate) struct CodexCompactionEventParams { + pub(crate) session_id: String, pub(crate) thread_id: String, pub(crate) turn_id: String, pub(crate) app_server_client: CodexAppServerClientMetadata, @@ -454,6 +457,7 @@ pub(crate) struct CodexCompactionEventRequest { #[derive(Serialize)] pub(crate) struct CodexTurnEventParams { + pub(crate) session_id: String, pub(crate) thread_id: String, pub(crate) turn_id: String, // TODO(rhan-oai): Populate once queued/default submission type is plumbed from @@ -510,6 +514,7 @@ pub(crate) struct CodexTurnEventRequest { #[derive(Serialize)] pub(crate) struct CodexTurnSteerEventParams { + pub(crate) session_id: String, pub(crate) thread_id: String, pub(crate) expected_turn_id: Option, pub(crate) accepted_turn_id: Option, @@ -618,11 +623,13 @@ pub(crate) fn codex_compaction_event_params( input: CodexCompactionEvent, app_server_client: CodexAppServerClientMetadata, runtime: CodexRuntimeMetadata, + session_id: String, thread_source: Option<&'static str>, subagent_source: Option, parent_thread_id: Option, ) -> CodexCompactionEventParams { CodexCompactionEventParams { + session_id, thread_id: input.thread_id, turn_id: input.turn_id, app_server_client, @@ -711,6 +718,7 @@ pub(crate) fn subagent_thread_started_event_request( input: SubAgentThreadStartedInput, ) -> ThreadInitializedEvent { let event_params = ThreadInitializedEventParams { + session_id: input.session_id, thread_id: input.thread_id, app_server_client: CodexAppServerClientMetadata { product_client_id: input.product_client_id, diff --git a/codex-rs/analytics/src/facts.rs b/codex-rs/analytics/src/facts.rs index 424dd523b2..2ca182483b 100644 --- a/codex-rs/analytics/src/facts.rs +++ b/codex-rs/analytics/src/facts.rs @@ -191,6 +191,7 @@ pub struct AppInvocation { #[derive(Clone)] pub struct SubAgentThreadStartedInput { + pub session_id: String, pub thread_id: String, pub parent_thread_id: Option, pub product_client_id: String, diff --git a/codex-rs/analytics/src/reducer.rs b/codex-rs/analytics/src/reducer.rs index b1dc822d43..97f51775fe 100644 --- a/codex-rs/analytics/src/reducer.rs +++ b/codex-rs/analytics/src/reducer.rs @@ -147,6 +147,7 @@ enum MissingAnalyticsContext { #[derive(Clone)] struct ThreadMetadataState { + session_id: String, thread_source: Option<&'static str>, initialization_mode: ThreadInitializationMode, subagent_source: Option, @@ -155,6 +156,7 @@ struct ThreadMetadataState { impl ThreadMetadataState { fn from_thread_metadata( + session_id: String, session_source: &SessionSource, initialization_mode: ThreadInitializationMode, ) -> Self { @@ -172,6 +174,7 @@ impl ThreadMetadataState { | SessionSource::Unknown => (None, None), }; Self { + session_id, thread_source: session_source.thread_source_name(), initialization_mode, subagent_source, @@ -348,6 +351,7 @@ impl AnalyticsReducer { thread_state .metadata .get_or_insert_with(|| ThreadMetadataState { + session_id: input.session_id.clone(), thread_source: Some("subagent"), initialization_mode: ThreadInitializationMode::New, subagent_source: Some(subagent_source_name(&input.subagent_source)), @@ -366,8 +370,8 @@ impl AnalyticsReducer { input: GuardianReviewEventParams, out: &mut Vec, ) { - let Some(connection_state) = - self.thread_connection_or_warn(AnalyticsDropSite::guardian(&input)) + let Some((connection_state, thread_metadata)) = + self.thread_context_or_warn(AnalyticsDropSite::guardian(&input)) else { return; }; @@ -375,6 +379,7 @@ impl AnalyticsReducer { GuardianReviewEventRequest { event_type: "codex_guardian_review", event_params: GuardianReviewEventPayload { + session_id: thread_metadata.session_id.clone(), app_server_client: connection_state.app_server_client.clone(), runtime: connection_state.runtime.clone(), guardian_review: input, @@ -573,6 +578,7 @@ impl AnalyticsReducer { connection_id, response.thread, response.model, + response.session_id, ThreadInitializationMode::New, out, ); @@ -582,6 +588,7 @@ impl AnalyticsReducer { connection_id, response.thread, response.model, + response.session_id, ThreadInitializationMode::Resumed, out, ); @@ -589,8 +596,9 @@ impl AnalyticsReducer { ClientResponse::ThreadFork { response, .. } => { self.emit_thread_initialized( connection_id, - response.thread, + response.thread.clone(), response.model, + response.thread.id, ThreadInitializationMode::Forked, out, ); @@ -744,6 +752,7 @@ impl AnalyticsReducer { connection_id: u64, thread: codex_app_server_protocol::Thread, model: String, + session_id: String, initialization_mode: ThreadInitializationMode, out: &mut Vec, ) { @@ -752,8 +761,11 @@ impl AnalyticsReducer { let Some(connection_state) = self.connections.get(&connection_id) else { return; }; - let thread_metadata = - ThreadMetadataState::from_thread_metadata(&thread_source, initialization_mode); + let thread_metadata = ThreadMetadataState::from_thread_metadata( + session_id.clone(), + &thread_source, + initialization_mode, + ); self.threads.insert( thread_id.clone(), ThreadAnalyticsState { @@ -765,6 +777,7 @@ impl AnalyticsReducer { ThreadInitializedEvent { event_type: "codex_thread_initialized", event_params: ThreadInitializedEventParams { + session_id, thread_id, app_server_client: connection_state.app_server_client.clone(), runtime: connection_state.runtime.clone(), @@ -793,6 +806,7 @@ impl AnalyticsReducer { input, connection_state.app_server_client.clone(), connection_state.runtime.clone(), + thread_metadata.session_id.clone(), thread_metadata.thread_source, thread_metadata.subagent_source.clone(), thread_metadata.parent_thread_id.clone(), @@ -850,6 +864,7 @@ impl AnalyticsReducer { out.push(TrackEventRequest::TurnSteer(CodexTurnSteerEventRequest { event_type: "codex_turn_steer_event", event_params: CodexTurnSteerEventParams { + session_id: thread_metadata.session_id.clone(), thread_id: pending_request.thread_id, expected_turn_id: Some(pending_request.expected_turn_id), accepted_turn_id, @@ -1015,6 +1030,7 @@ fn codex_turn_event_params( } = resolved_config; let token_usage = turn_state.token_usage.clone(); CodexTurnEventParams { + session_id: thread_metadata.session_id.clone(), thread_id, turn_id, app_server_client, diff --git a/codex-rs/core/src/agent/control.rs b/codex-rs/core/src/agent/control.rs index 11391147cc..d0f646465a 100644 --- a/codex-rs/core/src/agent/control.rs +++ b/codex-rs/core/src/agent/control.rs @@ -291,6 +291,7 @@ impl AgentControl { .services .analytics_events_client, client_metadata, + self.session_id, new_thread.thread_id, /*parent_thread_id*/ None, thread_config, diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index 01907a5594..30b20a9dab 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -109,6 +109,7 @@ pub(crate) async fn run_codex_thread_interactive( emit_subagent_session_started( &parent_session.services.analytics_events_client, client_metadata, + parent_session.services.agent_control.session_id(), codex.session.conversation_id, Some(parent_session.conversation_id), thread_config, diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 577852cc66..f181f6603a 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -3256,6 +3256,7 @@ impl Session { pub(crate) fn emit_subagent_session_started( analytics_events_client: &AnalyticsEventsClient, client_metadata: AppServerClientMetadata, + session_id: codex_protocol::SessionId, thread_id: ThreadId, parent_thread_id: Option, thread_config: ThreadConfigSnapshot, @@ -3274,6 +3275,7 @@ pub(crate) fn emit_subagent_session_started( .unwrap_or_default() .as_secs(); analytics_events_client.track_subagent_thread_started(SubAgentThreadStartedInput { + session_id: session_id.to_string(), 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(),