Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: Add injectable time and UUID providers#1255

Open
DABH wants to merge 1 commit into
google:main from
DABH:deterministic-clock-uuid-providers
Open

feat: Add injectable time and UUID providers #1255
DABH wants to merge 1 commit into
google:main from
DABH:deterministic-clock-uuid-providers

Conversation

@DABH

@DABH DABH commented Jun 9, 2026
edited
Loading

Copy link
Copy Markdown

Link to Issue or Description of Change

Problem:
ADK generates timestamps and IDs by calling Instant.now() and UUID.randomUUID() directly. There are use cases where it's useful to have custom clock and UUID providers, such as when you want to make runs reproducible/deterministic.

adk-python already solves this with platform.time / platform.uuid - see google/adk-python#4200. Closely inspired by that, we propose adding similar functionality for adk-java. Note that a similar PR is open right now for adk-go: google/adk-go#964.

Solution:
Introduce a leaf package com.google.adk.platform with two functional interfaces:

  • TimeProviderInstant now(), default SYSTEM backed by Instant::now.
  • UuidProviderString newUuid(), default SYSTEM backed by UUID.randomUUID().

Both default to today's wall-clock / random behavior, so this change is fully backwards-compatible.

The providers are threaded as data through InvocationContext rather than stored in an ambient ThreadLocal. ADK's core flow hops threads via RxJava (e.g. BaseLlmFlow observes on Schedulers.io() / the agent executor), and events are built downstream on those worker threads. We considered using a ThreadLocal but that would silently fall back to the system providers after a thread hop, failing open with no error. So the proposed solution here, carrying the providers on the InvocationContext (which is already captured in the RxJava lambdas and passed down the chain) makes them visible on whatever thread builds an event, so this is thread-safe. This mirrors the proposed changes to adk-go, which diverged from adk-python's contextvar mechanism for the same concurrency reason.

Users configure the providers once on the Runner (Runner.builder().timeProvider(...).uuidProvider(...)); no knowledge of ADK's internal threads is required.

The other files edited in this PR are just call sites updated to derive from the in-scope providers:

  • InvocationContext — invocation ID, plus now() / newUuid() accessors.
  • BaseLlmFlow, Functions, OutputSchema — event IDs + timestamps, function-call IDs.
  • Runner — invocation ID + event builds.
  • InMemorySessionService — auto session ID + lastUpdateTime (constructor injection; default constructor keeps SYSTEM behavior).

Event.generateEventId() and the Event.Builder.build() timestamp default now delegate to UuidProvider.SYSTEM / TimeProvider.SYSTEM, so the platform interfaces are the single source for generated IDs and timestamps while the Event API stays unchanged.

Testing Plan

Unit Tests:

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

New/updated tests:

  • platform/TimeProviderTest, platform/UuidProviderTestSYSTEM defaults + custom-provider behavior.
  • InvocationContextTest — providers default to SYSTEM, are carried by the builder/toBuilder(), and drive now() / newUuid().
  • FunctionsTest — function-call ID derives from the injected provider.
  • InMemorySessionServiceTest — injected providers produce a deterministic session ID and lastUpdateTime.
  • RunnerTest.runAsync_withDeterministicProviders_producesIdenticalEvents — the headline guarantee: running the same input twice through the full flow yields byte-identical event IDs, timestamps, and invocation IDs.

Local results (./mvnw -pl core test, openjdk@17), all green:

TimeProviderTest / UuidProviderTest Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
RunnerTest Tests run: 139, Failures: 0, Errors: 0, Skipped: 0
InvocationContextTest / FunctionsTest / InMemorySessionServiceTest Failures: 0, Errors: 0

./mvnw -pl core fmt:check — 0 non-complying files.

Manual End-to-End (E2E) Tests:

This is an in-process library seam with no runtime/config surface to exercise manually. The guarantee is verified by the unit test above, which asserts two independent runs of the same invocation produce identical IDs and timestamps.

Checklist

  • I have read the CONTRIBUTING.md document.
  • My pull request contains a single commit.
  • 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.

Additional context

Motivated by maintaining feature parity with adk-python and the proposed changes to adk-go.

@DABH DABH changed the title (削除) feat: Add injectable time and UUID providers for deterministic runs (削除ここまで) (追記) feat: Add injectable time and UUID providers (追記ここまで) Jun 9, 2026

Copy link
Copy Markdown
Contributor

Hi @DABH, thank you for your contribution! We appreciate you taking the time to submit this pull request. As per contribution policy, please ensure your PR consists of a single commit and i have noticed inconsistency code formatting. Could you please change your commits accordingly and address the code formatting issue?

@hemasekhar-p hemasekhar-p added the waiting on reporter Waiting for reaction by reporter. Failing that, maintainers will eventually closed it as stale. label Jun 10, 2026
Comment thread core/src/main/java/com/google/adk/agents/InvocationContext.java
Comment thread core/src/main/java/com/google/adk/flows/llmflows/BaseLlmFlow.java
Comment thread core/src/test/java/com/google/adk/runner/RunnerTest.java Outdated
Comment thread core/src/main/java/com/google/adk/agents/InvocationContext.java

Copy link
Copy Markdown
Contributor

@DABH, thank you for your quick response. when running a Maven build, some files are modifying due to incorrect formatting. so, use the Maven build to ensure your changes are properly aligned and please make sure your PR contains a single commit.

@hemasekhar-p hemasekhar-p added waiting on reporter Waiting for reaction by reporter. Failing that, maintainers will eventually closed it as stale. and removed waiting on reporter Waiting for reaction by reporter. Failing that, maintainers will eventually closed it as stale. labels Jun 11, 2026
...d IDs
ADK generates timestamps and IDs by calling Instant.now() and
UUID.randomUUID() directly, leaving callers no way to control them. This
blocks integrations that need to supply their own timestamps and IDs.
adk-python and adk-go already expose an equivalent seam.
Add a leaf com.google.adk.platform package with TimeProvider and
UuidProvider functional interfaces, each with a SYSTEM default that
preserves today's wall-clock/random behavior. Rather than an ambient
ThreadLocal (which would silently fall back to the system providers once
the RxJava flow hops onto a Schedulers worker thread), the providers are
threaded as data through InvocationContext, so they are visible on whatever
thread builds an event. Callers configure them once on the Runner, which
also injects them into the default InMemorySessionService it constructs.
Event ids and timestamps, the invocation id, function-call ids, the event
compaction summarizer, and the InMemorySessionService session id and
lastUpdateTime now derive from the in-scope providers. Event.generateEventId()
and the Event.Builder timestamp default delegate to the SYSTEM providers, so
the platform interfaces are the single source for generated ids and times
while the public Event API stays unchanged.
@DABH DABH force-pushed the deterministic-clock-uuid-providers branch from d92d033 to 742e700 Compare June 11, 2026 17:47

DABH commented Jun 11, 2026

Copy link
Copy Markdown
Author

Thanks @hemasekhar-p — addressed both. I rebased onto the latest main and ran the full Maven build (which runs the bound fmt:format goal); mvn fmt:check now reports 0 non-complying files across all modules. I've also squashed the change into a single commit. Let me know if you still see any files being reformatted on your end.

DABH commented Jun 11, 2026

Copy link
Copy Markdown
Author

Also @hemasekhar-p I made a few improvements to the design/implementation based on @xumaple's feedback (updated the PR description accordingly). Thanks again for the fast review and please just let me know what else I can do to help make this PR mergeable. I appreciate your help and look forward to hearing from you!

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

Reviewers

1 more reviewer

@xumaple xumaple xumaple left review comments

Reviewers whose approvals may not affect merge requirements

Labels

waiting on reporter Waiting for reaction by reporter. Failing that, maintainers will eventually closed it as stale.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /