🔴 Required Information
Describe the Bug:
RedisSessionService.append_event() sets session.last_update_time to time.time() instead of the appended event's own timestamp. The other three session backends store the event's timestamp:
| backend |
what it stores |
InMemorySessionService |
event.timestamp (in_memory_session_service.py:357-364) |
SqliteSessionService |
event_timestamp from event.timestamp (sqlite_session_service.py:433, :515-519) |
DatabaseSessionService |
event_timestamp, reloaded into last_update_time (database_session_service.py:976-1010) |
RedisSessionService |
time.time() (_redis_session_service.py:343) |
last_update_time is not cosmetic. It is the sort key list_sessions documents as "Sessions are ordered by last update time, oldest first" (base_session_service.py:102-118), and the Redis backend sorts on (s.last_update_time, s.user_id, s.id) (_redis_session_service.py:316-318).
The repository already records this as a known divergence. tests/unittests/sessions/_conformance.py registers the redis backend with the entry test_session_last_update_time_updates_on_event, and the harness docstring states that a divergence "becomes a defect anyone can pick up, and whoever fixes the backend has to delete the entry in the same change" (_conformance.py:15-31). That is the harness's own statement of the expected behaviour, marked unfixed.
Steps to Reproduce:
- Use
RedisSessionService as the session backend, then create a session.
- Append an
Event whose timestamp predates now — produced earlier, then replayed, re-delivered or imported. ADK re-delivers the same event object to a second session reference, which is why InMemorySessionService.append_event dedupes on event equality (in_memory_session_service.py:344-355).
- Read
session.last_update_time, or call list_sessions.
No Redis server is required; tests/unittests/integrations/redis/_fake_redis.py is an in-process stand-in.
Expected Behavior:
session.last_update_time equals event.timestamp, and the reloaded session agrees. The contract test asserts exactly this (tests/unittests/sessions/test_session_service.py:1573-1600): it builds an event at original_update_time + 10 and asserts both the in-memory and reloaded values equal it.
Observed Behavior:
The stored value is the wall clock at append time. The contract test on main at 9625b06:
XFAIL ...test_session_last_update_time_updates_on_event[redis] - Redis stamps the session with the wall clock instead of the appended event's timestamp.
5 passed, 1 xfailed
The five passing parameters are the other backends. A regression test that pins the clock far from the event's own timestamp fails by exactly the pinned offset:
E assert 1790353307.6994162 == 1790353207.6994162 ± 1.0e-06
Deterministic consequences:
- Two sessions that received the identical event report different activity times, breaking the re-delivery semantics the in-memory backend was built around.
- A session persisted via one backend and read via another (
src/google/adk/sessions/migration/) shows the Redis session jumping forward by however long the event sat in a queue, while SQLite does not.
list_sessions ordering is wrong for any event that predates its append.
Optional information
Environment Details:
- ADK Library Version:
main at 9625b06c9a1be6b9ecd85225657690ae5c0e9d3e
- Desktop OS: macOS
- Python Version: 3.14 locally; CI covers 3.10 through 3.14
- LiteLLM: N/A (no model call involved)
Regression: Not recent. The line has always read time.time(); the divergence entry documents it as long-standing.
Minimal reproduction:
from unittest import mock
from google.adk.events.event import Event
from google.adk.integrations.redis._config import RedisSessionServiceConfig
from google.adk.integrations.redis._redis_session_service import RedisSessionService
import pytest
@pytest.mark.asyncio
async def test_append_event_stamps_session_with_event_timestamp(session_service):
session = await session_service.create_session(app_name="app1", user_id="u1")
event_timestamp = session.last_update_time + 10
event = Event(author="agent", invocation_id="inv1", timestamp=event_timestamp)
with mock.patch("google.adk.integrations.redis._redis_session_service.time") as clock:
clock.time.return_value = event_timestamp + 100
await session_service.append_event(session, event)
assert session.last_update_time == pytest.approx(event_timestamp, abs=1e-6)
How often: Always (100%).
Scope note
This is the last_update_time defect only. The divergences map has a second Redis entry, test_append_event_to_unknown_session_raises_session_not_found, which is a separate defect: append_event writes the session key unconditionally (:384-390), so appending to a deleted, expired or never-stored session recreates it instead of raising SessionNotFoundError. That one overlaps open PR #7140, which owns those lines, and is out of scope here.
🔴 Required Information
Describe the Bug:
RedisSessionService.append_event()setssession.last_update_timetotime.time()instead of the appended event's owntimestamp. The other three session backends store the event's timestamp:InMemorySessionServiceevent.timestamp(in_memory_session_service.py:357-364)SqliteSessionServiceevent_timestampfromevent.timestamp(sqlite_session_service.py:433,:515-519)DatabaseSessionServiceevent_timestamp, reloaded intolast_update_time(database_session_service.py:976-1010)RedisSessionServicetime.time()(_redis_session_service.py:343)last_update_timeis not cosmetic. It is the sort keylist_sessionsdocuments as "Sessions are ordered by last update time, oldest first" (base_session_service.py:102-118), and the Redis backend sorts on(s.last_update_time, s.user_id, s.id)(_redis_session_service.py:316-318).The repository already records this as a known divergence.
tests/unittests/sessions/_conformance.pyregisters the redis backend with the entrytest_session_last_update_time_updates_on_event, and the harness docstring states that a divergence "becomes a defect anyone can pick up, and whoever fixes the backend has to delete the entry in the same change" (_conformance.py:15-31). That is the harness's own statement of the expected behaviour, marked unfixed.Steps to Reproduce:
RedisSessionServiceas the session backend, then create a session.Eventwhosetimestamppredates now — produced earlier, then replayed, re-delivered or imported. ADK re-delivers the same event object to a second session reference, which is whyInMemorySessionService.append_eventdedupes on event equality (in_memory_session_service.py:344-355).session.last_update_time, or calllist_sessions.No Redis server is required;
tests/unittests/integrations/redis/_fake_redis.pyis an in-process stand-in.Expected Behavior:
session.last_update_timeequalsevent.timestamp, and the reloaded session agrees. The contract test asserts exactly this (tests/unittests/sessions/test_session_service.py:1573-1600): it builds an event atoriginal_update_time + 10and asserts both the in-memory and reloaded values equal it.Observed Behavior:
The stored value is the wall clock at append time. The contract test on
mainat9625b06:The five passing parameters are the other backends. A regression test that pins the clock far from the event's own timestamp fails by exactly the pinned offset:
Deterministic consequences:
src/google/adk/sessions/migration/) shows the Redis session jumping forward by however long the event sat in a queue, while SQLite does not.list_sessionsordering is wrong for any event that predates its append.Optional information
Environment Details:
mainat9625b06c9a1be6b9ecd85225657690ae5c0e9d3eRegression: Not recent. The line has always read
time.time(); the divergence entry documents it as long-standing.Minimal reproduction:
How often: Always (100%).
Scope note
This is the
last_update_timedefect only. Thedivergencesmap has a second Redis entry,test_append_event_to_unknown_session_raises_session_not_found, which is a separate defect:append_eventwrites the session key unconditionally (:384-390), so appending to a deleted, expired or never-stored session recreates it instead of raisingSessionNotFoundError. That one overlaps open PR #7140, which owns those lines, and is out of scope here.