Skip to content

Conversation

@Ponts
Copy link

@Ponts Ponts commented Jan 22, 2026

Link to Issue or Description of Change

1. Link to an existing issue (if applicable):

Problem:
on_event_callback executes after append_event, preventing plugin modifications from being persisted #3990

Solution:
Run callbacks before persisting events.

Testing Plan

Ran python tests/unittests and verified all tests passed.
There exists tests that verify that events are yielded from the runner.

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

Manual End-to-End (E2E) Tests:

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Ponts, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses an issue where modifications made to events by plugins via on_event_callback were not being persisted. The changes reorder the event processing flow to ensure that plugin-modified events are correctly stored in the session, thereby resolving data inconsistency issues.

Highlights

  • Event Persistence Logic: The logic for running on_event_callback and applying custom metadata has been reordered to occur before the event is appended to the session. This ensures that any modifications made by plugins to an event are persisted correctly.
  • Event Yielding Simplification: The yield statement now consistently yields the event variable after it has potentially been modified by on_event_callback, simplifying the control flow.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@adk-bot adk-bot added the core [Component] This issue is related to the core interface and implementation label Jan 22, 2026
@adk-bot
Copy link
Collaborator

adk-bot commented Jan 22, 2026

Response from ADK Triaging Agent

Hello @Ponts, thank you for creating this PR!

Could you please update the issue number in the PR description? It currently says Closes: #_issue_number_.

In addition, could you please provide more details in the "Testing Plan" section, such as the commands you ran and the output you observed? This information will help reviewers to review your PR more efficiently. Thanks!

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly addresses the issue of persisting events after on_event_callback modifications by reordering the event processing logic. The on_event_callback is now executed, and any modifications are applied to the event, before the event is appended to the session service. The logging for modified events is a good addition for debugging. The simplification of the yield statement is also a positive change.

