Add session id to Codex analytics events

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Michael Musson
2026-05-04 05:34:41 +00:00
co-authored by Codex
parent 85f522a73e
commit 16d3cb7fbe
7 changed files with 51 additions and 5 deletions
@@ -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,
+8
View File
@@ -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<String>,
pub(crate) accepted_turn_id: Option<String>,
@@ -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<String>,
parent_thread_id: Option<String>,
) -> 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,
+1
View File
@@ -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<String>,
pub product_client_id: String,
+21 -5
View File
@@ -147,6 +147,7 @@ enum MissingAnalyticsContext {
#[derive(Clone)]
struct ThreadMetadataState {
session_id: String,
thread_source: Option<&'static str>,
initialization_mode: ThreadInitializationMode,
subagent_source: Option<String>,
@@ -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<TrackEventRequest>,
) {
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<TrackEventRequest>,
) {
@@ -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,
+1
View File
@@ -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,
+1
View File
@@ -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,
+2
View File
@@ -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<ThreadId>,
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(),