mirror of
https://github.com/openai/codex.git
synced 2026-10-27 18:48:37 +00:00
## Description This PR adds a new `historyMode = "legacy" | "paginated"` to `Thread`. This will be stored in `SessionMeta` in the JSONL rollout file and as a new column in the SQLite thread_metadata table, and exposed on `thread/start` and on the `Thread` object in app-server. ## What changed - Added canonical `ThreadHistoryMode` with `legacy` and `paginated`, defaulting old and new SessionMeta to `legacy`. - Carried `history_mode` through core session config, ThreadStore stored metadata, local/in-memory stores, rollout metadata extraction, and the existing SQLite `threads` table. - Added experimental `historyMode` to app-server v2 `Thread` and `thread/start`. - Made paginated stored threads metadata-discoverable but unsupported for legacy full-history reads, `load_history`, live resume, and create paths. - Regenerated app-server schema fixtures and added protocol/state/thread-store/app-server coverage for persistence and fail-closed behavior. ## Compatibility floor Because users may be running various versions of Codex binaries on the same machine (TUI, Codex App, etc.), we will need to establish a compatibility floor for upcoming paginated threads, which will change how thread storage reads and writes work. The overall plan here: ``` Release N: - Add historyMode to SessionMeta / Thread / SQLite metadata. - Teach binaries to understand paginated threads. - If a binary sees `historyMode="paginated"` but does not support the paginated contract, it refuses to resume/mutate the thread. - Default remains `"legacy"`. Release N+1: - First-party clients start opting into paginated threads where appropriate. - Internal dogfood / staged rollout. - Measure old-client usage and paginated-thread unsupported errors. Release N+2: - Only after Release N+ is overwhelmingly deployed, make paginated the default. - Accept that a small tail of N-1-or-older binaries may not understand paginated threads. ``` The important behavior change is fail-closed handling for a binary that encounters a persisted `paginated` thread before it knows how to fully support paginated history. In app-server, if a thread is `paginated`, we will: - allow metadata-only discovery paths like `thread/list` and `thread/read(includeTurns=false)`, so clients can still see the thread and inspect its `historyMode` - reject legacy full-history/live-thread paths like `thread/read(includeTurns=true)` and `thread/resume` with an unsupported JSON-RPC error - avoid silently treating an unknown or future `historyMode` as `legacy` Under the hood, the ThreadStore layer also rejects legacy operations that would need to load or replay the full thread history for a paginated thread. That gives us the behavior we want for Release N: future paginated threads are visible, but this binary fails closed instead of trying to operate on them as if they were legacy threads.
141 lines
5.5 KiB
Rust
141 lines
5.5 KiB
Rust
use codex_protocol::ThreadId;
|
|
use codex_protocol::protocol::ThreadHistoryMode;
|
|
use std::any::Any;
|
|
use std::future::Future;
|
|
use std::pin::Pin;
|
|
|
|
use crate::AppendThreadItemsParams;
|
|
use crate::ArchiveThreadParams;
|
|
use crate::CreateThreadParams;
|
|
use crate::DeleteThreadParams;
|
|
use crate::ItemPage;
|
|
use crate::ListItemsParams;
|
|
use crate::ListThreadsParams;
|
|
use crate::ListTurnsParams;
|
|
use crate::LoadThreadHistoryParams;
|
|
use crate::ReadThreadByRolloutPathParams;
|
|
use crate::ReadThreadParams;
|
|
use crate::ResumeThreadParams;
|
|
use crate::SearchThreadsParams;
|
|
use crate::StoredThread;
|
|
use crate::StoredThreadHistory;
|
|
use crate::ThreadPage;
|
|
use crate::ThreadSearchPage;
|
|
use crate::ThreadStoreError;
|
|
use crate::ThreadStoreResult;
|
|
use crate::TurnPage;
|
|
use crate::UpdateThreadMetadataParams;
|
|
|
|
/// Future returned by [`ThreadStore`] operations.
|
|
pub type ThreadStoreFuture<'a, T> = Pin<Box<dyn Future<Output = ThreadStoreResult<T>> + Send + 'a>>;
|
|
|
|
/// Storage-neutral thread persistence boundary.
|
|
pub trait ThreadStore: Any + Send + Sync {
|
|
/// Return this store as [`Any`] for implementation-owned escape hatches.
|
|
fn as_any(&self) -> &dyn Any;
|
|
|
|
/// Returns the history mode to use when history does not carry a persisted mode.
|
|
///
|
|
/// The default is legacy so existing stores stay compatible. Stores whose durable contract is
|
|
/// already paginated should override this instead of relying on core to infer storage behavior.
|
|
fn default_history_mode(&self) -> ThreadHistoryMode {
|
|
ThreadHistoryMode::Legacy
|
|
}
|
|
|
|
/// Creates a new live thread.
|
|
fn create_thread(&self, params: CreateThreadParams) -> ThreadStoreFuture<'_, ()>;
|
|
|
|
/// Reopens an existing thread for live appends.
|
|
fn resume_thread(&self, params: ResumeThreadParams) -> ThreadStoreFuture<'_, ()>;
|
|
|
|
/// Appends raw rollout items to a live thread.
|
|
///
|
|
/// Implementations should apply the shared rollout persistence policy before writing durable
|
|
/// replay history and before updating any implementation-owned projections.
|
|
fn append_items(&self, params: AppendThreadItemsParams) -> ThreadStoreFuture<'_, ()>;
|
|
|
|
/// Materializes the thread if persistence is lazy, then persists all queued items.
|
|
fn persist_thread(&self, thread_id: ThreadId) -> ThreadStoreFuture<'_, ()>;
|
|
|
|
/// Flushes all queued items and returns once they are durable/readable.
|
|
fn flush_thread(&self, thread_id: ThreadId) -> ThreadStoreFuture<'_, ()>;
|
|
|
|
/// Flushes pending items and closes the live thread writer.
|
|
fn shutdown_thread(&self, thread_id: ThreadId) -> ThreadStoreFuture<'_, ()>;
|
|
|
|
/// Discards the live thread writer without forcing pending in-memory items to become durable.
|
|
///
|
|
/// Core calls this when session initialization fails after a live writer has been created.
|
|
/// Implementations should release any live writer resources for the thread while preserving
|
|
/// already-durable thread data.
|
|
fn discard_thread(&self, thread_id: ThreadId) -> ThreadStoreFuture<'_, ()>;
|
|
|
|
/// Loads persisted history for resume, fork, rollback, and memory jobs.
|
|
fn load_history(
|
|
&self,
|
|
params: LoadThreadHistoryParams,
|
|
) -> ThreadStoreFuture<'_, StoredThreadHistory>;
|
|
|
|
/// Reads a thread summary and optionally its persisted history.
|
|
fn read_thread(&self, params: ReadThreadParams) -> ThreadStoreFuture<'_, StoredThread>;
|
|
|
|
/// Reads a rollout-backed thread by path when the store supports path-addressed lookups.
|
|
///
|
|
/// Deprecated: new callers should use [`ThreadStore::read_thread`] instead.
|
|
fn read_thread_by_rollout_path(
|
|
&self,
|
|
params: ReadThreadByRolloutPathParams,
|
|
) -> ThreadStoreFuture<'_, StoredThread>;
|
|
|
|
/// Lists stored threads matching the supplied filters.
|
|
fn list_threads(&self, params: ListThreadsParams) -> ThreadStoreFuture<'_, ThreadPage>;
|
|
|
|
/// Searches stored threads and returns search-only preview metadata.
|
|
fn search_threads(
|
|
&self,
|
|
_params: SearchThreadsParams,
|
|
) -> ThreadStoreFuture<'_, ThreadSearchPage> {
|
|
Box::pin(async {
|
|
Err(ThreadStoreError::Unsupported {
|
|
operation: "thread/search",
|
|
})
|
|
})
|
|
}
|
|
|
|
/// Lists turns within a stored thread.
|
|
fn list_turns(&self, _params: ListTurnsParams) -> ThreadStoreFuture<'_, TurnPage> {
|
|
Box::pin(async {
|
|
Err(ThreadStoreError::Unsupported {
|
|
operation: "list_turns",
|
|
})
|
|
})
|
|
}
|
|
|
|
/// Lists persisted items within a stored thread, optionally filtered to a turn.
|
|
fn list_items(&self, _params: ListItemsParams) -> ThreadStoreFuture<'_, ItemPage> {
|
|
Box::pin(async {
|
|
Err(ThreadStoreError::Unsupported {
|
|
operation: "list_items",
|
|
})
|
|
})
|
|
}
|
|
|
|
/// Applies a literal metadata patch and returns the updated thread.
|
|
///
|
|
/// Implementations should apply the supplied fields directly. Policy such as deciding whether
|
|
/// an append-derived preview should be emitted belongs above the store.
|
|
fn update_thread_metadata(
|
|
&self,
|
|
params: UpdateThreadMetadataParams,
|
|
) -> ThreadStoreFuture<'_, StoredThread>;
|
|
|
|
/// Archives a thread.
|
|
fn archive_thread(&self, params: ArchiveThreadParams) -> ThreadStoreFuture<'_, ()>;
|
|
|
|
/// Unarchives a thread and returns its updated metadata.
|
|
fn unarchive_thread(&self, params: ArchiveThreadParams) -> ThreadStoreFuture<'_, StoredThread>;
|
|
|
|
/// Deletes a thread's persisted rollout data and associated metadata.
|
|
fn delete_thread(&self, params: DeleteThreadParams) -> ThreadStoreFuture<'_, ()>;
|
|
}
|