Skip to content

fix: route workflow IDs and retry jitter through platform seams#6468

Open
DABH wants to merge 2 commits into
google:mainfrom
DABH:platform-determinism-seams
Open

fix: route workflow IDs and retry jitter through platform seams#6468
DABH wants to merge 2 commits into
google:mainfrom
DABH:platform-determinism-seams

Conversation

@DABH

@DABH DABH commented Jul 24, 2026

Copy link
Copy Markdown

Describe the bug

google.adk.platform provides injectable time and uuid providers so that embedders can customize how generated values are produced - for example to make agent runs deterministic and reproducible for testing, simulation, or auditing. Event IDs, invocation IDs, timestamps, and client function-call IDs already route through them, but three call sites on the workflow HITL/retry path still call the stdlib directly, so they bypass any installed provider:

  1. events/request_input.py - RequestInput.interrupt_id defaults to uuid.uuid4(). This is the ID that recorded user FunctionResponses reference on resume; an ID minted outside the installed provider can't be reproduced, which breaks interrupt matching.
  2. workflow/_tool_node.py - ToolContext.function_call_id is minted with uuid.uuid4() instead of platform_uuid.new_uuid() (which flows/llm_flows/functions.py already uses for the same purpose).
  3. workflow/utils/_retry_utils.py - retry backoff jitter draws from the global random module. Jitter defaults to 1.0 for any RetryConfig, so every retried node computes an unreproducible delay.

Describe the fix

  • Add a platform random seam - google.adk.platform._random with get_random() / set_random_provider() / reset_random_provider(), exported from google.adk.platform per the visibility convention - mirroring the existing time/uuid providers (ContextVar-based, default = stdlib random.Random()).
  • Route the three call sites through the platform seams. Default behavior is unchanged (same uuid4/uniform semantics when no provider is installed).
  • Migrate the two existing jitter tests from mock.patch('random.uniform', ...) to injecting a mock via set_random_provider, exercising the new seam with the exact same assertions.

This follows the injectable-provider approach also adopted in google/adk-java#1255.

Testing plan

New unit tests:

  • tests/unittests/platform/test_random.py - default provider, custom provider, reset (mirrors test_uuid.py).
  • tests/unittests/events/test_request_input.py - default interrupt_id is a uuid; minted via the platform id provider; explicit id preserved.
  • tests/unittests/workflow/test_tool_node.py::test_tool_node_function_call_id_uses_platform_id_provider - the tool receives a provider-minted function_call_id.
  • tests/unittests/workflow/utils/test_retry_utils.py::test_jitter_uses_platform_random_provider - a seeded provider makes the computed delay reproducible.
$ pytest tests/unittests/workflow/ tests/unittests/platform/ tests/unittests/events/
791 passed, 11 skipped, 10 xfailed in 6.95s

All pre-commit hooks (isort, pyink, addlicense, new-file-prefix, ADK compliance, codespell) pass.

@google-cla

google-cla Bot commented Jul 24, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

ADK routes event IDs, invocation IDs, timestamps, and client
function-call IDs through the injectable google.adk.platform providers so
that embedders can customize how generated values are produced — for
example to make agent runs deterministic and replayable. Three call sites
on the workflow HITL/retry path still reach for the stdlib directly, which
bypasses any installed provider:

- RequestInput.interrupt_id defaults to uuid.uuid4(). Recorded user
  responses reference this ID, so an ID that regenerates differently on a
  replayed run breaks resume matching.
- _ToolNode mints ToolContext.function_call_id with uuid.uuid4().
- Retry backoff jitter draws from the global random module (jitter defaults
  to 1.0 for any RetryConfig), making computed delays unreproducible.

Add a google.adk.platform random seam (get_random / set_random_provider /
reset_random_provider, exported via google.adk.platform per the visibility
convention) mirroring the existing time and uuid providers, and route the
three call sites through the platform seams. Default behavior is unchanged.

The two existing jitter tests patched random.uniform globally; they now
inject a mock through set_random_provider, exercising the new seam.
@DABH
DABH force-pushed the platform-determinism-seams branch from 41aa6ac to 95aeb2a Compare July 24, 2026 23:16
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants