Skip to content

Commit 78b1c4e

Browse files
committed
feat: extract durable runtime support changes
Moves runtime, event, and session service changes to a separate branch. These changes support deterministic execution required for durable integrations.
1 parent f27a9cf commit 78b1c4e

5 files changed

Lines changed: 165 additions & 4 deletions

File tree

pr_description.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Refactor: Temporal Integration Cleanup & Documentation
2+
3+
## Summary
4+
This PR refactors the `TemporalPlugin` to remove implicit default configurations and adds comprehensive documentation for the ADK-Temporal integration. The goal is to make the integration stricter, clearer, and easier to debug by forcing explicit configuration from the user.
5+
6+
## Key Changes
7+
8+
### 1. `TemporalPlugin` Refactor
9+
* **Removed Implicit Defaults**: The plugin no longer contains hardcoded default timeouts (previously 60s) or retry policies. Users must now explicitly provide `activity_options` when initializing the plugin.
10+
* **Removed Magic Model Resolution**: Removed the fallback logic that attempted to guess the model name from the agent context if missing. Failed requests will now raise a clear `ValueError` instead of behaving unpredictably.
11+
* **Simplified `before_model_callback`**: The interception hook is now a pure passthrough to `workflow.execute_activity` without additional logic or conditional checks (`workflow.in_workflow()` check removed to fail fast if misused).
12+
13+
### 2. Documentation (`README.md`)
14+
* Added a new `src/google/adk/integrations/temporal/README.md`.
15+
* **Internals Explained**: Documented the "Interception Flow" (how it short-circuits ADK) and "Dynamic Activities" (how `AgentName.generate_content` works without registration).
16+
* **Clear Usage Examples**: provided distinct, copy-pasteable snippets for **Agent Setup** (Workflow side) and **Worker Setup**, clarifying the role of `AdkWorkerPlugin`.
17+
18+
## Impact
19+
* **Safety**: Impossible to accidentally run with unsafe defaults.
20+
* **Debuggability**: Errors are now explicit (missing options, missing model).
21+
* **Clarity**: Users understand exactly *why* they are adding plugins (serialization, runtime determinism, activity routing) via the new docs.
22+
23+
## Usage Example
24+
25+
### Agent Side
26+
```python
27+
# You MUST now provide explicit options
28+
activity_options = {
29+
"start_to_close_timeout": timedelta(minutes=1),
30+
"retry_policy": RetryPolicy(maximum_attempts=3)
31+
}
32+
33+
agent = Agent(
34+
model="gemini-2.5-pro",
35+
plugins=[
36+
# Routes model calls to Temporal Activities
37+
TemporalPlugin(activity_options=activity_options)
38+
]
39+
)
40+
```
41+
42+
### Worker Side
43+
```python
44+
worker = Worker(
45+
client,
46+
task_queue="my-queue",
47+
# Configures ADK Runtime, Pydantic Support & Unsandboxed Runner
48+
plugins=[AdkWorkerPlugin()]
49+
)
50+
```

src/google/adk/events/event.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from typing import Optional
1919
import uuid
2020

21+
from google.adk import runtime
2122
from google.genai import types
2223
from pydantic import alias_generators
2324
from pydantic import ConfigDict
@@ -70,7 +71,7 @@ class Event(LlmResponse):
7071
# Do not assign the ID. It will be assigned by the session.
7172
id: str = ''
7273
"""The unique identifier of the event."""
73-
timestamp: float = Field(default_factory=lambda: datetime.now().timestamp())
74+
timestamp: float = Field(default_factory=lambda: runtime.get_time())
7475
"""The timestamp of the event."""
7576

7677
def model_post_init(self, __context):
@@ -125,4 +126,4 @@ def has_trailing_code_execution_result(
125126

126127
@staticmethod
127128
def new_id():
128-
return str(uuid.uuid4())
129+
return runtime.new_uuid()

src/google/adk/runtime.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
"""Runtime module for abstracting system primitives like time and UUIDs."""
16+
17+
import time
18+
import uuid
19+
from typing import Callable
20+
21+
_time_provider: Callable[[], float] = time.time
22+
_id_provider: Callable[[], str] = lambda: str(uuid.uuid4())
23+
24+
25+
def set_time_provider(provider: Callable[[], float]) -> None:
26+
"""Sets the provider for the current time.
27+
28+
Args:
29+
provider: A callable that returns the current time in seconds since the
30+
epoch.
31+
"""
32+
global _time_provider
33+
_time_provider = provider
34+
35+
36+
def set_id_provider(provider: Callable[[], str]) -> None:
37+
"""Sets the provider for generating unique IDs.
38+
39+
Args:
40+
provider: A callable that returns a unique ID string.
41+
"""
42+
global _id_provider
43+
_id_provider = provider
44+
45+
46+
def get_time() -> float:
47+
"""Returns the current time in seconds since the epoch."""
48+
return _time_provider()
49+
50+
51+
def new_uuid() -> str:
52+
"""Returns a new unique ID."""
53+
return _id_provider()

src/google/adk/sessions/in_memory_session_service.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from typing_extensions import override
2424

25+
from google.adk import runtime
2526
from . import _session_util
2627
from ..errors.already_exists_error import AlreadyExistsError
2728
from ..events.event import Event
@@ -108,14 +109,14 @@ def _create_session_impl(
108109
session_id = (
109110
session_id.strip()
110111
if session_id and session_id.strip()
111-
else str(uuid.uuid4())
112+
else runtime.new_uuid()
112113
)
113114
session = Session(
114115
app_name=app_name,
115116
user_id=user_id,
116117
id=session_id,
117118
state=session_state or {},
118-
last_update_time=time.time(),
119+
last_update_time=runtime.get_time(),
119120
)
120121

121122
if app_name not in self.sessions:

tests/unittests/test_runtime.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
"""Unit tests for the runtime module."""
16+
17+
import time
18+
import uuid
19+
import unittest
20+
from unittest.mock import MagicMock, patch
21+
22+
from google.adk import runtime
23+
24+
25+
class TestRuntime(unittest.TestCase):
26+
27+
def tearDown(self):
28+
# Reset providers to default after each test
29+
runtime.set_time_provider(time.time)
30+
runtime.set_id_provider(lambda: str(uuid.uuid4()))
31+
32+
def test_default_time_provider(self):
33+
# Verify it returns a float that is close to now
34+
now = time.time()
35+
rt_time = runtime.get_time()
36+
self.assertIsInstance(rt_time, float)
37+
self.assertAlmostEqual(rt_time, now, delta=1.0)
38+
39+
def test_default_id_provider(self):
40+
# Verify it returns a string uuid
41+
uid = runtime.new_uuid()
42+
self.assertIsInstance(uid, str)
43+
# Should be parseable as uuid
44+
uuid.UUID(uid)
45+
46+
def test_custom_time_provider(self):
47+
# Test override
48+
mock_time = 123456789.0
49+
runtime.set_time_provider(lambda: mock_time)
50+
self.assertEqual(runtime.get_time(), mock_time)
51+
52+
def test_custom_id_provider(self):
53+
# Test override
54+
mock_id = "test-id-123"
55+
runtime.set_id_provider(lambda: mock_id)
56+
self.assertEqual(runtime.new_uuid(), mock_id)

0 commit comments

Comments
 (0)