mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[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:
@@ -377,18 +377,31 @@ WHERE thread_id = ?
|
||||
self.get_thread_goal(thread_id).await
|
||||
}
|
||||
|
||||
pub async fn delete_thread_goal(&self, thread_id: ThreadId) -> anyhow::Result<bool> {
|
||||
let result = sqlx::query(
|
||||
pub async fn delete_thread_goal(
|
||||
&self,
|
||||
thread_id: ThreadId,
|
||||
) -> anyhow::Result<Option<crate::ThreadGoal>> {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
DELETE FROM thread_goals
|
||||
WHERE thread_id = ?
|
||||
RETURNING
|
||||
thread_id,
|
||||
goal_id,
|
||||
objective,
|
||||
status,
|
||||
token_budget,
|
||||
tokens_used,
|
||||
time_used_seconds,
|
||||
created_at_ms,
|
||||
updated_at_ms
|
||||
"#,
|
||||
)
|
||||
.bind(thread_id.to_string())
|
||||
.execute(self.pool.as_ref())
|
||||
.fetch_optional(self.pool.as_ref())
|
||||
.await?;
|
||||
|
||||
Ok(result.rows_affected() > 0)
|
||||
row.map(|row| thread_goal_from_row(&row)).transpose()
|
||||
}
|
||||
|
||||
pub async fn account_thread_goal_usage(
|
||||
@@ -622,7 +635,8 @@ mod tests {
|
||||
assert_eq!(0, replaced.tokens_used);
|
||||
assert_eq!(0, replaced.time_used_seconds);
|
||||
|
||||
assert!(
|
||||
assert_eq!(
|
||||
Some(replaced),
|
||||
runtime
|
||||
.thread_goals()
|
||||
.delete_thread_goal(thread_id)
|
||||
@@ -637,8 +651,9 @@ mod tests {
|
||||
.await
|
||||
.unwrap()
|
||||
);
|
||||
assert!(
|
||||
!runtime
|
||||
assert_eq!(
|
||||
None,
|
||||
runtime
|
||||
.thread_goals()
|
||||
.delete_thread_goal(thread_id)
|
||||
.await
|
||||
|
||||
@@ -891,7 +891,7 @@ ON CONFLICT(id) DO UPDATE SET
|
||||
let rows_affected = result.rows_affected();
|
||||
self.memories.delete_thread_memory(thread_id).await?;
|
||||
if rows_affected > 0 {
|
||||
self.thread_goals.delete_thread_goal(thread_id).await?;
|
||||
let _ = self.thread_goals.delete_thread_goal(thread_id).await?;
|
||||
}
|
||||
Ok(rows_affected)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user