mirror of
https://github.com/openai/codex.git
synced 2026-10-27 18:48:37 +00:00
Reuse current model context across paginated resume config loading (#56224)
## Why Resuming a paginated thread rechecks stored history after loading configuration. Unchanged model context can be reused instead of loading it again. ## What changed Retain the prepared model context across configuration loading and reuse it when the thread ID, rollout path, and history revision still match. Add `ThreadStore::is_model_context_current` to validate revisions, with a fresh read when validation fails or is unavailable. ## Testing Add coverage for revision invalidation after history is appended or removed. Extend resume tests to cover paginated history, concurrent metadata and lifecycle changes, workspace-root changes, and preservation of saved user messages in model requests. GitOrigin-RevId: bad2ca0caddb63523264ac530ffb801db3961f2b
This commit is contained in:
@@ -616,6 +616,25 @@ impl ThreadStore for LocalThreadStore {
|
||||
Box::pin(async move { model_context::load_latest_model_context(self, params).await })
|
||||
}
|
||||
|
||||
fn is_model_context_current(
|
||||
&self,
|
||||
params: LoadThreadHistoryParams,
|
||||
revision: String,
|
||||
) -> ThreadStoreFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
let resolved = if params.include_archived {
|
||||
thread_rollout_resolver::resolve_current_including_archived(self, params.thread_id)
|
||||
.await?
|
||||
} else {
|
||||
thread_rollout_resolver::resolve_current(self, params.thread_id).await?
|
||||
};
|
||||
let Some(resolved) = resolved else {
|
||||
return Ok(false);
|
||||
};
|
||||
Ok(history_revision::read(&resolved.path).await.as_ref() == Some(&revision))
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_fork(&self, params: PrepareForkParams) -> ThreadStoreFuture<'_, PreparedFork> {
|
||||
Box::pin(async move { paginated_fork::prepare(self, params).await })
|
||||
}
|
||||
|
||||
@@ -33,6 +33,62 @@ use crate::ThreadStore;
|
||||
use crate::local::test_support::test_config;
|
||||
use crate::local::test_support::write_session_file_with_history_mode;
|
||||
|
||||
#[tokio::test]
|
||||
async fn model_context_revision_invalidates_after_append_or_removal() {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
let uuid = Uuid::from_u128(/*v*/ 1009);
|
||||
let thread_id = ThreadId::from_string(&uuid.to_string()).expect("thread id");
|
||||
let path = write_paginated_rollout(
|
||||
home.path(),
|
||||
"2025-01-03T13-00-08",
|
||||
uuid,
|
||||
[user_message("saved message")],
|
||||
);
|
||||
let store = LocalThreadStore::new(test_config(home.path()), /*state_db*/ None);
|
||||
let params = LoadThreadHistoryParams {
|
||||
thread_id,
|
||||
include_archived: false,
|
||||
};
|
||||
let context = store
|
||||
.load_latest_model_context(params.clone())
|
||||
.await
|
||||
.expect("load model context");
|
||||
let revision = context.revision.expect("stable history revision");
|
||||
assert!(
|
||||
store
|
||||
.is_model_context_current(params.clone(), revision.clone())
|
||||
.await
|
||||
.expect("validate unchanged context")
|
||||
);
|
||||
|
||||
let new_item = user_message("message appended during configuration loading");
|
||||
append_items(&path, [new_item.clone()]);
|
||||
assert!(
|
||||
!store
|
||||
.is_model_context_current(params.clone(), revision.clone())
|
||||
.await
|
||||
.expect("invalidate stale context")
|
||||
);
|
||||
let updated = store
|
||||
.load_latest_model_context(params.clone())
|
||||
.await
|
||||
.expect("load updated model context");
|
||||
let mut expected = context.items;
|
||||
expected.push(new_item);
|
||||
assert_eq!(
|
||||
serde_json::to_value(updated.items).expect("serialize updated context"),
|
||||
serde_json::to_value(expected).expect("serialize expected context")
|
||||
);
|
||||
|
||||
std::fs::remove_file(path).expect("remove rollout");
|
||||
assert!(
|
||||
!store
|
||||
.is_model_context_current(params, revision)
|
||||
.await
|
||||
.expect("invalidate removed context")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stops_at_newest_usable_compaction_and_keeps_companions() {
|
||||
let home = TempDir::new().expect("temp dir");
|
||||
|
||||
@@ -268,6 +268,19 @@ pub trait ThreadStore: Any + Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks whether a previously loaded model context still represents the stored history.
|
||||
///
|
||||
/// Stores that cannot validate the opaque revision must return false. A false result or an
|
||||
/// error requires a fresh read. This does not acquire writer ownership;
|
||||
/// [`Self::resume_thread`] must still validate the snapshot after acquisition.
|
||||
fn is_model_context_current(
|
||||
&self,
|
||||
_params: LoadThreadHistoryParams,
|
||||
_revision: String,
|
||||
) -> ThreadStoreFuture<'_, bool> {
|
||||
Box::pin(async { Ok(false) })
|
||||
}
|
||||
|
||||
/// Freezes source history and model context used to initialize a referenced fork.
|
||||
///
|
||||
/// Stores without reference-backed fork support can retain this default implementation.
|
||||
|
||||
Reference in New Issue
Block a user