Include start times in terminal turn events (#32263)

## What changed

- Add an optional `started_at` Unix timestamp to `TurnCompleteEvent` and
  `TurnAbortedEvent`.
- Populate the timestamp from turn timing state when a turn completes or is
  aborted.
- Preserve the timestamp when synthesizing interrupted fork history and when
  importing external sessions.

## Testing

- Verify that interrupting a turn emits a `TurnAbortedEvent` with a start
  timestamp.

GitOrigin-RevId: 75b3911b95c060e5fc1843a5e293d8fe47377186
This commit is contained in:
Owen Lin
2026-07-10 20:25:03 +00:00
committed by copyberry
parent b66c25c6a3
commit bca577d69a
20 changed files with 144 additions and 7 deletions
+20
View File
@@ -199,6 +199,7 @@ fn truncates_before_requested_user_message() {
&SnapshotTurnState {
ends_mid_turn: false,
active_turn_id: None,
active_turn_started_at: None,
active_turn_start_index: None,
},
);
@@ -224,6 +225,7 @@ fn truncates_before_requested_user_message() {
&SnapshotTurnState {
ends_mid_turn: false,
active_turn_id: None,
active_turn_started_at: None,
active_turn_start_index: None,
},
);
@@ -248,6 +250,7 @@ fn out_of_range_truncation_drops_only_unfinished_suffix_mid_turn() {
&SnapshotTurnState {
ends_mid_turn: true,
active_turn_id: None,
active_turn_started_at: None,
active_turn_start_index: None,
},
);
@@ -299,6 +302,7 @@ fn out_of_range_truncation_drops_pre_user_active_turn_prefix() {
SnapshotTurnState {
ends_mid_turn: true,
active_turn_id: Some("turn-2".to_string()),
active_turn_started_at: None,
active_turn_start_index: Some(2),
},
);
@@ -340,6 +344,7 @@ async fn ignores_session_prefix_messages_when_truncating() {
&SnapshotTurnState {
ends_mid_turn: false,
active_turn_id: None,
active_turn_started_at: None,
active_turn_start_index: None,
},
);
@@ -1379,6 +1384,7 @@ fn interrupted_fork_snapshot_appends_interrupt_boundary() {
append_interrupted_boundary(
committed_history,
/*turn_id*/ None,
/*started_at*/ None,
InterruptedTurnHistoryMarker::ContextualUser,
)
.get_rollout_items()
@@ -1389,6 +1395,7 @@ fn interrupted_fork_snapshot_appends_interrupt_boundary() {
RolloutItem::ResponseItem(contextual_user_interrupted_marker()),
RolloutItem::EventMsg(EventMsg::TurnAborted(TurnAbortedEvent {
turn_id: None,
started_at: None,
reason: TurnAbortReason::Interrupted,
completed_at: None,
duration_ms: None,
@@ -1401,6 +1408,7 @@ fn interrupted_fork_snapshot_appends_interrupt_boundary() {
append_interrupted_boundary(
InitialHistory::New,
/*turn_id*/ None,
/*started_at*/ None,
InterruptedTurnHistoryMarker::ContextualUser,
)
.get_rollout_items()
@@ -1410,6 +1418,7 @@ fn interrupted_fork_snapshot_appends_interrupt_boundary() {
RolloutItem::ResponseItem(contextual_user_interrupted_marker()),
RolloutItem::EventMsg(EventMsg::TurnAborted(TurnAbortedEvent {
turn_id: None,
started_at: None,
reason: TurnAbortReason::Interrupted,
completed_at: None,
duration_ms: None,
@@ -1429,6 +1438,7 @@ fn disabled_interrupted_fork_snapshot_appends_only_interrupt_event() {
append_interrupted_boundary(
committed_history,
/*turn_id*/ None,
/*started_at*/ None,
InterruptedTurnHistoryMarker::Disabled,
)
.get_rollout_items()
@@ -1438,6 +1448,7 @@ fn disabled_interrupted_fork_snapshot_appends_only_interrupt_event() {
RolloutItem::ResponseItem(user_msg("hello")),
RolloutItem::EventMsg(EventMsg::TurnAborted(TurnAbortedEvent {
turn_id: None,
started_at: None,
reason: TurnAbortReason::Interrupted,
completed_at: None,
duration_ms: None,
@@ -1450,6 +1461,7 @@ fn disabled_interrupted_fork_snapshot_appends_only_interrupt_event() {
append_interrupted_boundary(
InitialHistory::New,
/*turn_id*/ None,
/*started_at*/ None,
InterruptedTurnHistoryMarker::Disabled,
)
.get_rollout_items()
@@ -1458,6 +1470,7 @@ fn disabled_interrupted_fork_snapshot_appends_only_interrupt_event() {
serde_json::to_value(vec![RolloutItem::EventMsg(EventMsg::TurnAborted(
TurnAbortedEvent {
turn_id: None,
started_at: None,
reason: TurnAbortReason::Interrupted,
completed_at: None,
duration_ms: None,
@@ -1475,6 +1488,7 @@ fn interrupted_snapshot_is_not_mid_turn() {
RolloutItem::ResponseItem(contextual_user_interrupted_marker()),
RolloutItem::EventMsg(EventMsg::TurnAborted(TurnAbortedEvent {
turn_id: Some("turn-1".to_string()),
started_at: None,
reason: TurnAbortReason::Interrupted,
completed_at: None,
duration_ms: None,
@@ -1486,6 +1500,7 @@ fn interrupted_snapshot_is_not_mid_turn() {
SnapshotTurnState {
ends_mid_turn: false,
active_turn_id: None,
active_turn_started_at: None,
active_turn_start_index: None,
},
);
@@ -1532,6 +1547,7 @@ fn completed_legacy_event_history_is_not_mid_turn() {
SnapshotTurnState {
ends_mid_turn: false,
active_turn_id: None,
active_turn_started_at: None,
active_turn_start_index: None,
},
);
@@ -1556,6 +1572,7 @@ fn mixed_response_and_legacy_user_event_history_is_mid_turn() {
SnapshotTurnState {
ends_mid_turn: true,
active_turn_id: None,
active_turn_started_at: None,
active_turn_start_index: None,
},
);
@@ -1642,6 +1659,7 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
let interrupted_abort_json = serde_json::to_value(RolloutItem::EventMsg(
EventMsg::TurnAborted(TurnAbortedEvent {
turn_id: expected_turn_id,
started_at: None,
reason: TurnAbortReason::Interrupted,
completed_at: None,
duration_ms: None,
@@ -1729,6 +1747,7 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
SnapshotTurnState {
ends_mid_turn: true,
active_turn_id: Some("turn-explicit".to_string()),
active_turn_started_at: None,
active_turn_start_index: Some(1),
},
);
@@ -1761,6 +1780,7 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
item,
RolloutItem::EventMsg(EventMsg::TurnAborted(TurnAbortedEvent {
turn_id: Some(turn_id),
started_at: None,
reason: TurnAbortReason::Interrupted,
completed_at: None,
duration_ms: None,