Comment on lines 836 to 838
_apply_run_config_custom_metadata(
event, invocation_context.run_config
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _apply_run_config_custom_metadata function is called here, after the on_event_callback has potentially modified the event. However, this function is also called earlier in the _exec_with_plugin method (around line 781, outside this diff). Applying custom metadata twice, even if it's a merge operation, is redundant and could lead to confusion or subtle bugs if the run_config.custom_metadata were to change dynamically or if plugins were expected to override certain metadata. Consider ensuring _apply_run_config_custom_metadata is called only once, at the final stage before persistence and yielding, to ensure consistency and avoid unnecessary processing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done again on the modified events to ensure the custom metadata from the run config is applied.
I am unsure if the live-call code makes use of the metadata for something, and that is why it is applied at the beginning.
I wanted to make a minimal change to ensure modified events are persisted

@GWeale
Copy link
Collaborator

GWeale commented Jan 22, 2026

Thanks for this fix! The approach is correct for non-live calls, and I confirmed locally that it resolves the persistence issue.

However, I noticed that the fix moves on_event_callback inside the else block, which means it no longer runs for live/bidi calls (is_live_call=True). In the original code, the callback was outside both branches and ran for all events.

Could you update the PR to ensure on_event_callback also runs (before append_event) for live calls? The live branch has multiple append_event calls that would need similar treatment.

@ryanaiagent ryanaiagent self-assigned this Jan 23, 2026
@ryanaiagent ryanaiagent added the request clarification [Status] The maintainer need clarification or more information from the author label Jan 23, 2026
@Ponts Ponts force-pushed the persist-modified-events branch from 5428ca1 to 17d8718 Compare January 23, 2026 08:26
@Ponts
Copy link
Author

Ponts commented Jan 23, 2026

I have updated the code to run the callbacks in the live/bidi calls block too and fixed the mistake with that it only ran on not-live calls.
I added running callbacks to the buffered events, which was not being done previously.

@ryanaiagent
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request effectively resolves the issue where plugin on_event_callback modifications were not being persisted by correctly reordering the event processing. The introduction of the __append_event helper method is a good step towards improving code organization and reducing duplication. However, a critical regression has been introduced by the removal of the after_run_callback execution, which is essential for global cleanup and finalization tasks. Additionally, there is a minor type hinting inaccuracy in the new __append_event method.

I am having trouble creating individual review comments. Click here to see my feedback.

src/google/adk/runners.py (846-851)

critical

The plugin_manager.run_after_run_callback was responsible for executing global cleanup tasks or finalizing logs and metrics data after the main event processing loop in _exec_with_plugin. Its removal means these crucial post-run operations will no longer be performed, which could lead to resource leaks, incomplete logging, or other unexpected behavior. Please ensure this callback is reinstated to maintain proper lifecycle management.

src/google/adk/runners.py (828)

high

The return type hint for __append_event is None, but the method explicitly returns an Event object. Please update the type hint to reflect the actual return type.

  async def __append_event(self, invocation_context: InvocationContext, session: Session, event: Event) -> Event:

@ryanaiagent
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request effectively addresses the issue where plugin modifications were not being persisted by ensuring that on_event_callback is executed and its results are applied before an event is appended to the session. This is achieved by encapsulating the event processing and appending logic into a new private helper method, __append_event. This change improves the logical consistency of event handling within the runner, ensuring that plugin-driven transformations are correctly integrated into the session's event history.

# This does NOT emit any event.
await plugin_manager.run_after_run_callback(
invocation_context=invocation_context
async def __append_event(self, invocation_context: InvocationContext, session: Session, event: Event) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The return type annotation for __append_event is None, but the method explicitly returns an Event object on line 846. Please update the type hint to -> Event for accuracy and better type checking.

Suggested change
async def __append_event(self, invocation_context: InvocationContext, session: Session, event: Event) -> None:
async def __append_event(self, invocation_context: InvocationContext, session: Session, event: Event) -> Event:

@ryanaiagent
Copy link
Collaborator

Hi @Ponts , can you also fix the formatting errors.

@Ponts Ponts force-pushed the persist-modified-events branch from 40c8b0f to 82068d5 Compare January 27, 2026 08:15
@Ponts
Copy link
Author

Ponts commented Jan 27, 2026

@ryanaiagent fixed :)

@ryanaiagent
Copy link
Collaborator

/gemini review

@ryanaiagent ryanaiagent added needs review [Status] The PR/issue is awaiting review from the maintainer and removed request clarification [Status] The maintainer need clarification or more information from the author labels Jan 27, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses the issue where plugin modifications to events were not being persisted by ensuring that on_event_callback is executed before the event is appended to the session. The refactoring into a new private helper method, __append_event, centralizes this logic, which is a good improvement. However, there are a couple of areas for further refinement regarding Pythonic naming conventions for internal methods and ensuring consistent application of run configuration metadata.

if modified_event:
event = modified_event
_apply_run_config_custom_metadata(event, invocation_context.run_config)
await self.session_service.append_event(session=session, event=event)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The _apply_run_config_custom_metadata function is currently applied inside the if modified_event: block. This leads to inconsistent behavior: if a plugin modifies the event, metadata is applied twice (once at line 780 in _exec_with_plugin and again here); if no plugin modifies the event, metadata is applied only once (at line 780). To ensure metadata is applied exactly once to the final event before persistence, it should be applied unconditionally after the modified_event check, but before self.session_service.append_event.

Suggested change
if modified_event:
event = modified_event
_apply_run_config_custom_metadata(event, invocation_context.run_config)
await self.session_service.append_event(session=session, event=event)
if modified_event:
event = modified_event
_apply_run_config_custom_metadata(event, invocation_context.run_config)
await self.session_service.append_event(session=session, event=event)

# This does NOT emit any event.
await plugin_manager.run_after_run_callback(
invocation_context=invocation_context
async def __append_event(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The method __append_event uses a double underscore prefix, which triggers Python's name mangling. While this indicates privacy, the conventional Pythonic way to denote a method intended for internal use within a class (but not strictly private to prevent subclass access) is a single underscore prefix, e.g., _append_event. This improves readability and maintainability while still signaling that it's not part of the public API. Please update this method name and all its call sites.

Suggested change
async def __append_event(
async def _append_event(

if self._should_append_event(event, is_live_call):
await self.session_service.append_event(
session=session, event=event
event = await self.__append_event(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Please update the call to __append_event to _append_event for consistency with Python's convention for internal methods.

Suggested change
event = await self.__append_event(
event = await self._append_event(

logger.debug('Appending buffered event: %s', buffered_event)
await self.session_service.append_event(
session=session, event=buffered_event
buffered_event = await self.__append_event(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Please update the call to __append_event to _append_event for consistency with Python's convention for internal methods.

Suggested change
buffered_event = await self.__append_event(
buffered_event = await self._append_event(

logger.debug('Appending non-buffered event: %s', event)
await self.session_service.append_event(
session=session, event=event
event = await self.__append_event(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Please update the call to __append_event to _append_event for consistency with Python's convention for internal methods.

Suggested change
event = await self.__append_event(
event = await self._append_event(

if modified_event:
_apply_run_config_custom_metadata(
modified_event, invocation_context.run_config
event = await self.__append_event(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Please update the call to __append_event to _append_event for consistency with Python's convention for internal methods.

Suggested change
event = await self.__append_event(
event = await self._append_event(

@ryanaiagent ryanaiagent added request clarification [Status] The maintainer need clarification or more information from the author and removed needs review [Status] The PR/issue is awaiting review from the maintainer labels Jan 27, 2026
@ryanaiagent
Copy link
Collaborator

Hi @Ponts , can you address the suggestions.

@Ponts Ponts force-pushed the persist-modified-events branch from 82068d5 to e98b846 Compare January 27, 2026 10:20
@Ponts
Copy link
Author

Ponts commented Jan 27, 2026

@ryanaiagent suggestions addressed

@ryanaiagent
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request successfully addresses the issue of plugin on_event_callback modifications not being persisted by introducing a new _append_event helper method. This refactoring correctly places the plugin callback execution and custom metadata application before the event is saved to the session. However, there is an issue with the placement of the run_after_run_callback, which is now incorrectly executed for every event instead of once at the end of the run.

Comment on lines 833 to 835
await plugin_manager.run_after_run_callback(
invocation_context=invocation_context
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The run_after_run_callback is designed for global cleanup tasks or finalizing logs and metrics data, and should be executed once after the entire _exec_with_plugin generator has finished yielding all events. Its current placement inside the async for event in agen: loop means it will be called for every event, which is an incorrect change in behavior and could lead to unintended side effects or performance issues. Please move this call outside the async for event in agen: loop to ensure it's executed only once per _exec_with_plugin invocation.

@Ponts Ponts force-pushed the persist-modified-events branch from e98b846 to eab80da Compare January 27, 2026 16:37
@Ponts
Copy link
Author

Ponts commented Jan 27, 2026

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the event persistence logic to correctly handle modifications from on_event_callback plugins. By introducing a new _append_event helper method, the code now ensures that plugin callbacks are executed before an event is persisted to the session, which resolves the issue of plugin modifications not being saved. The changes are well-structured and improve code clarity by centralizing the event appending logic. I have a couple of minor suggestions to further improve the code's readability.

event = await self._append_event(
invocation_context, session, event
)
# Run the on_event callbacks to optionally modify the event.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This comment appears to be a leftover from the refactoring and is now misleading, as the on_event callbacks are executed within the _append_event method. I suggest removing this comment to avoid confusion.

Comment on lines +857 to +858
if modified_event:
event = modified_event
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For clarity and to avoid potential issues with an Event object that might evaluate to False in a boolean context (though unlikely with Pydantic models), it's safer to explicitly check for is not None.

Suggested change
if modified_event:
event = modified_event
if modified_event is not None:
event = modified_event

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core [Component] This issue is related to the core interface and implementation request clarification [Status] The maintainer need clarification or more information from the author

Projects

None yet

Development

Successfully merging this pull request may close these issues.

on_event_callback executes after append_event, preventing plugin modifications from being persisted

4 participants