Preserve harness metadata across conversation history (#38058)

## What changed

- Wrap response items with optional harness-owned metadata while keeping the persisted response payload shape backward compatible.
- Store compacted-history metadata in an aligned sidecar and reject malformed sidecars.
- Carry metadata through history normalization, compaction, resume, fork, truncation, migration, and rollout recording without sending it to model providers.

## Testing

- Cover legacy rollout compatibility, metadata serialization and validation, compaction retention, and resume/fork reconstruction.

GitOrigin-RevId: 17a6127c9048ff984cb4df4d97c0751a38c29f5f
This commit is contained in:
rka-oai authored and copyberry committed 2026-08-11 19:30:12 +00:00
1 parent 44d992c14e
commit 3a6f747d77
67 files changed
+2075 -655

No files matched your search

+37 -2
View File
@@ -20,6 +20,7 @@ use time::macros::format_description;
use uuid::Uuid;
use crate::INTERACTIVE_SESSION_SOURCES;
use crate::ResponseItemEnvelope;
use crate::RolloutItem;
use crate::RolloutLine;
use crate::find_thread_path_by_id_str;
@@ -31,6 +32,7 @@ use crate::list::get_threads;
use crate::list::read_head_for_summary;
use crate::rollout_date_parts;
use anyhow::Result;
use codex_history::CodexHarnessMetadata;
use codex_protocol::ThreadId;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem;
@@ -1264,6 +1266,39 @@ async fn test_base_instructions_present_in_meta_is_preserved() {
assert_eq!(base, Some(base_text));
}
#[tokio::test]
async fn read_head_for_summary_omits_harness_metadata() {
let temp = TempDir::new().unwrap();
let rollout_path = temp.path().join("rollout.jsonl");
let response_item = ResponseItem::Message {
id: None,
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: "hello".to_string(),
}],
phase: None,
internal_chat_message_metadata_passthrough: None,
};
let line = RolloutLine {
timestamp: "2025-04-03T10:30:00Z".to_string(),
ordinal: None,
item: RolloutItem::ResponseItem(ResponseItemEnvelope {
item: response_item.clone(),
metadata: Some(CodexHarnessMetadata {}),
}),
};
fs::write(
&rollout_path,
format!("{}\n", serde_json::to_string(&line).unwrap()),
)
.unwrap();
let head = read_head_for_summary(&rollout_path).await.unwrap();
assert_eq!(head, vec![serde_json::to_value(response_item).unwrap()]);
assert!(head[0].get("metadata").is_none());
}
#[tokio::test]
async fn test_created_at_sort_uses_file_mtime_for_updated_at() -> Result<()> {
let temp = TempDir::new().unwrap();
@@ -1384,7 +1419,7 @@ async fn test_updated_at_uses_file_mtime() -> Result<()> {
let response_line = RolloutLine {
timestamp: format!("{ts}-{idx:02}"),
ordinal: None,
item: RolloutItem::ResponseItem(ResponseItem::Message {
item: RolloutItem::ResponseItem(ResponseItemEnvelope::new(ResponseItem::Message {
id: None,
role: "assistant".into(),
content: vec![ContentItem::OutputText {
@@ -1392,7 +1427,7 @@ async fn test_updated_at_uses_file_mtime() -> Result<()> {
}],
phase: None,
internal_chat_message_metadata_passthrough: None,
}),
})),
};
writeln!(file, "{}", serde_json::to_string(&response_line)?)?;
}