mirror of
https://github.com/openai/codex.git
synced 2026-10-24 18:18:41 +00:00
Defer synchronous Guardian tool-call truncation to preparation (#56505)
## What changed Preserve complete tool-call text when collecting synchronous Guardian context, then apply the profile's tool token limit when preparing the transcript. Calculate retained bytes and token usage from the truncated text. ## Testing Extend the oversized-action regression test to verify that a subsequent review includes the previous call's arguments truncated to the 1,000-token history limit. GitOrigin-RevId: 2b52505d9ccfa3651b3535e67cc12597be819459
This commit is contained in:
@@ -267,8 +267,8 @@ pub(crate) fn render_guardian_transcript_entries(
|
||||
/// Keep both tool calls and tool results here. The reviewer often needs the
|
||||
/// agent's exact queried path / arguments as well as the returned evidence to
|
||||
/// decide whether the pending approval is justified.
|
||||
/// Per-entry truncation happens during collection, using the current review's
|
||||
/// Node REPL cap; the cursor still counts every non-empty evidence entry.
|
||||
/// Tool-call truncation happens in sync preparation; other caps still apply
|
||||
/// during collection. The cursor counts every non-empty evidence entry.
|
||||
pub(super) fn collect_guardian_context(
|
||||
history: &dyn SectionHistory,
|
||||
node_repl_result_token_limit: usize,
|
||||
|
||||
@@ -808,6 +808,9 @@ async fn oversized_action_preserves_review_policy_and_next_review(
|
||||
.message_input_texts("user")
|
||||
.concat();
|
||||
assert!(context.contains("echo complete-action-review"));
|
||||
let historical_arguments =
|
||||
codex_guardian_context::truncate_text(&oversized, /*max_tokens*/ 1_000);
|
||||
assert!(context.contains(&format!("tool exec_command call: {historical_arguments}\n")));
|
||||
assert!(
|
||||
requests
|
||||
.last()
|
||||
|
||||
@@ -294,17 +294,15 @@ fn planned_action_budget_omits_descriptions_without_changing_arguments() {
|
||||
)
|
||||
});
|
||||
let required = bounded.render(action_presentation);
|
||||
let context = (crate::CollectedContext {
|
||||
sections: vec![crate::ContextSection::PlannedAction(action.clone())],
|
||||
})
|
||||
.compose(
|
||||
presentation,
|
||||
crate::PreparedTranscript {
|
||||
items: Vec::new(),
|
||||
omission_note: None,
|
||||
truncations: Vec::new(),
|
||||
},
|
||||
)
|
||||
let context = {
|
||||
let context = crate::CollectedContext {
|
||||
sections: vec![crate::ContextSection::PlannedAction(action.clone())],
|
||||
};
|
||||
let profile = &crate::ContextProfile::synchronous();
|
||||
let transcript = profile
|
||||
.prepare_transcript(context.transcript_entries(), /*entry_number_offset*/ 0);
|
||||
context.compose(presentation, transcript)
|
||||
}
|
||||
.unwrap();
|
||||
let mut required_items = required.iter().map(|item| text(item)).collect::<Vec<_>>();
|
||||
required_items.push(text("evidence omitted"));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Resolved Guardian evidence profiles and pure transcript retention.
|
||||
//! Resolved Guardian evidence profiles, transcript admission and retention.
|
||||
//! Sync keeps recent entries; async protects approvals/final answers and evicts
|
||||
//! in cacheable chunks. Hosts still own history snapshots and delivery cursors.
|
||||
//! User messages and manual approvals stay complete until whole-request admission.
|
||||
@@ -148,17 +148,38 @@ impl ContextProfile {
|
||||
.as_ref()
|
||||
.filter(|_| entry.kind == ConversationTranscriptEntryKind::User);
|
||||
let prepare_text = |text: &str| {
|
||||
let text = match self.target {
|
||||
ContextTarget::Sync => match &entry.kind {
|
||||
ConversationTranscriptEntryKind::ToolCall(_) => {
|
||||
std::borrow::Cow::Owned(crate::truncate_text(
|
||||
text,
|
||||
self.transcript.entry_limits.tool_tokens,
|
||||
))
|
||||
}
|
||||
ConversationTranscriptEntryKind::User
|
||||
| ConversationTranscriptEntryKind::Developer
|
||||
| ConversationTranscriptEntryKind::Assistant
|
||||
| ConversationTranscriptEntryKind::ProtectedAssistant
|
||||
| ConversationTranscriptEntryKind::Reasoning
|
||||
| ConversationTranscriptEntryKind::ToolOutput(_)
|
||||
| ConversationTranscriptEntryKind::NodeReplToolOutput(_) => {
|
||||
std::borrow::Cow::Borrowed(text)
|
||||
}
|
||||
},
|
||||
ContextTarget::Async => std::borrow::Cow::Borrowed(text),
|
||||
};
|
||||
let retained_bytes = text.len();
|
||||
let record = TranscriptRecord::new(
|
||||
&entry.kind,
|
||||
number,
|
||||
text.to_owned(),
|
||||
text.into_owned(),
|
||||
retained_source.map(|retained| retained.order.clone()),
|
||||
self.transcript_format,
|
||||
suffix,
|
||||
);
|
||||
let tokens =
|
||||
TruncationPolicy::Bytes(record.rendered().text_bytes).token_budget();
|
||||
(TranscriptContent::Record(record), tokens, text.len())
|
||||
(TranscriptContent::Record(record), tokens, retained_bytes)
|
||||
};
|
||||
let (content, tokens, retained_bytes) = match &entry.content {
|
||||
TranscriptContent::Text(text) => prepare_text(text),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! Both Guardian consumers receive the same role and tool-source attribution,
|
||||
//! with generated compaction summaries attributed to the assistant, complete user
|
||||
//! messages and capped non-user entries. Resolved context profiles
|
||||
//! messages and capped non-user entries, except sync tool calls prepared later. Resolved context profiles
|
||||
//! apply aggregate retention after the host selects its full/delta slice. Tool outputs with a
|
||||
//! call ID retain their generic label when the call is unavailable. Outputs
|
||||
//! without a call ID require an explicit name.
|
||||
@@ -118,7 +118,7 @@ impl SectionContributor for ConversationTranscriptSection {
|
||||
|
||||
fn contribute(&self, input: &SectionInput<'_>) -> Result<Option<ContextSection>, SectionError> {
|
||||
Ok(Some(ContextSection::ConversationTranscript {
|
||||
items: collect_transcript(input.history, input.transcript),
|
||||
items: collect_transcript_for_target(input.history, input.transcript, input.target),
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,14 @@ impl SectionContributor for ConversationTranscriptSection {
|
||||
pub fn collect_transcript(
|
||||
history: &dyn SectionHistory,
|
||||
config: &ConversationTranscriptConfig,
|
||||
) -> Vec<ConversationTranscriptEntry> {
|
||||
collect_transcript_for_target(history, config, crate::ContextTarget::Async)
|
||||
}
|
||||
|
||||
fn collect_transcript_for_target(
|
||||
history: &dyn SectionHistory,
|
||||
config: &ConversationTranscriptConfig,
|
||||
target: crate::ContextTarget,
|
||||
) -> Vec<ConversationTranscriptEntry> {
|
||||
let mut entries = Vec::new();
|
||||
let mut tool_names_by_call_id = HashMap::new();
|
||||
@@ -402,6 +410,11 @@ pub fn collect_transcript(
|
||||
| ConversationTranscriptEntryKind::Reasoning => {
|
||||
truncate_text(&text, config.entry_limits.message_tokens)
|
||||
}
|
||||
ConversationTranscriptEntryKind::ToolCall(_)
|
||||
if target == crate::ContextTarget::Sync =>
|
||||
{
|
||||
text
|
||||
}
|
||||
ConversationTranscriptEntryKind::ToolCall(_)
|
||||
| ConversationTranscriptEntryKind::ToolOutput(_) => {
|
||||
truncate_text(&text, config.entry_limits.tool_tokens)
|
||||
|
||||
Reference in New Issue
Block a user