Skip to content

[AI-303] google_adk_agents: graph workflows, dynamic workflows, and durable HITL (ADK v2) - #1675

Open
DABH wants to merge 12 commits into
mainfrom
google-adk-v2-graph-hitl
Open

[AI-303] google_adk_agents: graph workflows, dynamic workflows, and durable HITL (ADK v2)#1675
DABH wants to merge 12 commits into
mainfrom
google-adk-v2-graph-hitl

Conversation

@DABH

@DABH DABH commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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, and activity_as_tool intercepting as before. This PR adds the missing pieces (AI-303):

  • activity_node(...): run a Temporal activity as an ADK FunctionNode, either in a Workflow(edges=...) graph or via ctx.run_node(...).
  • HITL helpers HitlRequest, pending_hitl_requests, hitl_input_response, hitl_confirmation_response for ADK's adk_request_input / adk_request_confirmation pause-and-resume protocol. The durable wait itself is just workflow code: query the pending requests, wait on a signal or update, call run_async again. Works with activity_as_tool behind FunctionTool(require_confirmation=True).
  • Bug fix: the plugin's ADK time/uuid providers were never active inside workflows. ADK keeps them in ContextVars, and workflow code runs on executor threads that never saw the set_*_provider calls. 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.
  • google-adk floor bumped to 2.8.0. It's the first release with fix: route workflow IDs and retry jitter through platform seams google/adk-python#6468, which routes RequestInput ids, node-tool call ids, and retry jitter through google.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-week exclude-newer window until Sept 10, so there's a temporary exclude-newer-package line in pyproject that I'll drop before merge.
  • README sections and CHANGELOG entries.

Testing

16 new tests (graph, dynamic, HITL) with max_cached_workflows=0 so every task replays, plus two recorded histories in the replay test. 71 passed, 5 skipped (env-gated) on 2.8.0. Lint clean.

@DABH
DABH force-pushed the google-adk-v2-graph-hitl branch from 3d4be86 to 697e407 Compare July 24, 2026 23:15
DABH added 4 commits July 27, 2026 15:40
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.
@DABH
DABH force-pushed the google-adk-v2-graph-hitl branch from 697e407 to 41276f8 Compare July 27, 2026 20:42
…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.
@DABH
DABH force-pushed the google-adk-v2-graph-hitl branch from 41276f8 to 51dc7a5 Compare July 27, 2026 20:59
@DABH DABH changed the title google_adk_agents: graph workflows, dynamic workflows, and durable HITL (ADK v2) [AI-303] google_adk_agents: graph workflows, dynamic workflows, and durable HITL (ADK v2) Aug 14, 2026
DABH added 6 commits August 17, 2026 19:28
# 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.
@DABH
DABH marked this pull request as ready for review September 4, 2026 07:06
@DABH
DABH requested review from a team as code owners September 4, 2026 07:06

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

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_node and 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.

Comment thread pyproject.toml
Comment on lines +287 to +288
# 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" }
Comment on lines +294 to +299
params = [
p
for p in sig.parameters.values()
if p.kind
in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
]
] = {
_REQUEST_INPUT_FUNCTION_CALL_NAME: "input",
_REQUEST_CONFIRMATION_FUNCTION_CALL_NAME: "tool_confirmation",
_REQUEST_CREDENTIAL_FUNCTION_CALL_NAME: "credential",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

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.

3 participants