Skip to content

google_adk_agents: graph workflows, dynamic workflows, and durable HITL (ADK v2)#1675

Draft
DABH wants to merge 4 commits into
mainfrom
google-adk-v2-graph-hitl
Draft

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

Conversation

@DABH

@DABH DABH commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

What was changed

Adds ADK Python v2 feature support to temporalio.contrib.google_adk_agents (JIRA AI-303: graph workflows, dynamic workflows, and human-in-the-loop):

  • activity_node(activity_def, *, name=None, rerun_on_resume=False, **activity_config) (in .workflow, sibling of activity_tool) — wraps a Temporal activity as an ADK FunctionNode for use in Workflow(edges=[...]) graphs and dynamic ctx.run_node(...) calls. Single-param activities receive the upstream node's output directly; multi-param activities bind by name from a dict; local ADK runs call the function directly.
  • Durable HITL helpers (exported from the package): HitlRequest, pending_hitl_requests(event), hitl_input_response(...), hitl_confirmation_response(...) — cover ADK's pause/resume wire format (adk_request_input / adk_request_confirmation function calls answered by FunctionResponses) so a workflow can expose pending requests via a query, wait durably on a signal/update, and resume the runner. Tool confirmation composes with activity_tool: FunctionTool(func=activity_tool(fn, ...), require_confirmation=True) never schedules the activity until a human approves.
  • Fix: ADK platform providers were never active inside workflows. ADK holds its time/uuid providers in ContextVars; workflow code runs on executor threads whose contexts never saw the plugin's set_*_provider calls, so generated ids were stdlib uuid4 and Event timestamps wall-clock all along (masked because replay compares command sequences, not payloads — HITL makes it fatal since resume matches recorded responses by generated id). setup_deterministic_runtime() now installs the providers as ContextVar defaults, and additionally installs a workflow.random()-backed provider on ADK builds that expose the new random seam.
  • google-adk floor → >=2.5.0,<3 (Workflow-as-Tool needs ≥2.4; standalone-node/NodeTool HITL resumption and resumable-mode hardening need 2.5). The exclude-newer-package exemption is removable after 2026-07-30.
  • README sections: graph workflows, dynamic workflows, Workflow-as-Tool, durable HITL pattern, determinism notes.

Upstream dependencies (built on the assumption they merge)

Two adk-python PRs route the remaining seam-bypassing call sites through google.adk.platform:

Two tests are seam-gated: they skip on stock 2.5.0 and pass against a build containing #6468 (verified locally via a branch install). Before merge, the floor should bump to the first ADK release containing them.

Why

ADK v2 (2.0–2.5) rebuilt the framework around a graph runtime; the integration predates it. The graph engine is pure-asyncio and session-event-driven, so it runs deterministically inside workflows with the existing activity seams (TemporalModel, MCP, activity_tool) intercepting unchanged — this PR adds the node-level helper, the HITL mapping, tests proving replay safety, and the provider fix that HITL surfaced.

Checklist

  1. Closes AI-303 (graph + dynamic workflows + HITL; ToolContext/AI-163 deliberately out of scope)
  2. How was this tested: 17 new integration tests in tests/contrib/google_adk_agents/ (graph sequential/routing/parallel-join/agent-node/timeout/retry; dynamic loop/gather/Workflow-as-Tool/resume-with-exactly-once-activity; HITL input-node resume via update, confirmation approve/reject with activity-execution counting, multi-pending partial responses) — run with max_cached_workflows=0 so every workflow task fully replays; 2 new recorded replay histories; full suite green on stock 2.5.0 (35 passed, 7 skipped incl. 2 seam-gated + 5 pre-existing) and on the #6468 branch (37 passed); pyright/mypy/ruff/pydocstyle clean.
  3. Any docs updates needed: contrib README updated in this PR.

DABH added 4 commits July 24, 2026 15:35
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 3d4be86 to 697e407 Compare July 24, 2026 23:15
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.

1 participant