From dd655155a05a271c088e6dba70d7181d0d423405 Mon Sep 17 00:00:00 2001 From: Hiroaki Sano Date: Wed, 15 Jul 2026 18:25:47 +0900 Subject: [PATCH] fix(apps): validate EventsCompactionConfig.compaction_interval > 0 compaction_interval and overlap_size were plain required ints with no validation, unlike their sibling fields token_threshold (gt=0) and event_retention_size (ge=0). A compaction_interval of 0 (or negative) silently makes the sliding-window trigger fire on *every* invocation, because the guard in _run_compaction_for_sliding_window, if len(new_invocation_ids) < config.compaction_interval: return None can never hold. This is an easy foot-gun and is inconsistent with the validated sibling fields. Add Field(gt=0) to compaction_interval and Field(ge=0) to overlap_size (0 = no overlap, which is valid), plus tests. --- src/google/adk/apps/_configs.py | 16 ++++++++++++---- tests/unittests/apps/test_compaction.py | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/google/adk/apps/_configs.py b/src/google/adk/apps/_configs.py index 87f3666ebdd..e5813155af1 100644 --- a/src/google/adk/apps/_configs.py +++ b/src/google/adk/apps/_configs.py @@ -58,14 +58,22 @@ class EventsCompactionConfig(BaseModel): summarizer: Optional[BaseEventsSummarizer] = None """The event summarizer to use for compaction.""" - compaction_interval: int + compaction_interval: int = Field(gt=0) """The number of *new* user-initiated invocations that, once - fully represented in the session's events, will trigger a compaction.""" + fully represented in the session's events, will trigger a compaction. - overlap_size: int + Must be greater than 0. A value of 0 (or negative) would make the + sliding-window trigger fire on *every* invocation, because the guard + ``len(new_invocation_ids) < compaction_interval`` can never hold. + """ + + overlap_size: int = Field(ge=0) """The number of preceding invocations to include from the end of the last compacted range. This creates an overlap between consecutive - compacted summaries, maintaining context.""" + compacted summaries, maintaining context. + + Must be greater than or equal to 0 (0 means no overlap). + """ token_threshold: Optional[int] = Field( default=None, diff --git a/tests/unittests/apps/test_compaction.py b/tests/unittests/apps/test_compaction.py index 2f53e1e937a..cf2dee231ab 100644 --- a/tests/unittests/apps/test_compaction.py +++ b/tests/unittests/apps/test_compaction.py @@ -487,6 +487,24 @@ def test_events_compaction_config_rejects_missing_modes(self): with pytest.raises(ValidationError): EventsCompactionConfig() + def test_events_compaction_config_rejects_non_positive_interval(self): + # compaction_interval must be > 0. A value of 0 (or negative) would make + # the sliding-window trigger fire on every invocation because the guard + # `len(new_invocation_ids) < compaction_interval` can never hold. + with pytest.raises(ValidationError): + EventsCompactionConfig(compaction_interval=0, overlap_size=0) + + with pytest.raises(ValidationError): + EventsCompactionConfig(compaction_interval=-1, overlap_size=0) + + def test_events_compaction_config_rejects_negative_overlap(self): + with pytest.raises(ValidationError): + EventsCompactionConfig(compaction_interval=2, overlap_size=-1) + + def test_events_compaction_config_allows_zero_overlap(self): + config = EventsCompactionConfig(compaction_interval=2, overlap_size=0) + self.assertEqual(config.overlap_size, 0) + def test_latest_prompt_token_count_fallback_applies_compaction(self): events = [ self._create_event(1.0, 'inv1', 'a' * 40),