Order list_agent_sessions by activity time instead of primary-key order - #2271
Open
kriszyp wants to merge 4 commits into
Open
Order list_agent_sessions by activity time instead of primary-key order#2271kriszyp wants to merge 4 commits into
kriszyp wants to merge 4 commits into
Conversation
listSessions scanned the primary store with `reverse: true`, which orders by primary key. The key is session_id, a randomUUID, so the result was ordered by random id rather than by time -- the opposite of the "most recent first" the tool description promises. Observed on 5.2.4: of ten sessions the newest came back ninth and the oldest seventh, in strictly descending id order. The ordering alone is cosmetic, but `limit` is applied by the range scan before any notion of recency, so past the default of 100 the listing silently omitted an arbitrary subset -- possibly including the session just created -- and got monotonically less complete as sessions accumulated. Both createdAt and updatedAt were already indexed on the table, so this scans the updatedAt index descending instead, keeping the work bounded by `limit`. updatedAt rather than createdAt: for a resumable session list the useful "most recent" is last activity, so a long-running session that just did something sorts first. The tool description now says which timestamp it means. The mock in the unit suite hid this: its getRange reversed a Map's insertion order, which coincidentally matches most-recent-first, so the buggy code passed its own test. The mock now orders by key like the real store, and the seeded ids descend while activity time ascends, so key order and time order cannot agree by luck. Fixes #2268
Drop the `updatedAt > 0` sentinel condition and sort alone. Table.search pushes an order-aligned pseudo-condition when the sort attribute is indexed, and throws 404 when it is not (resources/Table.ts:3429-3441). The sentinel made conditions non-empty, so that guard could never fire: if `indexed` were ever dropped from the attribute, listSessions would have silently degraded to decoding every session record -- full transcripts and pending approvals -- and sorting in memory before applying the limit, with no error and this suite still green. Sort-only gets the same index behavior today and keeps the failure loud. It also stops excluding rows whose updatedAt is absent or 0, which get_agent_session still returns. Trim the added test comments to the one non-obvious constraint (consecutive createSession calls land in the same millisecond).
Round-2 review raised that the mock's search sorts in memory whatever it is asked to, so the suite would stay green if `updatedAt` lost `indexed: true` on the table while Table.search started throwing 404 in production. The attribute list is now a module constant and a test asserts the declaration the query depends on -- the same shape as the config-param registration guard in unitTests/config/replicationReceiveQueueParam.test.js. The mock's comparator treated a missing updatedAt as NaN, which leaves Array.prototype.sort order unspecified; it now coerces absent values to 0.
Contributor
There was a problem hiding this comment.
Code Review
This pull request updates the agent session listing functionality to sort sessions by their last updated timestamp (descending) using Table.search instead of a reverse primary key scan. It also extracts the session attributes, updates the MCP tool description, and adds comprehensive unit tests for the new sorting behavior. The review feedback suggests aligning with the repository's style guide by using strict assertion methods (assert.strictEqual and assert.deepStrictEqual) in tests and utilizing loose equality checks (row != null) for null-or-undefined validation.
This comment has been minimized.
This comment has been minimized.
Both from the styleguide (.gemini/styleguide.md): strict assertion variants where strict semantics are intended, and `!= null` rather than a truthiness check for null-or-undefined. Scoped to the lines this branch adds; the rest of the file keeps its existing idiom.
kriszyp
marked this pull request as ready for review
August 22, 2026 01:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
list_agent_sessionsis documented as returning sessions "most recent first". It scanned the primary store withreverse: true, and the primary key is arandomUUID, so the order was uncorrelated with time andlimittruncated an arbitrary subset rather than dropping the least recent. It now scans the already-indexedupdatedAtattribute descending, bounded bylimit.Observed on 5.2.4 before the fix: of ten sessions the newest came back ninth and the oldest seventh, in strictly descending id order.
For the human reviewer
updatedAtorcreatedAtas what "most recent" means. I choseupdatedAt— last activity — so a long-running session that just did something sorts first, which is what an operator scanning for recent agent activity wants and how any conversation list behaves.createdAtwould give stable creation order and stable pagination instead. Both attributes were already indexed, so there is no cost difference; this is purely list semantics, and the tool description now states which one it means. The third test pins the choice, so changing your mind means changing that test too.hdb_agent_sessiontable. The unit mock proveslistSessionsasks for descendingupdatedAt, and a second test asserts the table declaration marks that attribute indexed — so removingindexed: truenow fails the suite rather than passing while production 404s. What still isn't executed anywhere is the real planner: thatTable.searchindex-aligns this exact shape I confirmed by readingresources/Table.ts:3429-3441. The mock also mutatesmock.storein place to set timestamps, bypassing index maintenance. One case against the real table (underunitTests/resources/, which does exercise real tables) is additive later — until then that half rests on code reading, and you should decide whether that is good enough to merge on.limitstill has no cursor or total count. A caller cannot tell "100 sessions" from "the newest 100 of 5000". This PR makes the truncation predictable instead of arbitrary, which is the bug, but does not make it visible. Adding paging later is a breaking change to the tool contract, so it is cheaper to decide now if it is wanted.Date.now()written by whichever node handled the mutation. If this system table replicates, a node whose clock runs fast puts its sessions permanently at the top of every operator's list. Ordering-only, no integrity impact, and strictly better than the arbitrary order it replaces — flagged because the tool description now makes a promise a skewed cluster cannot keep. I did not confirm whetherhdb_agent_sessionparticipates in replication.Round-1 review caught a real defect in the first version of this fix, now resolved: I had added a
updatedAt > 0sentinel condition to force the index, which madeconditionsnon-empty and so disabled theresources/Table.ts:3435guard. Losingindexedon the attribute would then have silently degraded the call to decoding every session record — full transcripts and pending approvals — and sorting in memory, with no error and the suite still green. Sort-only gets the same index behavior and keeps the failure loud.Verification
npx mocha --conditions=typestrip unitTests/agent/session.test.js→ 11 passing.agent/session.tsfromorigin/main, deleteddist/agent/session.jsand the tsbuildinfo, rebuilt, re-ran → the three new tests fail, the eight pre-existing ones pass as a control. Restored and rebuilt → 11 passing.The existing test could not have caught this: the mock's
getRangereversed aMap's insertion order, which coincidentally matches most-recent-first, so the buggy code passed. The mock now orders by key like the real store, and the seeded ids descend while activity time ascends so key order and time order cannot agree by luck.Fixes #2268
One nit is knowingly carried: the exported constant's doc comment restates the dependency already stated at the query site. Trimming it would leave the review receipt no longer matching HEAD, which is a worse trade than the duplicated sentence.
Complexity: medium
Review-Coverage: authored=claude; ran=codex,gemini; declined=cursor-grok,cursor-composer,domain; rounds=4 @ d7b67bf
Human-Review-Need: 3 @ d7b67bf