Skip to content

Add thread recencyAt for sidebar ordering#27910

Merged
nornagon-openai merged 6 commits into
mainfrom
nornagon/codex/thread-recency-at
Jun 17, 2026
Merged

Add thread recencyAt for sidebar ordering#27910
nornagon-openai merged 6 commits into
mainfrom
nornagon/codex/thread-recency-at

Conversation

@nornagon-openai

@nornagon-openai nornagon-openai commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Add a server-owned recencyAt timestamp and recency_at thread-list sort key for product recency ordering while preserving the existing meaning of updatedAt as the latest persisted thread mutation.

This is the server-side alternative to #27697. Rather than narrowing updatedAt, clients can sort the sidebar by recency_at and continue treating updatedAt as mutation time.

Paired Codex Apps PR: openai/openai#1024599

Contract

  • recencyAt initializes when a thread is created.
  • A turn start advances recencyAt monotonically.
  • Commentary, agent output, tool results, token/accounting updates, turn completion, archive, unarchive, resume, and generic metadata writes do not advance it.
  • updatedAt retains its existing behavior and continues to advance for persisted thread mutations.
  • Current servers populate recencyAt; the response field is optional in generated TypeScript so clients connected to older servers can fall back to updatedAt.
  • Filesystem-only fallback uses existing updated/mtime ordering when SQLite is unavailable.

Persistence and compatibility

Migration 0038 adds second- and millisecond-precision recency columns, backfills them from the existing updated timestamp, creates list indexes, and includes an insert trigger so older binaries writing to a migrated database seed recency without causing later mutations to advance it.

Generic metadata upserts preserve existing recency values. Turn-start updates use a dedicated monotonic touch, and process-local allocation keeps millisecond cursor values unique. State DB list, search, read, filtered-list repair, rollout fallback propagation, and app-server conversions all carry the new field.

API

Thread responses include:

recencyAt?: number

thread/list and thread/search accept:

{ "sortKey": "recency_at" }

Generated TypeScript and JSON schemas are included.

Validation

  • just test -p codex-state — 146 passed
  • just test -p codex-rollout — 69 passed
  • just test -p codex-thread-store — 81 passed
  • just test -p codex-app-server-protocol — 231 passed
  • Focused app-server list ordering, response mapping, archive/unarchive, and resume lifecycle tests passed
  • Scoped just fix for state, rollout, thread-store, app-server-protocol, and app-server
  • just fmt
  • git diff --check
  • Independent correctness, simplicity, elegance, security, and test-quality reviews; actionable ordering, lifecycle, query-projection, and timestamp-uniqueness findings were addressed

@jif-oai jif-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-approving but the backfill issue needs to be fixed before merging

Comment thread codex-rs/state/src/model/thread_metadata.rs
Comment thread codex-rs/state/src/runtime/threads.rs
@nornagon-openai
nornagon-openai marked this pull request as ready for review June 16, 2026 14:43
@nornagon-openai
nornagon-openai requested a review from a team as a code owner June 16, 2026 14:43

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 336a28faaf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +183 to +187
ThreadSortKey::RecencyAt => item
.item
.recency_at
.as_deref()
.or(item.item.updated_at.as_deref())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include thread IDs in recency cursors

When search results are sorted by recency and the page boundary falls inside a group of threads sharing the same recency_at, this cursor only encodes the timestamp. The next keyset query cannot resume after the specific thread, so it advances past the rest of that timestamp bucket and silently drops matches from later pages.

Useful? React with 👍 / 👎.

let ThreadResumeResponse { thread, .. } = to_response::<ThreadResumeResponse>(resume_resp)?;

assert_eq!(thread.updated_at, before_resume.updated_at);
assert_eq!(thread.recency_at, before_resume.recency_at);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cover turn-start recency advancement

This asserts resume leaves recency_at unchanged but never that send_turn_start_request advances it. For this agent-visible recency change, add integration coverage for TurnStarted so that path cannot regress while this resume-only assertion passes. guidance

Useful? React with 👍 / 👎.

Ok(result.rows_affected() > 0)
}

pub async fn touch_thread_recency_at(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Split the recencyAt rollout into stages

This recencyAt rollout is non-mechanical but spans 66 files and 1,177 changed lines across migration, state runtime, thread-store, app-server protocol/schemas/tests, and TUI. Please split it into a smaller coherent stage so each behavior can be reviewed and tested. guidance

Useful? React with 👍 / 👎.

#[ts(type = "number")]
pub updated_at: i64,
/// Unix timestamp (in seconds) used for thread recency ordering.
#[ts(optional, type = "number")]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align recencyAt nullability across generated artifacts

When recency_at is None, serde emits "recencyAt": null, but #[ts(optional, type = "number")] makes clients expect omission/number instead of required nullable. Strict consumers can reject valid Thread payloads; make it required nullable or align artifacts per v2 guidance

Useful? React with 👍 / 👎.

.or_else(|| thread_id_from_rollout_path(item.path.as_path()))?;
let created_at = parse_rfc3339(item.created_at.as_deref()).unwrap_or_else(Utc::now);
let updated_at = parse_rfc3339(item.updated_at.as_deref()).unwrap_or(created_at);
let recency_at = parse_rfc3339(item.recency_at.as_deref()).unwrap_or(updated_at);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve SQLite recency for path reads

When a thread is read by rollout path, ThreadItem.recency_at comes from the rollout/file mtime fallback, and read_thread_by_rollout_path only overlays Git fields from SQLite. After a turn start followed by background output, path-based resume/read responses report recencyAt as the later updatedAt, so the new recency ordering signal is wrong for those callers.

Useful? React with 👍 / 👎.

@nornagon-openai
nornagon-openai merged commit fac3158 into main Jun 17, 2026
31 checks passed
@nornagon-openai
nornagon-openai deleted the nornagon/codex/thread-recency-at branch June 17, 2026 00:06
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants