Batch persistent history reads during reverse search (#33905)

## Why

Reverse search fetched persistent history one entry at a time, making a deep or unsuccessful search require a lookup for every older entry.

## What changed

- Keep the newest-entry probe lightweight, then scan older persistent history in bounded batches.
- Cache batch results by absolute offset and preserve continuation cursors across repeated searches.
- Ignore stale responses, retry failed batch reads a bounded number of times, and restore the draft without reporting a false “no match” state when history is unavailable.

## Testing

- Cover batch continuation, caching after query changes or cancellation, stale metadata, retry exhaustion, and thread routing.
- Verify that scanning 1,024 entries uses one single-entry lookup and eight batch lookups.

GitOrigin-RevId: efc791fcbd5290050af703dd31c5c21b33240e23
This commit is contained in:
Charlie Marsh authored and copyberry committed 2026-07-17 21:16:07 +00:00
1 parent 025db22058
commit 7ed19a9758
13 files changed
+965 -107

No files matched your search

+37 -3
View File
@@ -32,6 +32,7 @@ use codex_app_server_protocol::SkillsListResponse;
use codex_app_server_protocol::ThreadGoalStatus;
use codex_connectors::AppInfo;
use codex_file_search::FileMatch;
use codex_message_history::HistoryBatchCursor;
use codex_protocol::ThreadId;
use codex_protocol::openai_models::ModelPreset;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -65,13 +66,39 @@ pub(crate) enum ThreadGoalSetMode {
},
}
#[derive(Debug, Clone)]
pub(crate) struct HistoryLookupResponse {
/// One absolute history offset returned by a batch lookup.
///
/// Malformed rows retain their offset with `entry` set to `None` so the composer can cache the gap
/// without shifting every older record.
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct HistoryBatchEntryResponse {
pub(crate) offset: usize,
pub(crate) log_id: u64,
pub(crate) entry: Option<String>,
}
/// Persistent-history data routed back to the thread that requested it.
///
/// Batch responses preserve absolute offsets and malformed-row gaps so the composer can cache the
/// data independently of whichever search query is active when the response arrives.
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum HistoryLookupResponse {
Entry {
offset: usize,
log_id: u64,
entry: Option<String>,
},
Batch {
cursor: HistoryBatchCursor,
log_id: u64,
entries: Vec<HistoryBatchEntryResponse>,
next_older_cursor: Option<HistoryBatchCursor>,
},
BatchError {
cursor: HistoryBatchCursor,
log_id: u64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ConsolidationScrollbackReflow {
IfResizeReflowRan,
@@ -200,6 +227,13 @@ pub(crate) enum AppEvent {
log_id: u64,
},
/// Fetch a bounded batch of persistent history entries for reverse search.
LookupMessageHistoryBatch {
thread_id: ThreadId,
cursor: HistoryBatchCursor,
log_id: u64,
},
/// Start a new session.
NewSession,