RedisSessionService.append_event() currently serializes the caller's event list as the entire stored session. If the caller loaded only recent events with GetSessionConfig, the first append permanently deletes the excluded history. Runner.run_async() with RunConfig.get_session_config reaches the same path.
Reload the stored event history inside a Redis WATCH/MULTI/EXEC transaction, append the processed event once, and retain the caller's filtered view. A retry reloads the canonical history so another append is preserved. The stored JSON format, public API, state handling and configured TTL remain unchanged. Redis's existing behavior for missing or expired sessions is retained.
Reproduction
Environment details: Current main 3a06a8621d04fd87aad9f6b078c999c78a96fb66, ADK 2.9.0 package metadata, macOS arm64, Python 3.12.10, Redis 8.4.0, redis-py 8.1.0. No LiteLLM or model call.
Start a disposable Redis instance, for example redis-server --port 0 --unixsocket /tmp/adk-history-example.sock --save "" --appendonly no. Install ADK and redis, save the following as repro.py, then run REDIS_SOCKET=/tmp/adk-history-example.sock python repro.py. The custom agent returns a local reply without a model or credentials. It exercises both direct appends and Runner.run_async().
Final regression tests reject the original source: 8 fail, with the unfiltered control passing. Fixed Redis and session suites: 522 passed, 3 existing expected failures. Coverage includes recent-event, zero-event and timestamp filters, two appends, full Event equality, state scopes, partial events, intervening append/delete/expiry and TTL refresh.
Adjacent Runner, local-storage forwarding, compaction, rewind, live-flow and A2A tests: 1017 passed, 50 skipped, 3 expected failures. These runs emitted upstream/dependency warnings; skipped paths are not claimed as verified.
Built baseline and fixed wheels and installed them in a clean environment. A real Redis and deterministic custom-agent Runner check covers 48 cases across Redis, memory, SQLite and SQLAlchemy SQLite. The baseline loses history in 8 Redis cases; the fixed wheel passes all 48. Actual Redis WATCH conflicts, delete/expiry retries and 20 concurrent filtered appends pass. The same 52 checks pass with redis-py 4.2.0 on Python 3.10 and 8.1.0 on Python 3.12. The SQLAlchemy SQLite control requires its asyncio extra on this host.
Full pre-commit passes on both baseline and fixed trees; changed-file and commit hooks pass.
Full-extra mypy 2.3.1 comparisons on Python 3.10–3.13 find no new diagnostics. Baseline and fixed each report the same 842 errors after line normalization, so raw mypy exits remain 1. These macOS runs use the CI lancedb omission; they are not the GitHub-hosted Linux jobs.
Full unit suite completed through tox on every supported Python version, using the upstream pytest tests/unittests command. All five tox environments exit 1 with the failures detailed below; this is not an all-green matrix.
Python
Passed
Failed
Skipped
Xfailed
Xpassed
Warnings
3.10.19
14926
1
87
27
2
2072
3.11.14
14935
1
86
27
2
2004
3.12.14
14924
3
87
27
2
2048
3.13.15
14924
3
87
27
2
2044
3.14.7
14926
1
87
27
2
2140
Full-suite failures and environment:
All five versions fail tests/unittests/evaluation/test_local_eval_service.py::test_eval_injects_session_input_state_into_instruction with empty inferences. Running the unchanged baseline's entire test_local_eval_service.py module with each corresponding tox environment reproduces the same failure: 1 failed, 26 passed. This test uses the default in-memory session service.
Python 3.12 and 3.13 additionally fail both test_entry_point_loads_only_allowlisted_packages cases because Homebrew Python loads sitecustomize. Both cases fail identically on the unchanged baseline. A fresh interpreter already has Homebrew's sitecustomize loaded before any ADK import.
Dependencies were locked locally in a separate verification checkout with UV_EXCLUDE_NEWER=2026-09-16T09:00:00Z; no lockfile or generated artifact is submitted. The final runs use a tool-only PATH without gcloud because an existing CLI login test otherwise invokes an interactive ADC login on this host. The evaluation and import tests were run unchanged, with no additional test deselection. Earlier interrupted runs are not counted as completed checks.
Alternate A2A/MCP dependency matrices and GitHub-hosted CI have not been run locally.
Documentation
The companion documentation PR clarifies that retrieval filters limit the loaded view without deleting persisted events. Its strict build and served-page inspection pass. The two changes should be coordinated so the documentation reflects the Redis correction.
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
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.
Description of Change
RedisSessionService.append_event() currently serializes the caller's event list as the entire stored session. If the caller loaded only recent events with GetSessionConfig, the first append permanently deletes the excluded history. Runner.run_async() with RunConfig.get_session_config reaches the same path.
Reload the stored event history inside a Redis WATCH/MULTI/EXEC transaction, append the processed event once, and retain the caller's filtered view. A retry reloads the canonical history so another append is preserved. The stored JSON format, public API, state handling and configured TTL remain unchanged. Redis's existing behavior for missing or expired sessions is retained.
Reproduction
Environment details: Current main 3a06a8621d04fd87aad9f6b078c999c78a96fb66, ADK 2.9.0 package metadata, macOS arm64, Python 3.12.10, Redis 8.4.0, redis-py 8.1.0. No LiteLLM or model call.
Start a disposable Redis instance, for example redis-server --port 0 --unixsocket /tmp/adk-history-example.sock --save "" --appendonly no. Install ADK and redis, save the following as repro.py, then run REDIS_SOCKET=/tmp/adk-history-example.sock python repro.py. The custom agent returns a local reply without a model or credentials. It exercises both direct appends and Runner.run_async().
Before the fix, both workflows lose the two excluded events (exit 1):
Expected behavior, verified with the fixed wheel (exit 0):
Testing Plan
Full-suite failures and environment:
Documentation
The companion documentation PR clarifies that retrieval filters limit the loaded view without deleting persisted events. Its strict build and served-page inspection pass. The two changes should be coordinated so the documentation reflects the Redis correction.