Skip to content

Commit 07f597b

Browse files
authored
Merge branch 'main' into fix/vertex-anyof-schema-sanitization
2 parents 09c5709 + 0d4d378 commit 07f597b

4 files changed

Lines changed: 350 additions & 21 deletions

File tree

src/google/adk/apps/compaction.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from google.genai import types
2121

2222
from ..agents.base_agent import BaseAgent
23+
from ..events._rewind_events import _apply_rewinds
2324
from ..events.event import Event
2425
from ..sessions.base_session_service import BaseSessionService
2526
from ..sessions.session import Session
@@ -386,16 +387,22 @@ async def _run_compaction_for_token_threshold_config(
386387
if config.token_threshold is None or config.event_retention_size is None:
387388
return False
388389

390+
# Drop rewound invocations so the summary covers only live events, consistent
391+
# with prompt building and sliding-window compaction (all route through
392+
# _apply_rewinds); otherwise rewound content would leak back into future
393+
# prompts via the compaction summary.
394+
events = _apply_rewinds(session.events)
395+
389396
prompt_token_count = _latest_prompt_token_count(
390-
session.events,
397+
events,
391398
current_branch=current_branch,
392399
agent_name=agent_name,
393400
)
394401
if prompt_token_count is None or prompt_token_count < config.token_threshold:
395402
return False
396403

397404
events_to_compact = _events_to_compact_for_token_threshold(
398-
events=session.events,
405+
events=events,
399406
event_retention_size=config.event_retention_size,
400407
)
401408
if not events_to_compact:
@@ -530,7 +537,11 @@ async def _run_compaction_for_sliding_window(
530537
runner loop) is responsible for appending it to the session, so that
531538
persistence of this event stays at the runtime's synchronization point.
532539
"""
533-
events = session.events
540+
# Drop rewound invocations first so the summary covers only live events. This
541+
# keeps the compactor consistent with prompt building (the contents processor
542+
# also applies rewinds); otherwise rewound content would leak back into future
543+
# prompts via the compaction summary.
544+
events = _apply_rewinds(session.events)
534545
if not events:
535546
return
536547

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Helpers for resolving which events are live after rewinds."""
16+
17+
from __future__ import annotations
18+
19+
from .event import Event
20+
21+
22+
def _apply_rewinds(events: list[Event]) -> list[Event]:
23+
"""Returns ``events`` with rewound invocations removed.
24+
25+
Iterates backward. When an event carries
26+
``actions.rewind_before_invocation_id == X``, drops that event together with
27+
every event between it and the earliest event of invocation ``X`` (inclusive),
28+
then resumes the backward walk from there.
29+
30+
This is the single source of truth for "which events are live" after rewinds.
31+
Both LLM prompt building (``google.adk.flows.llm_flows.contents``) and context
32+
compaction (``google.adk.apps.compaction``) must agree on it, otherwise
33+
rewound content can leak back into prompts through a compaction summary.
34+
35+
Args:
36+
events: The full event history, in chronological order.
37+
38+
Returns:
39+
The chronological subset of ``events`` that survives all rewinds.
40+
"""
41+
kept: list[Event] = []
42+
i = len(events) - 1
43+
while i >= 0:
44+
event = events[i]
45+
if event.actions and event.actions.rewind_before_invocation_id:
46+
rewind_invocation_id = event.actions.rewind_before_invocation_id
47+
for j in range(0, i, 1):
48+
if events[j].invocation_id == rewind_invocation_id:
49+
i = j
50+
break
51+
else:
52+
kept.append(event)
53+
i -= 1
54+
kept.reverse()
55+
return kept

src/google/adk/flows/llm_flows/contents.py

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from ...agents.invocation_context import InvocationContext
2626
from ...events._branch_path import _BranchPath
27+
from ...events._rewind_events import _apply_rewinds
2728
from ...events.event import Event
2829
from ...models.llm_request import LlmRequest
2930
from ._base_llm_processor import BaseLlmRequestProcessor
@@ -709,24 +710,10 @@ def _get_contents(
709710
accumulated_input_transcription = ''
710711
accumulated_output_transcription = ''
711712

712-
# Filter out events that are annulled by a rewind.
713-
# By iterating backward, when a rewind event is found, we skip all events
714-
# from that point back to the `rewind_before_invocation_id`, thus removing
715-
# them from the history used for the LLM request.
716-
rewind_filtered_events = []
717-
i = len(events) - 1
718-
while i >= 0:
719-
event = events[i]
720-
if event.actions and event.actions.rewind_before_invocation_id:
721-
rewind_invocation_id = event.actions.rewind_before_invocation_id
722-
for j in range(0, i, 1):
723-
if events[j].invocation_id == rewind_invocation_id:
724-
i = j
725-
break
726-
else:
727-
rewind_filtered_events.append(event)
728-
i -= 1
729-
rewind_filtered_events.reverse()
713+
# Filter out events that are annulled by a rewind, so the rewound history is
714+
# never sent to the LLM. This is the same rewind logic the context compactor
715+
# applies, keeping the two consistent (see google.adk.events._rewind_events).
716+
rewind_filtered_events = _apply_rewinds(events)
730717

731718
# Parse the events, leaving the contents and the function calls and
732719
# responses from the current agent.

0 commit comments

Comments
 (0)