Reduce indexing overhead and repair agent search and child recovery

This commit is contained in:
2026-09-14 16:25:49 -05:00
parent 81b4c78b61
commit 506dc5b225
38 changed files with 1941 additions and 218 deletions
+28 -27
View File
@@ -495,6 +495,7 @@ impl ProviderRun {
pub fn replace_tool_result_archive(&mut self, archive: Vec<ConversationMessage>) {
self.tool_result_archive = archive;
archive_tool_results(&mut self.tool_result_archive, Vec::new());
}
/// Replaces a summarized prefix while the run is parked between model calls.
@@ -782,6 +783,7 @@ impl ProviderRun {
&mut self,
recoverable_call_ids: &HashSet<String>,
) -> Result<ProviderRunRestoreNormalization, ProviderRunProtocolError> {
archive_tool_results(&mut self.tool_result_archive, Vec::new());
if matches!(self.state, ProviderRunState::AwaitingModel { .. }) {
self.state = ProviderRunState::Failed {
failure: ProviderRunFailure {
@@ -1740,43 +1742,42 @@ fn add_usage(total: &mut Usage, turn: &Usage) {
.saturating_add(turn.cache_creation_input_tokens);
}
fn archive_tool_results(
/// Retain tool history once per call/result ID, including when repairing older archives.
/// A multipart message is appended once, never once per tool contained in it.
pub fn archive_tool_results(
archive: &mut Vec<ConversationMessage>,
messages: Vec<ConversationMessage>,
) {
const MAX_TOOL_RESULT_ARCHIVE_ENTRIES: usize = 400;
let mut pending_tool_uses = Vec::new();
for message in messages {
match &message.content {
MessageContent::ToolUse { .. } => pending_tool_uses.push(message),
MessageContent::ToolResult { .. } => {
if let Some(tool_use) = pending_tool_uses.pop() {
archive.push(tool_use);
}
archive.push(message);
let mut seen_uses = HashSet::new();
let mut seen_results = HashSet::new();
let previous = std::mem::take(archive);
for mut message in previous.into_iter().chain(messages) {
let keep = match &mut message.content {
MessageContent::ToolUse { tool_use_id, .. } => seen_uses.insert(tool_use_id.clone()),
MessageContent::ToolResult { tool_use_id, .. } => {
seen_results.insert(tool_use_id.clone())
}
MessageContent::MultiPart(parts) => {
for part in parts {
match part {
ContentPart::ToolUse { .. } => pending_tool_uses.push(message.clone()),
ContentPart::ToolResult { .. } => {
if let Some(tool_use) = pending_tool_uses.pop() {
archive.push(tool_use);
}
archive.push(message.clone());
}
ContentPart::Text(_)
| ContentPart::Reasoning { .. }
| ContentPart::Image { .. } => {}
parts.retain(|part| match part {
ContentPart::ToolUse { tool_use_id, .. } => {
seen_uses.insert(tool_use_id.clone())
}
}
ContentPart::ToolResult { tool_use_id, .. } => {
seen_results.insert(tool_use_id.clone())
}
ContentPart::Text(_)
| ContentPart::Reasoning { .. }
| ContentPart::Image { .. } => false,
});
!parts.is_empty()
}
MessageContent::Text(_) => {}
MessageContent::Text(_) => false,
};
if keep {
archive.push(message);
}
}
archive.extend(pending_tool_uses);
if archive.len() > MAX_TOOL_RESULT_ARCHIVE_ENTRIES {
archive.drain(0..archive.len() - MAX_TOOL_RESULT_ARCHIVE_ENTRIES);
}
@@ -1426,3 +1426,42 @@ fn serialized_numeric_turn_budgets_remain_explicit_limits() {
assert_eq!(limits.max_model_turns, Some(100));
assert_eq!(ProviderRunLimits::default().max_model_turns, None);
}
#[test]
fn multipart_archive_is_not_multiplied_and_legacy_duplicates_are_repaired() {
let mut run = run();
let batch = accept_tool_turn(
&mut run,
tool_turn(
(0..4)
.map(|id| tool_call(&format!("search-{id}"), "search_codebase"))
.collect(),
&["search_codebase"],
),
);
for id in 0..4 {
run.complete_tool(
&batch.work_id,
successful_result(&format!("search-{id}"), &format!("file contents {id}")),
)
.unwrap();
}
run.commit_tool_batch(&batch.work_id).unwrap();
let transcript = run.transcript().to_vec();
let mut archive = Vec::new();
archive_tool_results(&mut archive, transcript.clone());
assert_eq!(archive.len(), 2);
for message in &archive {
let MessageContent::MultiPart(parts) = &message.content else {
panic!("expected multipart tools");
};
assert_eq!(parts.len(), 4);
}
let expected = archive.clone();
let duplicated: Vec<_> = (0..4).flat_map(|_| expected.clone()).collect();
run.replace_tool_result_archive(duplicated);
assert_eq!(run.tool_result_archive(), expected);
// A repeated compaction callback must not grow the archive either.
archive_tool_results(&mut archive, transcript);
assert_eq!(archive, expected);
}