Add per-item retention policies for injected history (#61344)

## What changed

Allow `thread/inject_items` to accept wrapped items with `retention: "default"` or `retention: "always"`, while preserving support for raw response items. Persist explicit policies separately from legacy client-authored metadata.

Carry explicitly retained items through local and remote compaction, context-window changes, and child-agent creation, subject to retention budgets. Preserve history order and keep retained tool exchanges together; require matching calls and outputs to be adjacent in the injection request and both marked `always`.

Update Quinn dependencies and record exceptions for additional Hickory DNSSEC advisories where DNSSEC is disabled.

## Testing

Update injection tests for explicit retention metadata and extend the compaction scenario to cover a retained tool exchange across repeated compaction and restart.

GitOrigin-RevId: 1195a4ed62dc162a62e58bc2ad733262fbabea27
This commit is contained in:
Vivian Fang
2026-11-13 18:46:13 +00:00
committed by copyberry
parent d17887a8d1
commit c33002259e
29 changed files with 578 additions and 300 deletions
+50
View File
@@ -10,6 +10,7 @@ use crate::protocol::W3cTraceContext;
use crate::user_input::UserInput;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde_json::Value;
use std::collections::BTreeMap;
@@ -52,6 +53,55 @@ pub struct ResponseItemAnnotations {
pub retain: bool,
}
/// A Responses item for history injection. Wrapped items must specify a policy.
#[derive(Clone, Debug)]
pub struct InjectedResponseItem {
pub item: ResponseItem,
pub retention: ItemRetention,
}
impl<'de> Deserialize<'de> for InjectedResponseItem {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
struct WrappedItem {
item: ResponseItem,
retention: ItemRetention,
}
let value = Value::deserialize(deserializer)?;
if value.get("type").is_some() {
return ResponseItem::deserialize(value)
.map(Self::from)
.map_err(serde::de::Error::custom);
}
let WrappedItem { item, retention } =
WrappedItem::deserialize(value).map_err(serde::de::Error::custom)?;
Ok(Self { item, retention })
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ItemRetention {
/// Raw items use the thread's configured retention rules.
#[serde(skip)]
Unspecified,
/// Apply ordinary history rules even when the thread retains client developer messages.
Default,
/// Carry the item into later context windows and newly spawned children.
Always,
}
impl From<ResponseItem> for InjectedResponseItem {
fn from(item: ResponseItem) -> Self {
Self {
item,
retention: ItemRetention::Unspecified,
}
}
}
/// One turn input and the context that follows it through submission.
///
/// Callers choose start-or-steer, idle-start, or steer-only behavior through