Add persisted manual ordering for thread sections (#36007)

## What changed

- Add `thread/section/move` to atomically move a thread into, within, or out of a section. Threads can be inserted before an existing member or appended, and moves within a section preserve `sectionEnteredAt`.
- Add `section_position` sorting to `thread/list`, with ascending order as its default, and expose `sectionEnteredAt` in thread responses.
- Persist section positions and entry times in SQLite, including migration of existing section members into recency order. Section membership is no longer updated through `thread/metadata/update`.

## Testing

- Cover section moves, reordering, pagination, persistence across restarts and rollout reconciliation, concurrent updates, and rank renumbering.

GitOrigin-RevId: aec6d7ddedca5277029b5caf5c074975397e956c
This commit is contained in:
joeytrasatti-openai authored and copyberry committed 2026-07-29 18:37:22 +00:00
1 parent c41a38dd10
commit ad6fc66b6d
75 files changed
+2879 -543

No files matched your search

@@ -53,6 +53,8 @@ fn stored_thread(cwd: &str, title: &str, first_user_message: &str) -> StoredThre
.expect("valid timestamp"),
archived_at: None,
section: None,
section_position: None,
section_entered_at: None,
cwd: PathBuf::from(cwd),
cli_version: "test".to_string(),
source: SessionSource::Cli,
+27
View File
@@ -72,6 +72,7 @@ use codex_thread_store::InMemoryThreadStore;
use codex_thread_store::LoadThreadHistoryParams;
use codex_thread_store::LocalThreadStore;
use codex_thread_store::LocalThreadStoreConfig;
use codex_thread_store::MoveThreadToSectionParams;
use codex_thread_store::PreparedFork;
use codex_thread_store::ReadThreadByRolloutPathParams;
use codex_thread_store::ReadThreadParams;
@@ -730,6 +731,32 @@ impl ThreadManager {
})
}
/// Moves a persisted thread to, within, or out of a server-ordered section.
pub async fn move_thread_to_section(
&self,
thread_id: ThreadId,
section: Option<&str>,
before_thread_id: Option<ThreadId>,
) -> CodexResult<()> {
if let Ok(thread) = self.get_thread(thread_id).await
&& thread.config_snapshot().await.ephemeral
{
return Err(CodexErr::InvalidRequest(format!(
"ephemeral thread does not support section moves: {thread_id}"
)));
}
self.state
.thread_store
.move_thread_to_section(MoveThreadToSectionParams {
thread_id,
section: section.map(ToOwned::to_owned),
before_thread_id,
})
.await
.map_err(|err| thread_store_metadata_update_error(thread_id, err))
}
/// List `thread_id` plus all known descendants in its spawn subtree.
pub async fn list_agent_subtree_thread_ids(
&self,