Skip to content

feat(termination): add pluggable termination conditions for multi-age…#118

Open
davidmonterocrespo24 wants to merge 1 commit intogoogle:mainfrom
davidmonterocrespo24:feat/termination-conditions
Open

feat(termination): add pluggable termination conditions for multi-age…#118
davidmonterocrespo24 wants to merge 1 commit intogoogle:mainfrom
davidmonterocrespo24:feat/termination-conditions

Conversation

@davidmonterocrespo24
Copy link
Copy Markdown

Summary

Adds a new google.adk_community.termination module with pluggable termination
conditions for use with LoopAgent and Runner-based multi-agent workflows.

This contribution was originally submitted to google/adk-python as
PR #5213 (referencing
issue #5211 and
adk-js#193), and was redirected
here by maintainer @rohityan.

New Module: google.adk_community.termination

Termination Conditions

Class Description
MaxIterationsTermination Stops after N events
TextMentionTermination Stops when a keyword appears in event content
TimeoutTermination Stops after a wall-clock duration
TokenUsageTermination Stops when total/prompt/completion token budgets are exceeded
FunctionCallTermination Stops when a specific function response is received
ExternalTermination Stops on external signal via .set()
OrTerminationCondition a | b — stops if either condition is met
AndTerminationCondition a & b — stops only when both are met

All conditions implement:

  • check(events: list[Event]) -> TerminationResult
  • reset() — called automatically at run start, safe to reuse across invocations
  • terminated property

Usage Example

from google.adk_community.termination import (
    MaxIterationsTermination,
    TextMentionTermination,
)

# Stop after 10 events OR when "DONE" appears
condition = MaxIterationsTermination(10) | TextMentionTermination("DONE")

…nt workflows

Add a new google.adk_community.termination module with pluggable termination
conditions for use with LoopAgent and Runner-based multi-agent workflows.

New termination conditions:
- MaxIterationsTermination: stops after N events
- TextMentionTermination: stops when a keyword appears in event content
- TimeoutTermination: stops after a wall-clock duration
- TokenUsageTermination: stops when token budgets are exceeded
- FunctionCallTermination: stops when a specific function is called
- ExternalTermination: stops on external signal (set() method)
- OrTerminationCondition / AndTerminationCondition composites (via | and &)

All conditions implement check(events), reset(), and the terminated property.
reset() is called automatically at the start of each run so the same instance
can be reused across invocations.

Related: google/adk-python#5211
Related: google/adk-python#5213
Cross-repo: google/adk-js#193
@DeanChensj
Copy link
Copy Markdown
Collaborator

@gemini-cli /review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

🤖 Hi @DeanChensj, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! The termination conditions are well-implemented and the test coverage is excellent.

I've left a few minor suggestions regarding state consistency in compound conditions and more descriptive termination reasons. Overall, this is a great addition to the community package.

self,
left: TerminationCondition,
right: TerminationCondition,
) -> None:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In OrTerminationCondition, if the left condition matches, the right condition is never checked. While this works for the immediate termination, it means the right condition misses these events, which could lead to inconsistent internal state (e.g., token counts or iteration counts) if the condition is reused or part of a more complex logic.

Consider checking both sides even if the first one returns a result:

left_result = await self._left.check(events)
right_result = await self._right.check(events)

if left_result:
    self._terminated = True
    return left_result
if right_result:
    self._terminated = True
    return right_result


@property
def terminated(self) -> bool:
return self._terminated
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The generic reason "All termination conditions met" loses the specific context of why the underlying conditions terminated. It might be helpful to combine the reasons from both left and right.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants