Skip to content

Commit 8a09ec2

Browse files
feat(termination): add pluggable termination conditions for multi-agent 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
1 parent 0d10dd9 commit 8a09ec2

11 files changed

Lines changed: 1162 additions & 0 deletions

src/google/adk_community/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@
1414

1515
from . import memory
1616
from . import sessions
17+
from . import termination
1718
from . import version
1819
__version__ = version.__version__
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2025 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+
"""Community termination conditions for ADK multi-agent workflows."""
16+
17+
from __future__ import annotations
18+
19+
from .external_termination import ExternalTermination
20+
from .function_call_termination import FunctionCallTermination
21+
from .max_iterations_termination import MaxIterationsTermination
22+
from .termination_condition import AndTerminationCondition
23+
from .termination_condition import OrTerminationCondition
24+
from .termination_condition import TerminationCondition
25+
from .termination_condition import TerminationResult
26+
from .text_mention_termination import TextMentionTermination
27+
from .timeout_termination import TimeoutTermination
28+
from .token_usage_termination import TokenUsageTermination
29+
30+
__all__ = [
31+
'AndTerminationCondition',
32+
'ExternalTermination',
33+
'FunctionCallTermination',
34+
'MaxIterationsTermination',
35+
'OrTerminationCondition',
36+
'TerminationCondition',
37+
'TerminationResult',
38+
'TextMentionTermination',
39+
'TimeoutTermination',
40+
'TokenUsageTermination',
41+
]
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2025 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+
"""A termination condition controlled programmatically via ``set()``."""
16+
17+
from __future__ import annotations
18+
19+
from typing import Optional
20+
from typing import Sequence
21+
22+
from google.adk.events.event import Event
23+
from .termination_condition import TerminationCondition
24+
from .termination_condition import TerminationResult
25+
26+
27+
class ExternalTermination(TerminationCondition):
28+
"""A termination condition that is controlled externally by calling ``set()``.
29+
30+
Useful for integrating external stop signals such as a UI "Stop" button
31+
or application-level logic.
32+
33+
Example::
34+
35+
stop_button = ExternalTermination()
36+
37+
agent = LoopAgent(
38+
name='my_loop',
39+
sub_agents=[...],
40+
termination_condition=stop_button,
41+
)
42+
43+
# Elsewhere (e.g. from a UI event handler):
44+
stop_button.set()
45+
"""
46+
47+
def __init__(self) -> None:
48+
self._terminated = False
49+
50+
@property
51+
def terminated(self) -> bool:
52+
return self._terminated
53+
54+
def set(self) -> None:
55+
"""Signals that the conversation should terminate at the next check."""
56+
self._terminated = True
57+
58+
async def check(self, events: Sequence[Event]) -> Optional[TerminationResult]:
59+
if self._terminated:
60+
return TerminationResult(reason='Externally terminated')
61+
return None
62+
63+
async def reset(self) -> None:
64+
self._terminated = False
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Copyright 2025 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+
"""Terminates when a specific function (tool) has been executed."""
16+
17+
from __future__ import annotations
18+
19+
from typing import Optional
20+
from typing import Sequence
21+
22+
from google.adk.events.event import Event
23+
from .termination_condition import TerminationCondition
24+
from .termination_condition import TerminationResult
25+
26+
27+
class FunctionCallTermination(TerminationCondition):
28+
"""Terminates when a tool with a specific name has been executed.
29+
30+
The condition checks ``FunctionResponse`` parts in events.
31+
32+
Example::
33+
34+
# Stop when the "approve" tool is called
35+
condition = FunctionCallTermination('approve')
36+
"""
37+
38+
def __init__(self, function_name: str) -> None:
39+
self._function_name = function_name
40+
self._terminated = False
41+
42+
@property
43+
def terminated(self) -> bool:
44+
return self._terminated
45+
46+
async def check(self, events: Sequence[Event]) -> Optional[TerminationResult]:
47+
if self._terminated:
48+
return None
49+
50+
for event in events:
51+
for response in event.get_function_responses():
52+
if response.name == self._function_name:
53+
self._terminated = True
54+
return TerminationResult(
55+
reason=f"Function '{self._function_name}' was executed"
56+
)
57+
return None
58+
59+
async def reset(self) -> None:
60+
self._terminated = False
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2025 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+
"""Terminates after a maximum number of events have been processed."""
16+
17+
from __future__ import annotations
18+
19+
from typing import Optional
20+
from typing import Sequence
21+
22+
from google.adk.events.event import Event
23+
from .termination_condition import TerminationCondition
24+
from .termination_condition import TerminationResult
25+
26+
27+
class MaxIterationsTermination(TerminationCondition):
28+
"""Terminates the conversation after a maximum number of events.
29+
30+
Example::
31+
32+
# Stop after 10 events
33+
condition = MaxIterationsTermination(10)
34+
"""
35+
36+
def __init__(self, max_iterations: int) -> None:
37+
if max_iterations <= 0:
38+
raise ValueError('max_iterations must be a positive integer.')
39+
self._max_iterations = max_iterations
40+
self._count = 0
41+
self._terminated = False
42+
43+
@property
44+
def terminated(self) -> bool:
45+
return self._terminated
46+
47+
async def check(self, events: Sequence[Event]) -> Optional[TerminationResult]:
48+
if self._terminated:
49+
return None
50+
self._count += len(events)
51+
52+
if self._count >= self._max_iterations:
53+
self._terminated = True
54+
return TerminationResult(
55+
reason=(
56+
f'Maximum iterations of {self._max_iterations} reached,'
57+
f' current count: {self._count}'
58+
)
59+
)
60+
return None
61+
62+
async def reset(self) -> None:
63+
self._terminated = False
64+
self._count = 0

0 commit comments

Comments
 (0)