mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Share Guardian user-message retention logic (#42031)
## What changed - Add a shared `select_user_messages` helper in `guardian-context` that always anchors the first user message and fills the remaining token budget from newest to oldest. - Use the helper for both core Guardian prompt rendering and the Guardian V2 async scorer so they apply the same retention policy. ## Testing - Cover empty and single-message transcripts, over-budget anchors, and newest-first selection across multiple token budgets. GitOrigin-RevId: 7db246013875263ceb5806d2a6a19b4e2a3db1c4
This commit is contained in:
@@ -398,45 +398,27 @@ fn render_guardian_transcript_entries_with_offset(
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut included = vec![false; entries.len()];
|
||||
let mut message_tokens = 0usize;
|
||||
let mut tool_tokens = 0usize;
|
||||
let user_indices = entries
|
||||
let user_messages = entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, entry)| {
|
||||
matches!(entry.kind, ConversationTranscriptEntryKind::User).then_some(index)
|
||||
matches!(entry.kind, ConversationTranscriptEntryKind::User).then_some(
|
||||
codex_guardian_context::UserMessageCost {
|
||||
index,
|
||||
tokens: rendered_entries[index].1,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(&first_user_index) = user_indices.first() {
|
||||
included[first_user_index] = true;
|
||||
message_tokens += rendered_entries[first_user_index].1;
|
||||
}
|
||||
|
||||
if let Some(&last_user_index) = user_indices.last()
|
||||
&& !included[last_user_index]
|
||||
&& message_tokens + rendered_entries[last_user_index].1
|
||||
<= GUARDIAN_TRANSCRIPT_RETENTION.max_message_transcript_tokens
|
||||
{
|
||||
included[last_user_index] = true;
|
||||
message_tokens += rendered_entries[last_user_index].1;
|
||||
}
|
||||
|
||||
for &index in user_indices.iter().rev() {
|
||||
if included[index] {
|
||||
continue;
|
||||
}
|
||||
|
||||
let token_count = rendered_entries[index].1;
|
||||
if message_tokens + token_count
|
||||
> GUARDIAN_TRANSCRIPT_RETENTION.max_message_transcript_tokens
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let selection = codex_guardian_context::select_user_messages(
|
||||
&user_messages,
|
||||
GUARDIAN_TRANSCRIPT_RETENTION.max_message_transcript_tokens,
|
||||
);
|
||||
for index in selection.indices {
|
||||
included[index] = true;
|
||||
message_tokens += token_count;
|
||||
}
|
||||
let mut message_tokens = selection.tokens;
|
||||
let mut tool_tokens = 0usize;
|
||||
|
||||
let mut retained_non_user_entries = 0usize;
|
||||
for index in (0..entries.len()).rev() {
|
||||
|
||||
@@ -225,41 +225,28 @@ impl TranscriptConfig {
|
||||
}
|
||||
|
||||
let mut included = vec![false; entries.len()];
|
||||
let mut message_tokens = 0;
|
||||
let user_indices = entries
|
||||
let user_messages = entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, entry)| (entry.kind == TranscriptEntryKind::User).then_some(index))
|
||||
.filter_map(|(index, entry)| {
|
||||
(entry.kind == TranscriptEntryKind::User).then_some(
|
||||
codex_guardian_context::UserMessageCost {
|
||||
index,
|
||||
tokens: entry.tokens,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(&first_user_index) = user_indices.first() {
|
||||
included[first_user_index] = true;
|
||||
message_tokens += entries[first_user_index].tokens;
|
||||
}
|
||||
|
||||
if let Some(&latest_user_index) = user_indices.last()
|
||||
&& !included[latest_user_index]
|
||||
&& message_tokens + entries[latest_user_index].tokens
|
||||
<= retention.max_message_transcript_tokens
|
||||
{
|
||||
included[latest_user_index] = true;
|
||||
message_tokens += entries[latest_user_index].tokens;
|
||||
}
|
||||
|
||||
for &index in user_indices.iter().rev() {
|
||||
if included[index]
|
||||
|| message_tokens + entries[index].tokens > retention.max_message_transcript_tokens
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let selection = codex_guardian_context::select_user_messages(
|
||||
&user_messages,
|
||||
retention.max_message_transcript_tokens,
|
||||
);
|
||||
for index in selection.indices {
|
||||
included[index] = true;
|
||||
message_tokens += entries[index].tokens;
|
||||
}
|
||||
|
||||
let available_message_tokens = retention
|
||||
.max_message_transcript_tokens
|
||||
.saturating_sub(message_tokens);
|
||||
.saturating_sub(selection.tokens);
|
||||
let mut window = TranscriptWindow::new(&entries, retention, available_message_tokens);
|
||||
for index in 0..entries.len() {
|
||||
window.insert(index);
|
||||
|
||||
@@ -19,6 +19,9 @@ use transcript::ConversationTranscriptSection;
|
||||
pub use entry::ConversationTranscriptEntry;
|
||||
pub use entry::ConversationTranscriptEntryKind;
|
||||
pub use history::TranscriptHistory;
|
||||
pub use retention::UserMessageCost;
|
||||
pub use retention::UserMessageSelection;
|
||||
pub use retention::select_user_messages;
|
||||
pub use transcript::ConversationTranscriptConfig;
|
||||
pub use transcript::ConversationTranscriptOptions;
|
||||
pub use transcript::MANUAL_APPROVAL_DEVELOPER_PREFIX;
|
||||
@@ -29,6 +32,7 @@ pub use truncation::truncate_text;
|
||||
|
||||
mod entry;
|
||||
mod history;
|
||||
mod retention;
|
||||
mod transcript;
|
||||
mod truncation;
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
//! Shared user-message selection, independent of rendering and non-user eviction.
|
||||
//!
|
||||
//! Callers supply costs including their own formatting. The first user message
|
||||
//! remains an anchor even when it exceeds the budget, preserving existing behavior.
|
||||
|
||||
/// Rendered cost and original transcript position of a genuine user message.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct UserMessageCost {
|
||||
pub index: usize,
|
||||
pub tokens: usize,
|
||||
}
|
||||
|
||||
/// Selected original indices and their total rendered token cost.
|
||||
#[derive(Debug, Default, Eq, PartialEq)]
|
||||
pub struct UserMessageSelection {
|
||||
pub indices: Vec<usize>,
|
||||
pub tokens: usize,
|
||||
}
|
||||
|
||||
/// Keeps the first and latest user messages, then fills from newest to oldest.
|
||||
///
|
||||
/// Supply messages in conversation order with distinct original indices. The
|
||||
/// returned indices are in selection order; callers render in conversation order.
|
||||
/// Non-user evidence uses the remaining budget under the caller's own policy.
|
||||
pub fn select_user_messages(
|
||||
messages: &[UserMessageCost],
|
||||
max_message_tokens: usize,
|
||||
) -> UserMessageSelection {
|
||||
let Some((first, remaining)) = messages.split_first() else {
|
||||
return UserMessageSelection::default();
|
||||
};
|
||||
let mut selection = UserMessageSelection {
|
||||
indices: vec![first.index],
|
||||
tokens: first.tokens,
|
||||
};
|
||||
// The latest message is considered first, including when it cannot fit.
|
||||
for message in remaining.iter().rev() {
|
||||
if selection.tokens.saturating_add(message.tokens) <= max_message_tokens {
|
||||
selection.indices.push(message.index);
|
||||
selection.tokens += message.tokens;
|
||||
}
|
||||
}
|
||||
selection
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "retention_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,48 @@
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn user_selection_preserves_anchors_and_fills_from_newest() {
|
||||
let messages = [
|
||||
UserMessageCost {
|
||||
index: 2,
|
||||
tokens: 4,
|
||||
},
|
||||
UserMessageCost {
|
||||
index: 5,
|
||||
tokens: 3,
|
||||
},
|
||||
UserMessageCost {
|
||||
index: 8,
|
||||
tokens: 6,
|
||||
},
|
||||
UserMessageCost {
|
||||
index: 10,
|
||||
tokens: 2,
|
||||
},
|
||||
];
|
||||
for (budget, indices, tokens) in [
|
||||
(0, vec![2], 4),
|
||||
(4, vec![2], 4),
|
||||
(6, vec![2, 10], 6),
|
||||
(9, vec![2, 10, 5], 9),
|
||||
(12, vec![2, 10, 8], 12),
|
||||
(15, vec![2, 10, 8, 5], 15),
|
||||
] {
|
||||
assert_eq!(
|
||||
select_user_messages(&messages, budget),
|
||||
UserMessageSelection { indices, tokens },
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
select_user_messages(&[], /*max_message_tokens*/ 10),
|
||||
UserMessageSelection::default(),
|
||||
);
|
||||
assert_eq!(
|
||||
select_user_messages(&messages[..1], /*max_message_tokens*/ 10),
|
||||
UserMessageSelection {
|
||||
indices: vec![2],
|
||||
tokens: 4
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user