Preserve turn attribution across recovery and compaction (#51402)

## Why

Recovering an interrupted turn replaced its original trigger with `retry` and restored only its root lineage from model context. Turns suspended before that context was written could lose their attribution entirely.

## What changed

- Persist regular-turn attribution in `TurnStartedEvent` and compaction resume metadata, including the trigger, parent and root turn IDs, and initiating agent path.
- Restore matching attribution during recovery, keep unknown triggers absent, and retain the model-context root fallback for older records.
- Reconstruct attribution from surviving turns across rollback and late completion or abort events, and clear inherited attribution when spawning a forked agent.

## Testing

Add coverage for repeated cold recovery, suspension before context persistence, legacy rollouts, fresh attribution on subsequent turns, compaction checkpoints, rollback, and late terminal events.

GitOrigin-RevId: 85b5763e8fbd48a1ea2c07cfda8196e63c2cd33a
This commit is contained in:
Eddie Chen
2026-10-06 15:50:27 +00:00
committed by copyberry
parent a4ebc509f4
commit 551bd409eb
50 changed files with 912 additions and 38 deletions
@@ -799,6 +799,7 @@ fn out_of_range_truncation_drops_pre_user_active_turn_prefix() {
RolloutItem::ResponseItem(user_msg("u1").into()),
RolloutItem::ResponseItem(assistant_msg("a1").into()),
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_attribution: None,
turn_id: "turn-2".to_string(),
root_turn_id: None,
trace_id: None,
@@ -3125,6 +3126,7 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
history_mode: Some(ThreadHistoryMode::Legacy),
initial_history: InitialHistory::Forked(vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_attribution: None,
turn_id: "turn-explicit".to_string(),
root_turn_id: None,
trace_id: None,