[AI-303] google_adk_agents: graph workflows, dynamic workflows, and durable HITL (ADK v2) - #1675
[AI-303] google_adk_agents: graph workflows, dynamic workflows, and durable HITL (ADK v2)#1675DABH wants to merge 16 commits into
Conversation
3d4be86 to
697e407
Compare
Graph/dynamic workflow support relies on Workflow-as-Tool (added in ADK 2.4.0) and HITL resumption for standalone nodes and NodeTool plus related resumable-mode hardening (added in ADK 2.5.0). Existing contrib tests pass unchanged against 2.5.0. The exclude-newer-package exemption can be removed once 2.5.0 (published 2026-07-16) passes the two-week cooldown.
…K v2 - activity_node() wraps a Temporal activity as an ADK FunctionNode for use in Workflow graphs and dynamic ctx.run_node() calls. - HITL helpers (HitlRequest, pending_hitl_requests, hitl_input_response, hitl_confirmation_response) cover ADK's pause/resume wire format so workflows can durably wait on human input via signals/updates. - setup_deterministic_runtime() additionally installs a workflow.random()- backed provider on ADK versions exposing the platform random seam. - README sections for graph workflows, dynamic workflows, durable HITL, and determinism notes.
ADK holds its platform time/uuid/random providers in ContextVars, and the public set_*_provider helpers only affect the calling context. Temporal executes workflow code on executor threads whose contexts never see the run_context call, so the deterministic providers were previously never active inside workflows: event/function-call ids came from stdlib uuid4 and Event timestamps from wall-clock time. This went unnoticed because Temporal replay compares command sequences, not payloads — but any flow whose control depends on a generated id (HITL resume matching recorded responses by interrupt/function-call id) diverged on replay and hung. Rebind each platform module's ContextVar with the deterministic provider as its default. Context-local set_*_provider calls still override it, and the providers fall back to real primitives outside workflows.
- Graph: sequential/conditional-routing/parallel-join graphs with activity-backed nodes, LlmAgent node through the invoke_model activity, node timeout (durable timer), ADK RetryConfig retry, and a seam-gated jittered-retry replay test. All run with max_cached_workflows=0 so every workflow task fully replays. - Dynamic: ctx.run_node loops and asyncio.gather fan-out, Workflow-as-Tool, and HITL resume proving completed activity children are served from the session cache (exactly one real execution). - HITL: human-input node resume via query + update, activity_tool behind FunctionTool(require_confirmation=True) with the gated activity running exactly once on approval and never on rejection (replay-proven via forced eviction), multiple pending requests with partial responses, and a seam-gated default-interrupt-id replay test. - Replay: recorded graph_workflow.json and hitl_workflow.json histories added to the replay regression test. Tests marked seam-gated skip on google-adk releases that predate the upstream platform-seam routing (google/adk-python PR) and run against a build that includes it.
697e407 to
41276f8
Compare
…d bindings) basedpyright fails CI on warnings: replace typing.Optional/typing.Mapping with PEP 604/collections.abc forms, drop two unused query-result bindings, and mark the intentionally-unused node_input parameters (the name is load-bearing for ADK FunctionNode binding) with targeted ignores.
41276f8 to
51dc7a5
Compare
# Conflicts: # pyproject.toml # temporalio/contrib/google_adk_agents/README.md # temporalio/contrib/google_adk_agents/_plugin.py # tests/contrib/google_adk_agents/test_adk_streaming.py
google-adk 2.8.0 (released 2026-08-26) is the first release containing the platform seams this plugin relies on: the random provider and the routing of RequestInput interrupt ids, _ToolNode function-call ids, and retry jitter through google.adk.platform (google/adk-python@8f85107c), plus the deterministic ParallelWorker failure selection (google/adk-python@d31b5e7d). Resolve google-adk from PyPI again and raise the extra's floor accordingly. The repo's exclude-newer window is two weeks, so a per-package exclude-newer-package override admits 2.8.0 until it ages into the window (2026-09-10); it can be removed after that.
There was a problem hiding this comment.
🟡 Changes recommended
Positional-only activity signatures are mishandled, and the temporary dependency cutoff remains committed.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds ADK v2 graph, dynamic workflow, and durable human-in-the-loop support to the Temporal Google ADK integration.
Changes:
- Adds
activity_nodeand HITL request/response helpers. - Installs deterministic ADK time, UUID, and random providers.
- Adds documentation, dependency updates, integration tests, and replay histories.
File summaries
| File | Description |
|---|---|
CHANGELOG.md |
Documents new ADK v2 capabilities and version floor. |
pyproject.toml |
Raises Google ADK minimum and adds a temporary cutoff override. |
uv.lock |
Locks Google ADK 2.8.0 and updated dependencies. |
temporalio/contrib/google_adk_agents/__init__.py |
Exports HITL APIs. |
temporalio/contrib/google_adk_agents/_hitl.py |
Implements HITL request parsing and response construction. |
temporalio/contrib/google_adk_agents/_plugin.py |
Installs deterministic platform providers. |
temporalio/contrib/google_adk_agents/workflow.py |
Adds activity-backed graph nodes. |
temporalio/contrib/google_adk_agents/README.md |
Documents graph, dynamic, and HITL workflows. |
tests/contrib/google_adk_agents/test_adk_graph_workflows.py |
Tests graph execution and replay safety. |
tests/contrib/google_adk_agents/test_adk_dynamic_workflows.py |
Tests dynamic nodes and resume behavior. |
tests/contrib/google_adk_agents/test_adk_hitl.py |
Tests durable input and confirmation flows. |
tests/contrib/google_adk_agents/test_google_adk_agents_replay.py |
Extends recorded-history replay coverage. |
tests/contrib/google_adk_agents/histories/graph_workflow.json |
Adds graph workflow replay history. |
tests/contrib/google_adk_agents/histories/hitl_workflow.json |
Adds HITL workflow replay history. |
Review details
- Files reviewed: 13/14 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # Admit google-adk 2.8.0 (released 2026-08-26) inside the 2-week window; drop after 2026-09-10. | ||
| exclude-newer-package = { google-adk = "2026-08-27T00:00:00Z" } |
There was a problem hiding this comment.
Agreed, this is scaffolding. 2.8.0 leaves the two-week window on Sep 9, and this line (plus the lock entry it generates) comes out right after that, before merge.
| params = [ | ||
| p | ||
| for p in sig.parameters.values() | ||
| if p.kind | ||
| in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY) | ||
| ] |
There was a problem hiding this comment.
Good catch. Fixed in e4b35b9: positional-only parameters are now included, and dict inputs bind to positional arguments by name with errors for missing or unexpected keys. Tests added for a positional-only single parameter, a positional-only pair with a defaulted third, and both error paths.
| ] = { | ||
| _REQUEST_INPUT_FUNCTION_CALL_NAME: "input", | ||
| _REQUEST_CONFIRMATION_FUNCTION_CALL_NAME: "tool_confirmation", | ||
| _REQUEST_CREDENTIAL_FUNCTION_CALL_NAME: "credential", |
There was a problem hiding this comment.
This exposes adk_request_credential as a pending HitlRequest, but neither response helper can actually resume it: hitl_input_response() emits adk_request_input and hitl_confirmation_response() emits adk_request_confirmation. ADK’s auth preprocessor only consumes a response whose name is exactly adk_request_credential, so an agent-auth flow discovered through this API remains unresolved if the caller follows the documented helpers. Could we add and export a hitl_credential_response() helper with an auth-resume test, or omit the credential kind until it is supported?
There was a problem hiding this comment.
You're right that nothing could answer it. I went with the omit route in 8a98a4d. Supporting auth properly needs more than a response helper: ADK exchanges the credential with network I/O inside the flow, which would run in workflow code, and the exchanged secret would end up in workflow history. The docs now say auth requests are out of scope for these helpers and credentials should be resolved worker-side. Happy to add a helper later if we want an in-workflow auth story.
activity.defn only rejects keyword-only parameters, so positional-only ones are valid activity signatures. activity_node filtered them out of its parameter list, so a single positional-only parameter fell into the zero-argument branch and the activity was scheduled without its input, and mixed signatures were miscounted. Include positional-only parameters and bind dict inputs to positional arguments by name, with clear errors for missing or unexpected keys, instead of Signature.bind, which cannot bind positional-only parameters from keywords.
pending_hitl_requests surfaced adk_request_credential calls as kind="credential", but no helper could build the adk_request_credential response ADK's auth path consumes, so an auth flow discovered through this API could never be resumed. Supporting it properly is more than a response helper: ADK exchanges the credential with network I/O inside the flow, and the exchanged secret would be recorded in workflow history. Leave auth requests out of the helpers and document that credentials should be resolved worker-side.
ADK's content processor probes for the anthropic, litellm, and openai model classes on every LLM turn. Any of those SDKs that is installed but not yet imported gets imported inside each workflow sandbox on the first turn, which is slow enough to trip the workflow deadlock detector and fail the workflow task; the retried task re-executes live, which is how the duplicate-span failure in test_single_agent_telemetry showed up in CI. Pass them through like the OpenAI Agents plugin does for openai.
What was changed
ADK v2 moved to a graph runtime. Its scheduler is plain asyncio driven by session events, so it already runs deterministically inside a workflow with
TemporalModel, the MCP toolsets, andactivity_as_toolintercepting as before. This PR adds the missing pieces (AI-303):activity_node(...): run a Temporal activity as an ADKFunctionNode, either in aWorkflow(edges=...)graph or viactx.run_node(...).HitlRequest,pending_hitl_requests,hitl_input_response,hitl_confirmation_responsefor ADK'sadk_request_input/adk_request_confirmationpause-and-resume protocol (auth requests are out of scope; see the README). The durable wait itself is just workflow code: query the pending requests, wait on a signal or update, callrun_asyncagain. Works withactivity_as_toolbehindFunctionTool(require_confirmation=True).set_*_providercalls. Replay only compares commands, so nobody noticed until HITL, which matches responses on generated ids. The providers (time, uuid, and now random) are installed as ContextVar defaults instead.anthropic,litellm, andopenaiSDKs through the workflow sandbox. ADK probes them on every LLM turn, and importing an installed one inside each sandbox was slow enough to trip the deadlock detector. That was behind the duplicate-span flake intest_single_agent_telemetryon the macOS CI runner.RequestInputids, node-tool call ids, and retry jitter throughgoogle.adk.platform; without that the fix above doesn't reach those call sites. The old git pin to ADK main is gone. 2.8.0 sits inside our two-weekexclude-newerwindow until Sept 10, so there's a temporaryexclude-newer-packageline in pyproject that I'll drop before merge.Testing
16 new tests (graph, dynamic, HITL) with
max_cached_workflows=0so every task replays, plus two recorded histories in the replay test. 71 passed, 5 skipped (env-gated) on 2.8.0. Lint clean.