-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Add get_events() and filter_events() methods to Session #4959
Description
Feature Request: Add get_events() and filter_events() methods to Session
Problem Statement
Currently, when working with sessions that have been rewound, there is no built-in way to filter out events that have been invalidated by rewind operations. This forces every consumer to implement their own filtering logic, leading to code duplication, inconsistency, and potential bugs.
Current Situation
After the rewind feature was introduced in 9dce06f, session.events returns all events, including those that have been annulled by a rewind. When a rewind occurs, an event with actions.rewind_before_invocation_id is appended, but all previous events from that invocation onwards remain in the events list.
This means that when iterating over session events, we see "ghost" events that are no longer part of the active conversation flow.
Example of the Problem
# Session with events:
# - inv-1: user asks "What is Python?"
# - inv-1: agent responds "Python is a programming language"
# - inv-2: user asks "What is Java?"
# - inv-2: agent responds "Java is a programming language"
# - inv-rewind: rewind_before_invocation_id = "inv-2"
# - inv-3: user asks "What is JavaScript?"
# Current behavior - we see ALL events including the rewound inv-2:
for event in session.events:
print(event.content)
# Output shows 6 events, including the "ghost" inv-2 events
# What we want - only see active events:
for event in session.filter_events():
print(event.content)
# Output should show only 3 events: inv-1 and inv-3Why This Is Needed
1. Code Duplication
Every consumer needs to implement the same filtering logic. In our codebase, we had to implement:
@staticmethod
def _filter_rewound_events(events: list) -> list:
"""Filter out events that have been annulled by a rewind."""
filtered = []
i = len(events) - 1
while i >= 0:
event = events[i]
if event.actions and event.actions.rewind_before_invocation_id:
rewind_invocation_id = event.actions.rewind_before_invocation_id
for j in range(0, i):
if events[j].invocation_id == rewind_invocation_id:
i = j
break
else:
filtered.append(event)
i -= 1
filtered.reverse()
return filteredThis is 15+ lines of non-trivial logic that every ADK consumer must write.
2. Error-Prone
The rewind filtering algorithm is complex and easy to get wrong:
- Must iterate backward through events
- Must handle multiple sequential rewinds
- Must maintain chronological order after filtering
- Edge cases (rewind to first invocation, missing target, etc.)
3. Inconsistent Behavior
Different consumers may implement filtering differently, leading to:
- Different interpretations of what "rewound" means
- Subtle bugs in edge cases
- Harder to debug issues across different codebases
4. Breaking the Abstraction
Consumers shouldn't need to understand the internal structure of rewind events (actions.rewind_before_invocation_id) to work with sessions. This is an implementation detail that should be hidden behind a clean API.
Real-World Use Case
In our agent system, we need to:
- Display conversation history to users - we only want to show active messages, not rewound ones
- Process events for analytics - we need to count actual user interactions, excluding rewound attempts
- Build context for the LLM - when constructing prompts, we only want to include active conversation history
Currently, we must manually filter events in all these places:
# In our chat controller
for event in self._filter_rewound_events(session.events):
if event.author == "user":
conversation_history.append(self._extract_text(event))
# In our analytics service
active_events = self._filter_rewound_events(session.events)
user_messages = [e for e in active_events if e.author == "user"]
# In our context builder
for event in self._filter_rewound_events(session.events):
context += self._format_event(event)Proposed Solution
Add two methods to the Session class:
get_events() -> list[Event]
Returns all events in the session (same as session.events but as a method for API consistency).
filter_events(*, exclude_rewound: bool = True) -> list[Event]
Returns filtered events, with the primary use case being exclusion of rewound events.
# Get only active events (default behavior)
for event in session.filter_events():
process(event)
# Get all events including rewound ones
for event in session.filter_events(exclude_rewound=False):
process_all(event)Benefits
- Cleaner code - One-line filtering instead of 15+ lines
- Consistent behavior - All consumers use the same filtering logic
- Fewer bugs - Centralized, well-tested implementation
- Better abstraction - Hides implementation details of rewind
- Backward compatible -
session.eventsstill works as before
Additional Context
- The rewind feature is relatively new and not all consumers may be aware of the need to filter
GetSessionConfigprovidesnum_recent_eventsandafter_timestampfiltering, but no way to filter rewound events- This pattern (filtering by state) is common in other session-based frameworks
Related Pull Request
I've already implemented this feature and submitted a PR: #4960