Skip to content

Commit 65e1b2a

Browse files
open-design-release-bot[bot]open-design-crew[bot]bone3deep1962-collab
authored
feat(analytics): track recoverable task outcomes (#6411) (#6473)
(cherry picked from commit e6aeb38) Co-authored-by: open-design-crew[bot] <299007234+open-design-crew[bot]@users.noreply.github.com> Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com>
1 parent c670aff commit 65e1b2a

28 files changed

Lines changed: 2389 additions & 70 deletions

apps/daemon/src/db.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ function migrate(db: SqliteDb): void {
216216
pre_turn_file_names_json TEXT,
217217
session_mode TEXT,
218218
run_context_json TEXT,
219+
task_analytics_json TEXT,
219220
applied_plugin_snapshot_json TEXT,
220221
telemetry_finalized_at INTEGER,
221222
started_at INTEGER,
@@ -417,6 +418,9 @@ function migrate(db: SqliteDb): void {
417418
if (!messageCols.some((c: DbRow) => c.name === 'run_context_json')) {
418419
db.exec(`ALTER TABLE messages ADD COLUMN run_context_json TEXT`);
419420
}
421+
if (!messageCols.some((c: DbRow) => c.name === 'task_analytics_json')) {
422+
db.exec(`ALTER TABLE messages ADD COLUMN task_analytics_json TEXT`);
423+
}
420424
if (!messageCols.some((c: DbRow) => c.name === 'applied_plugin_snapshot_json')) {
421425
db.exec(`ALTER TABLE messages ADD COLUMN applied_plugin_snapshot_json TEXT`);
422426
}
@@ -2512,6 +2516,7 @@ export function listMessages(db: SqliteDb, conversationId: string) {
25122516
pre_turn_file_names_json AS preTurnFileNamesJson,
25132517
session_mode AS sessionMode,
25142518
run_context_json AS runContextJson,
2519+
task_analytics_json AS taskAnalyticsJson,
25152520
applied_plugin_snapshot_json AS appliedPluginSnapshotJson,
25162521
created_at AS createdAt, started_at AS startedAt, ended_at AS endedAt,
25172522
position
@@ -2565,7 +2570,8 @@ export function upsertMessage(db: SqliteDb, conversationId: string, m: DbRow) {
25652570
events_json = ?, attachments_json = ?, comment_attachments_json = ?,
25662571
produced_files_json = ?, trace_object_files_json = ?, feedback_json = ?,
25672572
pre_turn_file_names_json = ?,
2568-
session_mode = ?, run_context_json = ?, applied_plugin_snapshot_json = ?,
2573+
session_mode = ?, run_context_json = ?, task_analytics_json = ?,
2574+
applied_plugin_snapshot_json = ?,
25692575
telemetry_finalized_at = CASE
25702576
WHEN ? THEN COALESCE(telemetry_finalized_at, ?)
25712577
ELSE telemetry_finalized_at
@@ -2590,6 +2596,7 @@ export function upsertMessage(db: SqliteDb, conversationId: string, m: DbRow) {
25902596
m.preTurnFileNames ? JSON.stringify(m.preTurnFileNames) : null,
25912597
normalizeMessageSessionModeForStorage(m.sessionMode),
25922598
m.runContext ? JSON.stringify(m.runContext) : null,
2599+
m.taskAnalytics ? JSON.stringify(m.taskAnalytics) : null,
25932600
m.appliedPluginSnapshot ? JSON.stringify(m.appliedPluginSnapshot) : null,
25942601
m.telemetryFinalized === true ? 1 : 0,
25952602
now,
@@ -2611,17 +2618,18 @@ export function upsertMessage(db: SqliteDb, conversationId: string, m: DbRow) {
26112618
// run_id, run_status, result_delivery_state, last_run_event_id, events_json, attachments_json,
26122619
// comment_attachments_json, produced_files_json, trace_object_files_json,
26132620
// feedback_json, pre_turn_file_names_json, session_mode, run_context_json,
2614-
// applied_plugin_snapshot_json, telemetry_finalized_at, started_at,
2615-
// ended_at, position, created_at.
2621+
// task_analytics_json, applied_plugin_snapshot_json,
2622+
// telemetry_finalized_at, started_at, ended_at, position, created_at.
26162623
db.prepare(
26172624
`INSERT INTO messages
26182625
(id, conversation_id, role, content, agent_id, agent_name,
26192626
run_id, run_status, result_delivery_state, last_run_event_id, events_json,
26202627
attachments_json, comment_attachments_json, produced_files_json,
26212628
trace_object_files_json, feedback_json, pre_turn_file_names_json,
2622-
session_mode, run_context_json, applied_plugin_snapshot_json,
2629+
session_mode, run_context_json, task_analytics_json,
2630+
applied_plugin_snapshot_json,
26232631
telemetry_finalized_at, started_at, ended_at, position, created_at)
2624-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2632+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
26252633
).run(
26262634
m.id,
26272635
conversationId,
@@ -2642,6 +2650,7 @@ export function upsertMessage(db: SqliteDb, conversationId: string, m: DbRow) {
26422650
m.preTurnFileNames ? JSON.stringify(m.preTurnFileNames) : null,
26432651
normalizeMessageSessionModeForStorage(m.sessionMode),
26442652
m.runContext ? JSON.stringify(m.runContext) : null,
2653+
m.taskAnalytics ? JSON.stringify(m.taskAnalytics) : null,
26452654
m.appliedPluginSnapshot ? JSON.stringify(m.appliedPluginSnapshot) : null,
26462655
m.telemetryFinalized === true ? now : null,
26472656
m.startedAt ?? null,
@@ -2670,6 +2679,7 @@ export function upsertMessage(db: SqliteDb, conversationId: string, m: DbRow) {
26702679
pre_turn_file_names_json AS preTurnFileNamesJson,
26712680
session_mode AS sessionMode,
26722681
run_context_json AS runContextJson,
2682+
task_analytics_json AS taskAnalyticsJson,
26732683
applied_plugin_snapshot_json AS appliedPluginSnapshotJson,
26742684
created_at AS createdAt, started_at AS startedAt, ended_at AS endedAt,
26752685
position
@@ -3675,6 +3685,7 @@ function normalizeMessage(row: DbRow) {
36753685
preTurnFileNames: parseJsonOrUndef(row.preTurnFileNamesJson),
36763686
sessionMode: normalizeMessageSessionMode(row.sessionMode),
36773687
runContext: parseJsonOrUndef(row.runContextJson),
3688+
taskAnalytics: parseJsonOrUndef(row.taskAnalyticsJson),
36783689
appliedPluginSnapshot: parseJsonOrUndef(row.appliedPluginSnapshotJson),
36793690
createdAt: row.createdAt ?? undefined,
36803691
startedAt: row.startedAt ?? undefined,

apps/daemon/src/routes/runs.ts

Lines changed: 141 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@ import {
1515
type RunResultPackageResponse,
1616
} from '@open-design/contracts';
1717
import {
18+
buildRunCreatedV4Aliases,
19+
buildRunFinishedV4Aliases,
1820
deriveConfigureGlobals,
1921
modelIdForTracking,
2022
sessionModeToTracking,
2123
type TrackingDesignSystemSource,
2224
type TrackingDesignSystemKind,
2325
type TrackingDesignSystemEditSurface,
26+
type RunTaskLineageProps,
27+
type TrackingRunRecoveryActionType,
2428
} from '@open-design/contracts/analytics';
2529
import type { OdNativeEvent } from '@open-design/agui-adapter';
2630
import { newInsertId, readAnalyticsContext } from '../analytics.js';
@@ -87,7 +91,10 @@ import {
8791
} from '../run-analytics-observability.js';
8892
import {
8993
diffRunArtifacts,
94+
primaryArtifactChangeForRun,
9095
snapshotProjectArtifacts,
96+
supportingAssetFilesChangedForRun,
97+
type RunArtifactDiff,
9198
type RunArtifactBaseline,
9299
} from '../run-artifact-fs.js';
93100
import {
@@ -342,6 +349,7 @@ interface ChatRun {
342349
artifactsModified?: number;
343350
designSystemCreated: boolean;
344351
previewModuleCount: number;
352+
diff?: RunArtifactDiff;
345353
};
346354
designSystemId?: string | null;
347355
designSystemRequestedId?: string | null;
@@ -1875,6 +1883,46 @@ export function registerRunRoutes(app: Express, ctx: RegisterRunRoutesDeps) {
18751883
const hintProjectTurnIndex = typeof analyticsHints.projectTurnIndex === 'number'
18761884
? analyticsHints.projectTurnIndex
18771885
: undefined;
1886+
const taskExecutionId = typeof analyticsHints.taskExecutionId === 'string'
1887+
&& analyticsHints.taskExecutionId.length > 0
1888+
? analyticsHints.taskExecutionId
1889+
: run.clientRequestId ?? run.id;
1890+
const initialRunId = typeof analyticsHints.initialRunId === 'string'
1891+
&& analyticsHints.initialRunId.length > 0
1892+
? analyticsHints.initialRunId
1893+
: run.id;
1894+
const taskRunIndex = typeof analyticsHints.taskRunIndex === 'number'
1895+
&& Number.isInteger(analyticsHints.taskRunIndex)
1896+
&& analyticsHints.taskRunIndex >= 0
1897+
? analyticsHints.taskRunIndex
1898+
: 0;
1899+
const recoveryActionTypes: ReadonlySet<TrackingRunRecoveryActionType> = new Set([
1900+
'manual_retry',
1901+
'resume_run',
1902+
'authorize_and_retry',
1903+
'switch_model_retry',
1904+
'switch_runtime_retry',
1905+
'question_answer',
1906+
]);
1907+
const recoveryActionType = typeof analyticsHints.recoveryActionType === 'string'
1908+
&& recoveryActionTypes.has(
1909+
analyticsHints.recoveryActionType as TrackingRunRecoveryActionType,
1910+
)
1911+
? analyticsHints.recoveryActionType as TrackingRunRecoveryActionType
1912+
: undefined;
1913+
const taskLineage: RunTaskLineageProps = {
1914+
task_execution_id: taskExecutionId,
1915+
initial_run_id: initialRunId,
1916+
task_run_index: taskRunIndex,
1917+
...(typeof analyticsHints.sourceRunId === 'string' && analyticsHints.sourceRunId.length > 0
1918+
? { source_run_id: analyticsHints.sourceRunId }
1919+
: {}),
1920+
...(recoveryActionType ? { recovery_action_type: recoveryActionType } : {}),
1921+
...(typeof analyticsHints.recoveryActionInstanceId === 'string'
1922+
&& analyticsHints.recoveryActionInstanceId.length > 0
1923+
? { recovery_action_instance_id: analyticsHints.recoveryActionInstanceId }
1924+
: {}),
1925+
};
18781926
const conversationTurnIndex = run.conversationId
18791927
? conversationTurnIndexForRun(db, run.conversationId, run.id)
18801928
: null;
@@ -2088,6 +2136,7 @@ export function registerRunRoutes(app: Express, ctx: RegisterRunRoutesDeps) {
20882136
}
20892137
: {}),
20902138
};
2139+
Object.assign(baseProps, buildRunCreatedV4Aliases(baseProps, taskLineage));
20912140
design.runs.setAnalyticsRecovery?.(run, {
20922141
context: analyticsContext,
20932142
properties: baseProps,
@@ -2170,7 +2219,9 @@ export function registerRunRoutes(app: Express, ctx: RegisterRunRoutesDeps) {
21702219
// in the rollout `last_token_usage`, read here best-effort.
21712220
const firstCallUsage = await (async (): Promise<{
21722221
first_call_input_tokens?: number;
2222+
first_call_input_tokens_effective?: number;
21732223
first_call_cache_read_input_tokens?: number;
2224+
first_call_cache_creation_input_tokens?: number;
21742225
first_call_cache_hit_ratio?: number;
21752226
} | null> => {
21762227
if (run.agentId === 'codex') {
@@ -2187,20 +2238,39 @@ export function registerRunRoutes(app: Express, ctx: RegisterRunRoutesDeps) {
21872238
'codex',
21882239
),
21892240
).CODEX_HOME;
2190-
return await readCodexRolloutFirstCall({ codexHome, sessionId });
2241+
const codexUsage = await readCodexRolloutFirstCall({ codexHome, sessionId });
2242+
return codexUsage
2243+
? {
2244+
...codexUsage,
2245+
first_call_input_tokens_effective:
2246+
codexUsage.first_call_input_tokens,
2247+
}
2248+
: null;
21912249
} catch {
21922250
return null;
21932251
}
21942252
}
21952253
if (usageAnalytics.first_call_input_tokens === undefined) return null;
21962254
return {
21972255
first_call_input_tokens: usageAnalytics.first_call_input_tokens,
2256+
...(usageAnalytics.first_call_input_tokens_effective !== undefined
2257+
? {
2258+
first_call_input_tokens_effective:
2259+
usageAnalytics.first_call_input_tokens_effective,
2260+
}
2261+
: {}),
21982262
...(usageAnalytics.first_call_cache_read_input_tokens !== undefined
21992263
? {
22002264
first_call_cache_read_input_tokens:
22012265
usageAnalytics.first_call_cache_read_input_tokens,
22022266
}
22032267
: {}),
2268+
...(usageAnalytics.first_call_cache_creation_input_tokens !== undefined
2269+
? {
2270+
first_call_cache_creation_input_tokens:
2271+
usageAnalytics.first_call_cache_creation_input_tokens,
2272+
}
2273+
: {}),
22042274
...(usageAnalytics.first_call_cache_hit_ratio !== undefined
22052275
? { first_call_cache_hit_ratio: usageAnalytics.first_call_cache_hit_ratio }
22062276
: {}),
@@ -2225,13 +2295,15 @@ export function registerRunRoutes(app: Express, ctx: RegisterRunRoutesDeps) {
22252295
let artifactsModified: number | undefined;
22262296
let designSystemCreated: boolean;
22272297
let previewModuleCount: number;
2298+
let artifactDiff: RunArtifactDiff | undefined;
22282299
const artifactOutcome = run.artifactOutcome;
22292300
if (artifactOutcome) {
22302301
artifactCount = artifactOutcome.artifactCount;
22312302
artifactsCreated = artifactOutcome.artifactsCreated;
22322303
artifactsModified = artifactOutcome.artifactsModified;
22332304
designSystemCreated = artifactOutcome.designSystemCreated;
22342305
previewModuleCount = artifactOutcome.previewModuleCount;
2306+
artifactDiff = artifactOutcome.diff;
22352307
} else {
22362308
const artifactBaseline = runArtifactBaselines.take(run.id);
22372309
if (artifactBaseline && !artifactBaseline.contended) {
@@ -2245,6 +2317,7 @@ export function registerRunRoutes(app: Express, ctx: RegisterRunRoutesDeps) {
22452317
diff = null;
22462318
}
22472319
if (diff) {
2320+
artifactDiff = diff;
22482321
artifactCount = diff.touched;
22492322
artifactsCreated = diff.created;
22502323
artifactsModified = diff.modified;
@@ -2309,7 +2382,23 @@ export function registerRunRoutes(app: Express, ctx: RegisterRunRoutesDeps) {
23092382
insertId: `${runInsertId}-${retryEvent.event}-${index}`,
23102383
});
23112384
}
2312-
const finishedProperties = {
2385+
const clarificationRequested = runAskedUserQuestion(run.events);
2386+
const interactionMode = typeof reqBody.sessionMode === 'string'
2387+
? sessionModeToTracking(reqBody.sessionMode)
2388+
: undefined;
2389+
const primaryArtifactChange = artifactDiff
2390+
? primaryArtifactChangeForRun({
2391+
diff: artifactDiff,
2392+
projectKind: runProjectKind,
2393+
hadExistingArtifacts: hintHasExistingArtifact === true,
2394+
...(interactionMode ? { interactionMode } : {}),
2395+
clarificationRequested,
2396+
})
2397+
: undefined;
2398+
const supportingAssetFilesChanged = artifactDiff
2399+
? supportingAssetFilesChangedForRun(artifactDiff, runProjectKind)
2400+
: undefined;
2401+
const finishedProperties: Record<string, unknown> = {
23132402
...baseProps,
23142403
design_system_id: run.designSystemId ?? undefined,
23152404
design_system_digest: run.designSystemDigest ?? undefined,
@@ -2345,7 +2434,7 @@ export function registerRunRoutes(app: Express, ctx: RegisterRunRoutesDeps) {
23452434
: {}),
23462435
...(artifactsCreated !== undefined ? { artifacts_created: artifactsCreated } : {}),
23472436
...(artifactsModified !== undefined ? { artifacts_modified: artifactsModified } : {}),
2348-
asked_user_question: runAskedUserQuestion(run.events),
2437+
asked_user_question: clarificationRequested,
23492438
retry_attempt_count: run.retryAttemptCount ?? 0,
23502439
retry_final_result: run.retryFinalResult ?? 'not_attempted',
23512440
...(agentCliVersion
@@ -2444,6 +2533,55 @@ export function registerRunRoutes(app: Express, ctx: RegisterRunRoutesDeps) {
24442533
tool_name_count: toolAnalytics.tool_name_count,
24452534
tool_names: toolAnalytics.tool_names_csv,
24462535
};
2536+
Object.assign(
2537+
finishedProperties,
2538+
buildRunFinishedV4Aliases(finishedProperties, taskLineage, {
2539+
inputAccountingMode: usageAnalytics.input_accounting_mode,
2540+
...(firstCallUsage
2541+
? {
2542+
firstModelCall: {
2543+
...(firstCallUsage.first_call_input_tokens !== undefined
2544+
? { provider_input_tokens: firstCallUsage.first_call_input_tokens }
2545+
: {}),
2546+
...(firstCallUsage.first_call_input_tokens_effective !== undefined
2547+
? { effective_input_tokens: firstCallUsage.first_call_input_tokens_effective }
2548+
: {}),
2549+
...(firstCallUsage.first_call_cache_read_input_tokens !== undefined
2550+
? { cache_read_tokens: firstCallUsage.first_call_cache_read_input_tokens }
2551+
: {}),
2552+
...(firstCallUsage.first_call_cache_creation_input_tokens !== undefined
2553+
? { cache_write_tokens: firstCallUsage.first_call_cache_creation_input_tokens }
2554+
: {}),
2555+
},
2556+
}
2557+
: {}),
2558+
...(primaryArtifactChange
2559+
? { primaryArtifactChange }
2560+
: {}),
2561+
...(artifactDiff
2562+
? {
2563+
artifactFiles: {
2564+
changed_file_count: artifactDiff.contentTouched,
2565+
created_file_count: artifactDiff.contentCreated,
2566+
modified_file_count: artifactDiff.contentModified,
2567+
...(supportingAssetFilesChanged !== undefined
2568+
? {
2569+
supporting_asset_files_changed_count:
2570+
supportingAssetFilesChanged,
2571+
}
2572+
: {}),
2573+
},
2574+
}
2575+
: {}),
2576+
...(isDesignSystemRun
2577+
? {
2578+
designSystemChangeType: designSystemCreated
2579+
? hintHasExistingArtifact === true ? 'modified' : 'created'
2580+
: 'none',
2581+
}
2582+
: {}),
2583+
}),
2584+
);
24472585
// Refresh local recovery snapshot so crash recovery matches PostHog
24482586
// `run_finished` (usage/timing/tools), not only run_created baseProps.
24492587
// Keep the base insertId here: reconcileDurableRunTerminals appends

0 commit comments

Comments
 (0)