Persist response token usage in rollout history (#41912)

## Why

Resumed threads need to continue per-turn and per-thread token totals without scanning arbitrarily far beyond the latest compaction checkpoint.

## What changed

- Add durable `TokenUsageRecord` rollout items with response, turn, thread, session, and root-turn attribution.
- Restore accumulated usage on resume and snapshot the latest record plus the compaction response ID in compaction checkpoints.
- Preserve root-turn lineage in persisted turn context, while ensuring forked child threads start with their own usage totals.

## Testing

- Cover usage accumulation across multiple responses and resumed turns.
- Cover local and remote compaction checkpoints, invalid remote compaction output, rollout reconstruction, and fork isolation.

GitOrigin-RevId: ef9e0c4a9102a08a2c382be4cdac68c84353c90a
This commit is contained in:
Owen Lin authored and copyberry committed 2026-08-31 21:15:38 +00:00
1 parent a7913390f7
commit 5f79a92e39
67 files changed
+932 -162

No files matched your search

+2 -2
View File
@@ -276,7 +276,7 @@ impl GoalService {
};
if let Some(runtime) = runtime.as_ref() {
runtime.invalidate_turn_lineage().await;
runtime.clear_pending_turn_start_options().await;
}
if objective.is_some() {
@@ -321,7 +321,7 @@ impl GoalService {
})?;
let cleared = cleared_goal.is_some();
if cleared && let Some(runtime) = runtime.as_ref() {
runtime.invalidate_turn_lineage().await;
runtime.clear_pending_turn_start_options().await;
}
drop(goal_state_permit);
drop(runtime);
+1 -4
View File
@@ -135,7 +135,7 @@ impl GoalRuntimeHandle {
self.inner.root_accounting_state.clone()
}
pub(crate) async fn invalidate_turn_lineage(&self) {
pub(crate) async fn clear_pending_turn_start_options(&self) {
let Some(thread_manager) = self.inner.thread_manager.upgrade() else {
return;
};
@@ -143,9 +143,6 @@ impl GoalRuntimeHandle {
return;
};
thread.thread_extension_data().remove::<TurnStartOptions>();
if let Some(turn_id) = self.inner.accounting_state.current_turn_id() {
thread.invalidate_turn_lineage(turn_id.as_str()).await;
}
}
pub(crate) async fn goal_state_permit(&self) -> Result<SemaphorePermit<'_>, String> {