Originally created by: sosidudku1
We have no visibility into response cost in product analytics — neither how slow nor how heavy turns are — so we can't tell whether slow or complex turns drive drop-off. This adds three content-free "shape" metrics to the existing message_sent event.
New optional fields on message_sent (each omitted when unmeasurable; none ever attached to first_message_sent):
| Field | Meaning |
|---|---|
latency_ms |
Wall-clock turn duration (submit -> final assistant response). |
step_count |
Number of agent steps taken — a proxy for how much work the turn required. From RunTurnResult.stepCount. |
outcome |
How the turn ended: reply / finish / max_steps / cancelled / failed. max_steps means the reply was cut off by the step budget. |
MessageEventContext with optional latencyMs / stepCount / outcome; captureMessageSent spreads each in only when present.runTurn, time the turn around turnController.enqueue. On success, emit latency + result.stepCount + result.reason; on throw, emit latency + outcome: "failed" (no result to read), then rethrow. The emit moved from before the turn to after it, so duration and outcome are observable — the same "fires on success or failure" guarantee as before.Unchanged in spirit: the event still carries only provider / model plus these numeric/enum shape fields — no message content, file paths, tool arguments, or any user data. The event doc comment is updated to match. Respects the analytics opt-out (no-ops when disabled).
first_message_sent now also fires after the turn — believe this is fine (arguably more correct), flagging since it's a semantic shift.RunTurnResult. Happy to surface them as a small follow-up in agent-loop.ts if wanted.tsc --noEmit clean.src/analytics/ tests: 26/26 pass (added 2 asserting the metrics land on message_sent and stay off first_message_sent).⚛️ Generated with Atomic Agent
Ticket changed by: Ooooze