Preserve Guardian history across thread reconstruction (#42065)

## Why

Guardian review evidence must remain available after a compacted thread is resumed or forked, while rolled-back or parent-local evidence must not grant authorization in a different context.

## What changed

- Store the bounded, model-invisible Guardian transcript alongside compacted rollout history and restore it from the newest surviving checkpoint during replay.
- Trim Guardian history at rollback boundaries, clearing it when the boundary has already been evicted.
- Preserve the checkpoint for user-initiated forks, but remove it when spawning a subagent so parent review evidence is not inherited.
- Keep the new rollout field optional for compatibility with existing compacted records and legacy readers.

## Testing

- Cover Guardian history across compaction, restart, paginated and pathless stores, user forks, rollback, bounded replay, serialization, and subagent forks.

GitOrigin-RevId: 6ab076d5e0c3aac4d6a41ebd50ba2b200c90213e
This commit is contained in:
jif
2026-09-01 14:33:58 +00:00
committed by copyberry
parent 3a04482645
commit 28097e98eb
23 changed files with 369 additions and 27 deletions
@@ -759,6 +759,7 @@ fn compacted(message: &str, replacement_history: Option<Vec<ResponseItem>>) -> R
message: message.to_string(),
replacement_history: replacement_history
.map(|items| items.into_iter().map(Into::into).collect()),
guardian_history: None,
mcp_resource_origins: None,
window_number: Some(1),
first_window_id: None,
@@ -230,6 +230,7 @@ fn compacted(replacement_history: Vec<ResponseItem>) -> RolloutItem {
RolloutItem::Compacted(CompactedItem {
message: "checkpoint".to_string(),
replacement_history: Some(replacement_history.into_iter().map(Into::into).collect()),
guardian_history: None,
mcp_resource_origins: None,
window_number: Some(1),
first_window_id: None,
@@ -1408,6 +1409,7 @@ async fn migration_compacts_subagent_prefix_and_does_not_project_it() {
RolloutItem::Compacted(CompactedItem {
message: "superseded checkpoint".repeat(1024),
replacement_history: Some(Vec::new()),
guardian_history: None,
mcp_resource_origins: None,
window_number: Some(1),
first_window_id: None,
@@ -1430,6 +1432,7 @@ async fn migration_compacts_subagent_prefix_and_does_not_project_it() {
}
.into(),
]),
guardian_history: None,
mcp_resource_origins: None,
window_number: Some(2),
first_window_id: None,