Skip to content

Apply Google ADK deterministic providers inside workflow tasks - #1854

Open
DABH wants to merge 16 commits into
mainfrom
fix/adk-providers-in-workflow-threads
Open

DABH wants to merge 16 commits into
mainfrom
fix/adk-providers-in-workflow-threads

Conversation

@DABH

@DABH DABH commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

ADK keeps its time and uuid providers in contextvars.ContextVars, and GoogleAdkPlugin set them from the worker's context. Workflow tasks run on the workflow task executor's threads, which start with an empty context, so inside a workflow ADK still used wall-clock time and uuid.uuid4() for session, event, invocation, and function-call ids. Only debug mode, which runs activations inline, saw the deterministic values.

The plugin now rebinds each google.adk.platform ContextVar (time, uuid, and the random seam added in ADK 2.8.0) to one whose default is the Temporal provider, so it applies in every context on Worker and Replayer, sandboxed or not. Installation is idempotent; outside a workflow the providers fall back to the standard library.

Breaking: the google-adk extra now requires google-adk>=2.8.0, the first release with the _random seam.

Tested with a real Worker (sandboxed and unsandboxed) plus replay, and a fresh-thread check that fails on main. Lock and doc notes are in the first comment.

Same approach as _install_provider in #1675, which can rebase onto this.

ADK keeps its time, id, and random providers in contextvars.ContextVars.
GoogleAdkPlugin set them in the worker's context, but workflow tasks run
on the workflow task executor's threads, which start with an empty
context, so ADK code inside a workflow read the defaults: wall-clock time
and uuid.uuid4() for session, event, invocation, and function-call ids.
Only debug mode, which runs activations inline, saw the deterministic
values.

Rebind each google.adk.platform ContextVar to one whose default is the
Temporal provider so it is visible from every context, on Worker and
Replayer alike. Also install the random provider ADK added in 2.8.0 and
raise the google-adk floor to 2.8.0.
@DABH
DABH requested review from a team as code owners September 11, 2026 06:07
Use workflow.time() for the time provider, warn when installing replaces
a provider set earlier in the calling context, and document that
overrides must be made after the worker starts and that ADK id and
random generation raise ReadOnlyContextError in read-only contexts.
Tests assert provider identity.
@DABH

DABH commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Notes for review:

  • uv.lock: besides google-adk 2.4.0 -> 2.8.0, the diff carries google-genai 2.11.0 -> 2.20.0 (ADK 2.8.0 requires google-genai>=2.19), a new aiohttp edge, and marker/exclude-newer re-serialization from a newer uv. uv lock --check passes.
  • Behaviour documented in the CHANGELOG and README: installation warns if it replaces an ADK provider set earlier in the calling context (overrides must be made after the worker starts or from workflow code), and, like workflow.uuid4(), ADK id generation and get_random() in query handlers and update validators now raise ReadOnlyContextError instead of returning a random value.
  • The README's "Support for custom span exporters" bullet is removed as a stale-doc cleanup, unrelated to the fix.
  • Validation: uv run pytest tests/contrib/google_adk_agents -> 58 passed, 5 skipped (tests needing GOOGLE_API_KEY and the CI-skipped MCP tests); uv run poe lint (ruff, pyright, mypy, basedpyright, pydocstyle) passes.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

ADK’s public reset functions can restore nondeterministic standard-library providers inside workflows.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Ensures Google ADK uses Temporal’s deterministic time, UUID, and random providers across worker and replay threads.

Changes:

  • Installs thread-visible, idempotent ADK provider defaults.
  • Adds worker, sandbox, replay, fallback, and override tests.
  • Requires Google ADK 2.8.0 and documents compatibility implications.
File summaries
File Description
temporalio/contrib/google_adk_agents/_plugin.py Implements deterministic provider installation.
tests/contrib/google_adk_agents/test_adk_platform_providers.py Tests provider behavior across execution contexts.
temporalio/contrib/google_adk_agents/README.md Documents provider semantics.
pyproject.toml Raises the Google ADK minimum version.
uv.lock Locks Google ADK 2.8.0 and dependencies.
CHANGELOG.md Records the fix and replay compatibility warning.
Review details
  • Files reviewed: 5/6 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread temporalio/contrib/google_adk_agents/_plugin.py
Comment thread temporalio/contrib/google_adk_agents/_plugin.py Outdated
reset_*_provider() restores the module's _default_* binding, so install
now rebinds that too; a set-then-reset cycle lands back on the
deterministic providers instead of wall clock and stdlib random. ADK ids
and randoms now come from a workflow.new_random() cached on the workflow
instance (as the opentelemetry and langsmith integrations do) so ADK
draws never shift the sequence user code sees from workflow.random(),
with an explicit read-only guard so query handlers cannot advance the
cached stream.
@DABH DABH closed this Sep 14, 2026
@DABH DABH reopened this Sep 14, 2026
Comment thread temporalio/contrib/google_adk_agents/_plugin.py Outdated
Comment thread temporalio/contrib/google_adk_agents/_plugin.py Outdated
Per review: query handlers and update validators now get a
nondeterministic fallback random instead of ReadOnlyContextError - their
results are never replayed, and the guard only has to keep the cached
private stream untouched (a replay test proves it stays untouched).
workflow.uuid4() accepts an optional keyword-only random argument so the
uuid-from-generator derivation lives in one place; the ADK id provider
and langsmith's _uuid_from_random now delegate to it. Changelog entries
rehomed under Unreleased after the 1.33.0 cut and reworded for the
private-stream design.
Comment thread temporalio/workflow/_context.py Outdated
Comment thread temporalio/contrib/google_adk_agents/_plugin.py Outdated
DABH and others added 3 commits September 15, 2026 11:43
Query handlers and update validators now get time.time() from the ADK
time provider: their results are never replayed, and workflow.time()
would hand them the last activation's timestamp, stale by however long
the workflow has been parked. Covered by a query in the
query-during-run test.
Per review: the keyword random shadowed the module-level random()
function inside uuid4, which forced the body to inline the runtime
call. With the rename the body simply calls random() again, and
callers read as workflow.uuid4(rng=...).
Comment thread temporalio/contrib/google_adk_agents/_plugin.py Outdated
Comment thread temporalio/contrib/google_adk_agents/_plugin.py Outdated
DABH and others added 5 commits September 17, 2026 11:42
Per review: the read-only and outside-workflow fallbacks now return a
fresh random.Random() instead of module-level singletons. Nothing needs
to persist there - read-only results are never replayed, and ADK's
guidance to return an existing instance only matters for a seeded
generator whose sequence must continue; an unseeded one draws fresh OS
entropy either way, and ADK's only get_random() caller uses the value
immediately for retry jitter.
…workflow-threads

Resolve temporalio/contrib/google_adk_agents/_plugin.py in favor of this
branch's provider installation (module-level providers, the locked
_install_provider that also rebinds ADK's _default_* bindings, read-only
handling, and the workflow-private random stream), which supersedes the
three-argument _install_provider and the workflow.random()-sharing
providers added by #1675; keep #1675's optional model SDK passthrough.

Reconcile the CHANGELOG to one entry for the google-adk>=2.8.0 bump and
drop #1675's note about workflow.random()/workflow.uuid4() sequences
shifting across the upgrade, which no longer applies now that ADK draws
from a private stream. Reword three #1675 test comments to match.
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.

4 participants