mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
Deduplicate retained instructions against Guardian transcripts (#48109)
## Why Synchronous Guardian reviews can include the same user instruction in both the retained instructions section and the conversation transcript. Avoid this duplication while preserving complete authorization evidence and its original acceptance order. ## What changed - Carry retained source metadata into transcript rendering. Omit a retained instruction when the transcript delivers its complete matching source revision. - Label qualifying transcript entries with retained source order and protect them from budget truncation. Keep separate retained instructions when source metadata is missing or incomplete. - Track delivery of source-order guidance so it remains available even when all retained instructions are delivered through the transcript. Invalidate that delivery proof when messages are shortened. ## Testing Add coverage for complete-source deduplication, missing or incomplete metadata, budget protection, guidance redelivery, and source metadata preservation across resume. Update Guardian review assertions and snapshots to verify instructions are delivered once. GitOrigin-RevId: 20ad0304501fa49f1596c4cfaf3ef89d64815753
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
//! call ID retain their generic label when the call is unavailable. Outputs
|
||||
//! without a call ID require an explicit name.
|
||||
|
||||
use codex_history::RetainedContextEntry;
|
||||
use codex_protocol::protocol::TruncationPolicy;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use codex_protocol::mcp::is_node_repl_backed_tool;
|
||||
@@ -114,8 +116,13 @@ pub fn collect_transcript(
|
||||
let mut entries = Vec::new();
|
||||
let mut tool_names_by_call_id = HashMap::new();
|
||||
let mut heartbeat_versions = HashMap::new();
|
||||
// Positional legacy labels cannot establish reusable delivery proof, including
|
||||
// through transcript copies. The separate retained section stays authoritative.
|
||||
let retained_context = history
|
||||
.retained_context()
|
||||
.filter(|context| !crate::retained_instructions::has_legacy_order(context));
|
||||
|
||||
for item in history.items() {
|
||||
for (item, mut source) in history.items_with_sources() {
|
||||
let (kind, mut text) = match item {
|
||||
ResponseItem::Message {
|
||||
role,
|
||||
@@ -308,6 +315,8 @@ pub fn collect_transcript(
|
||||
if let Some(heartbeat) = codex_history::Heartbeat::from_message(item) {
|
||||
match heartbeat_versions.get(heartbeat.automation_id) {
|
||||
Some((instructions, number)) if *instructions == heartbeat.instructions => {
|
||||
// A reference no longer delivers the complete source instruction.
|
||||
source = None;
|
||||
text = format!(
|
||||
"Scheduled automation {} ran at {}. Instructions unchanged from transcript entry [{}]; this is a replay of that earlier instruction, not a new human instruction. If the referenced instructions are unavailable, do not infer authorization from this reference.",
|
||||
heartbeat.automation_id, heartbeat.timestamp, number
|
||||
@@ -344,6 +353,39 @@ pub fn collect_transcript(
|
||||
}
|
||||
};
|
||||
entries.push(ConversationTranscriptEntry {
|
||||
retained_source: retained_context.and_then(|context| {
|
||||
let source = source.filter(|source| {
|
||||
source.complete
|
||||
&& kind == ConversationTranscriptEntryKind::User
|
||||
&& source.id.role == codex_history::RetainedSourceRole::User
|
||||
&& Some(source.id.message_id.as_str())
|
||||
== item.id().map(codex_protocol::ResponseItemId::as_str)
|
||||
&& source.id.turn_id == item.turn_id().unwrap_or_default()
|
||||
})?;
|
||||
crate::retained_instructions::source_order_labels(context).find_map(
|
||||
|(order, entry)| {
|
||||
if context.source(entry).as_ref() != Some(source) {
|
||||
return None;
|
||||
}
|
||||
let RetainedContextEntry::UserMessage(message) = entry else {
|
||||
return None;
|
||||
};
|
||||
let rendered = format!(
|
||||
"Retained source order: {order}\n{}",
|
||||
crate::GuardianRootMessage::User(message.text.clone()).render()
|
||||
);
|
||||
(rendered.len()
|
||||
<= TruncationPolicy::Tokens(
|
||||
crate::retained_instructions::MAX_INSTRUCTION_TOKENS,
|
||||
)
|
||||
.byte_budget())
|
||||
.then(|| crate::RetainedTranscriptSource {
|
||||
order,
|
||||
source: source.clone(),
|
||||
})
|
||||
},
|
||||
)
|
||||
}),
|
||||
kind,
|
||||
text,
|
||||
original_bytes,
|
||||
|
||||
Reference in New Issue
Block a user