mirror of
https://github.com/openai/codex.git
synced 2026-11-14 14:49:31 +00:00
## Why Incomplete retained checkpoints can omit root user instructions that still survive in live history. Queued input can also reach model history after a later-accepted answer, so recording order cannot reliably order grants and restrictions for subagent authorization reviews. ## What changed - Reconcile retained evidence with surviving local user messages using source identity and persisted acceptance order, including answers present only in the checkpoint. - Preserve checkpoint gaps and mark evidence incomplete when recovered instructions lack an order or conflict with an existing order. - Restore the input-order counter from surviving local metadata so new instructions sort after recovered evidence, even without a retained checkpoint. ## Testing Add unit coverage for source matching, acceptance ordering, persistent gaps, conflicting orders, and counter restoration. Extend subagent authorization tests to cover checkpoint resume, queued approvals, missing sources, and a subsequent revocation. GitOrigin-RevId: 8bfbfd2c797d725796187e5cecf7f3f11a5f3380
63 lines
2.2 KiB
Rust
63 lines
2.2 KiB
Rust
//! Stateless selection of original host-verified question/answer records.
|
|
//! Omit a record atomically when it cannot fit; never truncate a restriction out of a grant.
|
|
|
|
use codex_history::RetainedContext;
|
|
use codex_history::RetainedContextEntry;
|
|
use codex_history::VerifiedAnswer;
|
|
|
|
use crate::GuardianRootMessage;
|
|
use codex_protocol::protocol::TruncationPolicy;
|
|
|
|
const MAX_ANSWER_TOKENS: usize = 900;
|
|
|
|
/// Request-bounded answers and whether they are complete enough for cached fast approval.
|
|
pub struct RenderedVerifiedAnswers {
|
|
pub fragments: Vec<String>,
|
|
pub complete: bool,
|
|
}
|
|
|
|
/// Renders each response with its original source roles and the existing approximate per-answer budget.
|
|
pub fn render_verified_answers(context: &RetainedContext) -> RenderedVerifiedAnswers {
|
|
let mut complete = context.verified_answers_complete();
|
|
let mut fragments = Vec::new();
|
|
for (order, (_, entry)) in context.ordered_entries().enumerate() {
|
|
let RetainedContextEntry::VerifiedAnswer(answer) = entry else {
|
|
continue;
|
|
};
|
|
if let Some(text) = render_verified_answer(answer) {
|
|
// The bounded order label is framing, not part of the existing answer budget.
|
|
fragments.push(format!("Retained source order: {order}\n{text}"));
|
|
} else {
|
|
complete = false;
|
|
}
|
|
}
|
|
if !complete {
|
|
fragments.insert(
|
|
/*index*/ 0,
|
|
GuardianRootMessage::IncompleteVerifiedAnswers.render(),
|
|
);
|
|
}
|
|
RenderedVerifiedAnswers {
|
|
fragments,
|
|
complete,
|
|
}
|
|
}
|
|
|
|
/// Selects one complete response, preserving both sides of every question/answer pair.
|
|
/// Returning None means the caller must account for missing evidence, not partial permission.
|
|
pub fn render_verified_answer(answer: &VerifiedAnswer) -> Option<String> {
|
|
let text = answer
|
|
.questions
|
|
.iter()
|
|
.map(|pair| {
|
|
format!(
|
|
"{}{}",
|
|
GuardianRootMessage::Assistant(pair.question.clone()).render(),
|
|
GuardianRootMessage::User(pair.answer.clone()).render()
|
|
)
|
|
})
|
|
.collect::<String>();
|
|
(!text.is_empty() && text.len() <= TruncationPolicy::Tokens(MAX_ANSWER_TOKENS).byte_budget())
|
|
.then_some(text)
|
|
}
|