Skip to content

fix(api): order session records by producer time so turns stop interleaving#5494

Open
mmabrouk wants to merge 1 commit into
feat/sessions-last-message-onlyfrom
fix/sessions-record-ordering
Open

fix(api): order session records by producer time so turns stop interleaving#5494
mmabrouk wants to merge 1 commit into
feat/sessions-last-message-onlyfrom
fix/sessions-record-ordering

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

The problem

A conversation rebuilt from the record log came back with its turns interleaved. A three-turn run reconstructed as user, user, assistant, user: the reply to the first question landed after the second question.

get_records ordered by (created_at, record_index). Two facts make that wrong:

  • record_index restarts at 0 on every turn, so it only orders records within a turn.
  • The ingest worker batches records from adjacent turns into a single write, so those rows share a created_at.

When both happen together the tiebreak sorts the next turn's first record ahead of the previous turn's later ones. Straight from the table, with turn 2's user record jumping ahead of turn 1's agent record:

record_source | record_index | turn_id  | timestamp     | created_at
user          |            0 | turn-2   | 07.644        | 07.880223
agent         |            3 | turn-1   | 07.544        | 07.880223

Same created_at, so record_index 0 wins over 3, and turn 2's question sorts before turn 1's answer.

This ordering predates the last-message-only work. It did not matter while records fed a viewer. It matters now that the model's context is rebuilt from them.

The fix

Order by the producer-stamped timestamp first. The runner already sets it on every record, and unlike the per-turn index it is monotonic across turns. Ingest time and the per-turn index stay as tiebreaks, and rows written before timestamp existed sort last within their batch rather than jumping the queue.

Testing

Verified against the live table for a three-turn conversation: ordering by producer time reproduces the true conversation order, where the previous ordering did not. Found during the differential QA on #5486.

Known follow-up

This fixes the ordering. It also surfaced a separate issue that is not fixed here: the worker writes a turn's records to Postgres after the next turn has already read them, so the immediately preceding turn's reply can still be missing at reconstruction time. That is a read-after-write question about the ingest pipeline, and it needs its own decision.

@dosubot dosubot Bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Jul 24, 2026
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 24, 2026 10:11pm

Request Review

@dosubot dosubot Bot added the Bug Report Something isn't working label Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c36b1032-4168-4d89-b5b5-fb8ab107fac0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sessions-record-ordering

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…eaving

`get_records` ordered by (created_at, record_index). `record_index` restarts at 0
on every turn, and the ingest worker batches records from adjacent turns into one
write, so those rows share a `created_at`. The tiebreak then sorted the NEXT
turn's first record ahead of the PREVIOUS turn's later ones, and a conversation
rebuilt from the log came back interleaved: a live three-turn run reconstructed as
user, user, assistant, user with the replies in the wrong places.

Order by the producer-stamped `timestamp` first. It is the only key monotonic
across turns, and the runner already sets it on every record. Ingest time and the
per-turn index stay as tiebreaks, and rows predating `timestamp` sort last within
their batch rather than jumping the queue.

Claude-Session: https://claude.ai/code/session_01KM69J7uHafgciiN5zfG7qR
@mmabrouk
mmabrouk force-pushed the fix/sessions-fail-on-lost-records branch from 8f19a97 to f621400 Compare July 24, 2026 22:10
@mmabrouk
mmabrouk force-pushed the fix/sessions-record-ordering branch from 293785a to d4501e0 Compare July 24, 2026 22:10
@mmabrouk
mmabrouk changed the base branch from fix/sessions-fail-on-lost-records to feat/sessions-last-message-only July 24, 2026 22:10
@mmabrouk
mmabrouk requested a review from ardaerzin July 24, 2026 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Report Something isn't working size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant