[codex-analytics] emit goal lifecycle analytics (#27078)

## Why
- Currently, there is no analytics event for `/goal` behavior
- Existing events cannot identify goal execution or its resulting
outcome
- The original update in
[#26182](https://github.com/openai/codex/pull/26182) was implemented
before `/goal` moved into `codex-goal-extension`.

## What Changed
- Adds `codex_goal_event` serialization and enrichment to
`codex-analytics`
- Emits goal events from the canonical `codex-goal-extension` mutation
and accounting paths:
  - `created` when a new logical goal is persisted
  - `usage_accounted` when cumulative goal usage is persisted
  - `status_changed` when the stored goal status changes
  - `cleared` when the goal is deleted
- Preserves causal `turn_id` for turn driven events and uses null
attribution for external or idle lifecycle events
- Changes goal deletion to return the deleted row so `cleared` retains
the stable goal ID

## Event Details

Includes standard analytics metadata along with goal specific fields:
- `goal_id`: Stable ID stored in the local SQLite goal row and shared
across the goal's events
- `event_kind`: Observed operation (see the 4 lifecycle events cited in
the above bullet)
- `goal_status`: Resulting or last stored status: `active`, `paused`,
`blocked`, `usage_limited`, etc.
  - `has_token_budget`: Indicates whether a token budget is configured
  - `turn_id`: Causal turn ID, or null when no causal turn exists
- `cumulative_tokens_accounted`: Cumulative tokens on `usage_accounted`
events; null otherwise
- `cumulative_time_accounted_seconds`: Cumulative active time on
`usage_accounted` events; null otherwise

## Validation
- `just test -p codex-analytics -p codex-state -p codex-goal-extension`
- `just test -p codex-core -E 'test(/goal/)'`
- `just test -p codex-app-server`
- `cargo build -p codex-analytics -p codex-core -p codex-state -p
codex-app-server`
This commit is contained in:
marksteinbrick-oai
2026-06-09 18:45:54 -07:00
committed by GitHub
parent 4a3eac2144
commit 608b8b1cc6
23 changed files with 412 additions and 24 deletions
+3 -1
View File
@@ -1,6 +1,7 @@
use std::sync::Arc;
use std::sync::Weak;
use codex_analytics::AnalyticsEventsClient;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ThreadGoal;
use codex_app_server_protocol::ThreadGoalUpdatedNotification;
@@ -30,6 +31,7 @@ pub(crate) fn thread_extensions<S>(
event_sink: Arc<dyn ExtensionEventSink>,
auth_manager: Arc<AuthManager>,
state_db: Option<StateDbHandle>,
analytics_events_client: AnalyticsEventsClient,
thread_manager: Weak<ThreadManager>,
goal_service: Arc<GoalService>,
executor_skill_provider: Arc<dyn codex_skills_extension::SkillProvider>,
@@ -42,6 +44,7 @@ where
codex_goal_extension::install_with_backend(
&mut builder,
state_db,
analytics_events_client,
codex_otel::global(),
thread_manager,
goal_service,
@@ -140,7 +143,6 @@ pub(crate) fn guardian_agent_spawner(
mod tests {
use std::time::Duration;
use codex_analytics::AnalyticsEventsClient;
use codex_protocol::protocol::ThreadGoal as CoreThreadGoal;
use codex_protocol::protocol::ThreadGoalStatus;
use codex_protocol::protocol::ThreadGoalUpdatedEvent;
+1
View File
@@ -196,6 +196,7 @@ mod tests {
Arc::new(NoopExtensionEventSink),
auth_manager.clone(),
Some(state_db.clone()),
codex_analytics::AnalyticsEventsClient::disabled(),
thread_manager.clone(),
Arc::new(codex_goal_extension::GoalService::new()),
Arc::clone(&executor_skill_provider),
@@ -327,6 +327,7 @@ impl MessageProcessor {
app_server_extension_event_sink(outgoing.clone(), thread_state_manager.clone()),
auth_manager.clone(),
state_db.clone(),
analytics_events_client.clone(),
thread_manager.clone(),
Arc::clone(&goal_service),
Arc::clone(&executor_skill_provider),