From 41069bcd0fcc18ea5be422dfdfa1426176923745 Mon Sep 17 00:00:00 2001 From: baba9811 Date: Wed, 16 Sep 2026 00:11:47 +0900 Subject: [PATCH] fix(sessions): restore oldest-first Redis session lists Follow the base session service ordering contract and use user and session IDs to break timestamp ties. Remove the recorded ordering divergence and cover both user-scoped and app-wide lists. --- .../redis/_redis_session_service.py | 4 +-- .../redis/test_redis_session_service.py | 34 +++++++++++++++++++ tests/unittests/sessions/_conformance.py | 4 --- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/google/adk/integrations/redis/_redis_session_service.py b/src/google/adk/integrations/redis/_redis_session_service.py index 2717b767203..cc7c41c7cd8 100644 --- a/src/google/adk/integrations/redis/_redis_session_service.py +++ b/src/google/adk/integrations/redis/_redis_session_service.py @@ -313,8 +313,8 @@ async def list_sessions( except Exception as e: logger.warning("Failed to parse session at key %s: %s", key, e) - # Sort descending by last_update_time - sessions.sort(key=lambda s: s.last_update_time, reverse=True) + # Sort oldest first, with stable ordering when update times are equal. + sessions.sort(key=lambda s: (s.last_update_time, s.user_id, s.id)) return ListSessionsResponse(sessions=sessions) async def delete_session( diff --git a/tests/unittests/integrations/redis/test_redis_session_service.py b/tests/unittests/integrations/redis/test_redis_session_service.py index 8197dcd91b7..eb70670430c 100644 --- a/tests/unittests/integrations/redis/test_redis_session_service.py +++ b/tests/unittests/integrations/redis/test_redis_session_service.py @@ -17,6 +17,7 @@ from __future__ import annotations import json +from unittest import mock from google.adk.errors.already_exists_error import AlreadyExistsError from google.adk.events.event import Event @@ -203,6 +204,39 @@ async def test_list_sessions(session_service): assert session_ids_all == {"s1", "s2", "s3"} +@pytest.mark.asyncio +@pytest.mark.parametrize( + "user_id, expected", + [ + ("u1", [("u1", "s1"), ("u1", "s2")]), + (None, [("u2", "s0"), ("u1", "s1"), ("u1", "s2"), ("u2", "s1")]), + ], +) +async def test_list_sessions_ordered_by_activity_with_stable_ties( + session_service, user_id, expected +): + """Sessions are oldest first, with ties ordered by user and session id.""" + with mock.patch( + "google.adk.integrations.redis._redis_session_service.time" + ) as clock: + for owner, session_id, timestamp in ( + ("u2", "s1", 20.0), + ("u1", "s2", 20.0), + ("u1", "s1", 20.0), + ("u2", "s0", 10.0), + ): + clock.time.return_value = timestamp + await session_service.create_session( + app_name="app1", user_id=owner, session_id=session_id + ) + + response = await session_service.list_sessions( + app_name="app1", user_id=user_id + ) + + assert [(s.user_id, s.id) for s in response.sessions] == expected + + @pytest.mark.asyncio async def test_list_sessions_glob_metacharacters_match_literally( session_service, fake_redis diff --git a/tests/unittests/sessions/_conformance.py b/tests/unittests/sessions/_conformance.py index 858971da62c..3a62bec21da 100644 --- a/tests/unittests/sessions/_conformance.py +++ b/tests/unittests/sessions/_conformance.py @@ -144,10 +144,6 @@ async def _make_per_agent_database( 'redis', _make_redis, divergences={ - 'test_list_sessions_ordered_by_last_update_time': ( - 'Redis sorts sessions newest first, while the base class' - ' documents oldest first.' - ), 'test_session_last_update_time_updates_on_event': ( 'Redis stamps the session with the wall clock instead of the' " appended event's timestamp."