A human-in-the-loop Google ADK agent running durably on Temporal with the
googleadk contrib
integration. The agent has a sensitive delete_resource function tool that must
not run without a human's approval.
How it works:
- The model calls
delete_resource. On its first invocation the tool seesctx.ToolConfirmation() == nil, callsctx.RequestConfirmation("Delete …?", nil), and returns without doing the delete — so ADK pauses the agent. ApprovalWorkflowdetects the pause viagoogleadk.PendingConfirmationsand durably blocks on a Temporal signal (googleadk.ConfirmationSignalName, read withworkflow.GetSignalChannel) carrying agoogleadk.ConfirmationDecision.- When the decision arrives, the workflow resumes the run with
googleadk.ConfirmationResponse(decision). ADK re-dispatches the original tool call, which now sees a confirmation and performs the delete (or is blocked if denied).
This is the differentiator: the wait for the human is durable. The workflow can sit blocked for minutes or days and survive worker restarts — no state is lost. When the approval signal finally arrives, the agent resumes exactly where it paused.
delete_resourceruns in-workflow and only simulates the delete (it returns a status map) to keep the demo deterministic. A real destructive operation does I/O and must not run in the workflow — expose it withgoogleadk.ActivityAsToolso it runs worker-side under Temporal's retry/timeout policy. The confirmation gate is identical either way: the tool still callsctx.RequestConfirmation(...)before doing the work.- The workflow handles one pending confirmation per resume pass. This is also
the recommended pattern: as the
googleadk.ConfirmationResponsedocs note, resuming several decisions in one pass can re-dispatch the approved tool calls in an order that is not replay-stable, so when the confirmed tools dispatch Activities, answer one decision perRunpass (additional pending confirmations surface again on the next pass).
- A running Temporal server
(e.g.
temporal server start-dev). - A Gemini API key from https://aistudio.google.com/apikey, exported worker-side.
-
Start a Temporal server (see prerequisites).
-
In a second terminal, start the worker:
export GEMINI_API_KEY=...
go run googleadk/humanintheloop/worker/main.go- In a third terminal, run the starter:
go run googleadk/humanintheloop/starter/main.goThe starter asks the agent to delete a resource; the workflow pauses awaiting
approval, and the starter then sends an approval signal (via
client.SignalWorkflow) to demonstrate the resume. In a real system the signal
would come from an operator clicking "approve" in a UI, possibly much later.
workflow_test.go scripts the model to call delete_resource, uses
env.RegisterDelayedCallback to deliver the approval through the real Temporal
signal, and asserts the delete completes only after approval (plus a denial case).
No API key or network needed:
go test ./googleadk/humanintheloop/...