Skip to content

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

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

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

Conversation

@DABH

@DABH DABH commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Draft PR body update for #1675

The current body is stale after merging main (activity_tool → activity_as_tool rename)
and after de-gating the random provider + pinning google-adk to upstream main.
Suggested replacement body below the line.


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_as_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_as_tool: FunctionTool(func=activity_as_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 time, uuid, and random providers as ContextVar defaults, unconditionally. With the ids and retry jitter routed through google.adk.platform (see below), default RequestInput interrupt ids, _ToolNode function-call ids, and node-retry jitter all derive from workflow.uuid4() / workflow.random() and replay deterministically — no explicit interrupt_id plumbing required.
  • 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).
  • Temporary dev pin to ADK main: the seams the provider fix relies on merged upstream after 2.7.1 shipped, so [tool.uv.sources] pins google-adk to google/adk-python@8f85107c (lockfile-only; published metadata keeps the normal version range). Remove the pin and bump the floor once the first ADK release containing that commit ships.
  • README sections: graph workflows, dynamic workflows, Workflow-as-Tool, durable HITL pattern, determinism notes. CHANGELOG entry under Unreleased.

Upstream dependencies (merged)

Two adk-python PRs route the remaining seam-bypassing call sites through google.adk.platform; both are now on ADK main and included in the pinned rev:

Neither is in a PyPI release yet (2.7.0/2.7.1 predate them), hence the [tool.uv.sources] git pin. There is no version-gated code: the plugin installs all three providers unconditionally, and the previously seam-gated tests now run unconditionally.

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_as_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 landed separately in [AI-163] google_adk_agents: support ToolContext session state in activity_tool #1683)
  2. How was this tested: 18 new integration test cases in tests/contrib/google_adk_agents/ (graph sequential/routing/parallel-join/multi-param/agent-node/timeout/retry/jittered-retry-replay; 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, default-interrupt-id replay safety) — all workers run with max_cached_workflows=0 so every workflow task fully replays; jittered-retry and default-interrupt-id tests also re-verified via repeated runs; 2 new recorded replay histories (which replay green against the pinned ADK main, i.e. no history re-recording needed). Full tests/contrib/google_adk_agents suite against ADK main: 59 passed, 5 skipped (pre-existing: missing GOOGLE_API_KEY / MCP tests marked skip in CI). poe lint (ruff, pyright, mypy, basedpyright, pydocstyle) clean.
  3. Any docs updates needed: contrib README updated in this PR.

@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
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