-
Notifications
You must be signed in to change notification settings - Fork 54
feat: add HITL approval gateway for production ADK agent workflows #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
garythomasgeorge
wants to merge
3
commits into
google:main
Choose a base branch
from
garythomasgeorge:feat/hitl-approval-gateway
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| ## Summary | ||
| Closes #[ISSUE NUMBER] | ||
| Adds a production-ready Human-in-the-Loop approval gateway for Google ADK agents. This addresses a documented gap where ADK's built-in Tool Confirmation feature explicitly does not support `DatabaseSessionService` or `VertexAiSessionService` — the two session backends required for production deployments — making structured human oversight unavailable in any persistent production environment. | ||
|
|
||
| ## Problem | ||
| ADK's Tool Confirmation (v1.14.0+) is experimental and has three blockers for production use: | ||
| 1. Does not support `DatabaseSessionService` or `VertexAiSessionService` | ||
| 2. Does not trigger inside `AgentTool` or across A2A boundaries | ||
| 3. No structured approval UI, audit trail, or persistence layer | ||
|
|
||
| Validated by community issues: #1797, #1851, #2645, #3276, #3567 on `google/adk-python`. | ||
|
|
||
| ## Solution | ||
| A session-agnostic HITL approval gateway that manages approval state in its own persistence layer (SQLite, with a documented path to Postgres), independent of ADK's session service. The agent resumes via ADK's standard REST API after a human decision is submitted. | ||
|
|
||
| ### What's included | ||
|
|
||
| **Core module** (`src/google/adk_community/tools/hitl/`) | ||
| - `gateway.py` — `hitl_tool` decorator that wraps any async function before it is passed to `FunctionTool`. Adding HITL to an existing tool takes ~5 lines. | ||
| - `models.py` — `ApprovalRequest` Pydantic model, normalised data contract capturing agent context, payload, risk level, and audit metadata | ||
| - `adapters/adk1.py` — ADK 1.x adapter translating `request_confirmation()` events into `ApprovalRequest` objects | ||
|
|
||
| **Service** (`src/google/adk_community/services/hitl_approval/`) | ||
| - `api.py` — FastAPI application | ||
| - `routes.py` — REST endpoints for approval queue management | ||
| - `store.py` — SQLite persistence with full audit log | ||
|
|
||
| **Sample** (`contributing/samples/hitl_approval/`) | ||
| - `credit_agent/agent.py` — Credit approval agent demonstrating end-to-end integration | ||
| - `dashboard/app.py` — Reference Streamlit approval inbox UI | ||
| - `start_servers.sh` — One-command startup for all three services | ||
| - `requirements.txt` — Sample-only dependencies | ||
|
|
||
| ### Architecture | ||
| ``` | ||
| ADK Agent Pipeline | ||
| ↓ | ||
| @hitl_tool decorator (wraps async function → FunctionTool) | ||
| ↓ POST /approvals/ — creates ApprovalRequest | ||
| FastAPI + SQLite (approval state) | ||
| ↓ serves pending approvals | ||
| Streamlit Dashboard (reviewer decides) | ||
| ↓ POST /approvals/{id}/decide | ||
| FastAPI updates status in SQLite | ||
| ↓ decorator polls GET /approvals/{id} every 2 s | ||
| Agent resumes execution (wrapper unblocks; runs tool if approved) | ||
| ``` | ||
|
|
||
| ### Forward compatibility | ||
| Built with an adapter pattern so the same approval backend and dashboard work with ADK 1.x today and ADK 2.0's `RequestInput` pattern when it reaches stable — without teams needing to rebuild their approval layer on upgrade. | ||
|
|
||
| ## Testing | ||
| ### Unit tests | ||
| All 11 tests passing: | ||
| ```text | ||
| ============================= test session starts ============================= | ||
| platform darwin -- Python 3.11.15, pytest-9.0.2, pluggy-1.6.0 | ||
| rootdir: /Users/garythomasgeorge/Desktop/Work/AI Dev/adk-python-community | ||
| configfile: pyproject.toml | ||
| plugins: anyio-4.12.1, asyncio-1.3.0 | ||
| asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function | ||
| collected 11 items | ||
|
|
||
| tests/unittests/tools/test_hitl_gateway.py ...... [ 54%] | ||
| tests/unittests/services/test_hitl_approval_api.py ..... [100%] | ||
|
|
||
| ================================ 11 passed in 1.76s ================================= | ||
| Exit code: 0 | ||
| ``` | ||
|
|
||
| ### Manual E2E | ||
| Full end-to-end flow verified: | ||
| - Agent triggers approval request → appears in Streamlit dashboard ✓ | ||
| - Reviewer approves in dashboard → agent resumes correctly ✓ | ||
| - Uvicorn restart → SQLite persists previous approvals ✓ | ||
|
|
||
| > 🎥 *Please drag-and-drop your `hitl_demo_video_1774318429041.webp` file here before publishing* | ||
|
|
||
| ## Testing plan | ||
| For reviewers wanting to reproduce locally: | ||
| ```bash | ||
| cd contributing/samples/hitl_approval | ||
| uv pip install -r requirements.txt | ||
| ./start_servers.sh | ||
| ``` | ||
|
|
||
| Then open: | ||
| - ADK Dev UI: `http://localhost:8080` | ||
| - Streamlit dashboard: `http://localhost:8501` | ||
| - FastAPI docs: `http://localhost:8000/docs` | ||
|
|
||
| Trigger an approval by asking the credit agent to process an amount over $500. | ||
|
|
||
| ## Notes for reviewers | ||
| - Opening as **Draft** — happy to address structural feedback before requesting full review | ||
| - ADK 2.0 adapter (`adapters/adk2.py`) is planned as a follow-up PR once 2.0 moves toward stable | ||
| - Confirmed structure placement from proposal issue: `tools/hitl` for the gateway and models, `services/hitl_approval` for the FastAPI backend — let me know if you'd prefer a different organisation | ||
|
|
||
| ## Related | ||
| - Proposal issue: #[ISSUE NUMBER] | ||
| - ADK Tool Confirmation docs (known limitations): https://google.github.io/adk-docs/tools-custom/confirmation/ | ||
| - ADK multi-agent HITL pattern reference: https://developers.googleblog.com/developers-guide-to-multi-agent-patterns-in-adk/ | ||
| - Existing community example this extends: https://github.com/jackwotherspoon/adk-human-in-the-loop |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| # ADK HITL Approval Dashboard | ||
|
|
||
| A drop-in **production-ready Human-in-the-Loop (HITL) approval middleware** for Google Agent Development Kit (ADK) agents — complete with an API backend and a demo Streamlit dashboard UI. | ||
|
|
||
| ## The Problem Solved | ||
|
|
||
| ADK 1.x ships with an experimental `require_confirmation=True` feature that handles pausing the LLM loop for human verification. However, it is fundamentally built for local debugging and introduces major blockers to an enterprise environment: | ||
|
|
||
| 1. **Incompatible with Persistent Sessions:** Native confirmations intentionally do not serialize well and will completely fail to resume your agent if you use `DatabaseSessionService`, `SpannerSessionService`, or `VertexAiSessionService` (the mandatory session backends for production deployments). | ||
| 2. **Single-Agent Limitations:** They silently break across `AgentTool` nested bounds and true multi-agent (A2A) topologies, causing missing events or infinitely looping models. | ||
| 3. **No Resilient Audit Log:** The native confirmation tool leaves no easily queryable paper trail linking the human supervisor to a precise LLM request. | ||
|
|
||
| *This project is the production implementation of the HITL pattern covered in the [ADK Multi-Agent Patterns Guide (Advent of Agents Day 13)](https://medium.com/@garythomasgeorge/why-google-adks-human-in-the-loop-story-has-a-production-gap-and-one-way-it-could-be-fixed-66aabef33a32).* | ||
|
|
||
| ## What This Library Provides | ||
|
|
||
| This project solves the production gaps by explicitly decoupling the human approval payload from ADK's internal session memory. It introduces a session-agnostic REST API layer using an Adapter pattern. | ||
|
|
||
| ### The 3-Layer Architecture | ||
|
|
||
| ``` | ||
| ┌─────────────────────────────────────────┐ | ||
| │ Dashboard UI (Streamlit) │ Layer 3: Demo/reference UI | ||
| │ Approval inbox, audit log viewer │ (Easily replaced by Zendesk/etc.) | ||
| └──────────────────┬──────────────────────┘ | ||
| │ | ||
| ┌──────────────────▼──────────────────────┐ | ||
| │ ApprovalRequest Model (Pydantic) │ Layer 2: Normalised Contract API | ||
| │ FastAPI backend + SQLite store │ Session-agnostic persistence | ||
| └────────────────┬────────────────────────┘ | ||
| │ | ||
| ┌──────────┴───────────┐ | ||
| ┌─────▼──────┐ ┌──────────▼──────┐ | ||
| │ ADK 1.x │ │ ADK 2.0 │ Layer 1: Adapters | ||
| │ Adapter │ │ Adapter │ Only this changes between versions | ||
| └────────────┘ └─────────────────┘ | ||
| ``` | ||
|
|
||
| By retaining HITL state inside an independent FastAPI engine and SQLite database, an active agent can pause safely. When a human supervisor hits "Approve" inside a centralized web portal hours later, the middleware simply posts the decision back into the agent's `/run_sse` stream seamlessly. | ||
|
|
||
| ## Configuration | ||
|
|
||
| | Environment Variable | Default | Description | | ||
| |---|---|---| | ||
| | `ADK_HITL_API_URL` | `http://localhost:8000` | URL of the HITL approval FastAPI backend. Override for Cloud Run or any remote deployment. | | ||
| | `ADK_HITL_POLL_INTERVAL_S` | `2.0` | Base polling interval in seconds. Up to 1s of random jitter is added automatically to reduce backend traffic under concurrent load. | | ||
|
|
||
| Set these before starting the gateway: | ||
|
|
||
| ```bash | ||
| export ADK_HITL_API_URL="https://your-hitl-service.run.app" | ||
| export ADK_HITL_POLL_INTERVAL_S="3.0" | ||
| ``` | ||
|
|
||
| ## Quick Start (Local Sandbox) | ||
|
|
||
| We have provided a demo customer service agent (`credit_agent`) alongside a launch script to test the interaction end-to-end. | ||
|
|
||
| 1. Create your Python virtual environment and sync dependencies using `uv` (requires Python 3.11+): | ||
|
|
||
| ```bash | ||
| uv venv --python "python3.11" ".venv" | ||
| source .venv/bin/activate | ||
| uv sync --all-extras | ||
| ``` | ||
|
|
||
| 2. Start the FastAPI backend, Streamlit dashboard, and ADK Live Chat agent all at once: | ||
|
|
||
| ```bash | ||
| ./start_servers.sh | ||
| ``` | ||
|
|
||
| 3. Open `http://localhost:8080` to chat with the agent and ask for a $75 account credit. | ||
| 4. When the agent pauses and asks for a supervisor, open `http://localhost:8501` to approve or reject the request. | ||
|
|
||
| ## How to Use in Your Own ADK Application | ||
|
|
||
| Wrapping an ADK agent with a formal enterprise HITL checkpoint takes under 5 lines of code: | ||
|
|
||
| 1. Import the `hitl_tool` gateway wrapper. | ||
| 2. Decorate your function tool. | ||
| 3. Attach it to your ADK Agent initialization using a standard `FunctionTool`. | ||
|
|
||
| ```python | ||
| from google.adk.tools import FunctionTool | ||
| from google.adk_community.tools.hitl.gateway import hitl_tool | ||
|
|
||
| # 1. Wrap your function with the decorator | ||
| @hitl_tool(agent_name="my_billing_agent") | ||
| async def issue_refund(user_id: str, amount: float): | ||
| # This block won't execute until explicitly approved in the dashboard | ||
| return {"status": "success", "amount_refunded": amount} | ||
|
|
||
| # 2. Attach to ADK Agent | ||
| root_agent = Agent( | ||
| name="my_billing_agent", | ||
| tools=[FunctionTool(issue_refund)] | ||
| ) | ||
| ``` | ||
|
|
||
| ## Production Integration Strategies | ||
|
|
||
| This repository acts as the production baseline for a contact center or enterprise orchestration grid. Once deployed to staging, consider swapping out: | ||
|
|
||
| - **Storage Layer:** Replace the local `SQLite` engine in `app/api/store.py` with `PostgreSQL` or `Cloud Spanner`. | ||
| - **Proactive Notification:** Hook the FastAPI `POST /approvals/` route into Slack, PagerDuty, or Microsoft Teams to actively ping channels when a high-risk request pops up. | ||
| - **Remove Streamlit:** Bypass the Streamlit frontend completely and point your existing support portal interface (like Salesforce Service Cloud) directly to `GET /approvals/pending` and `POST /approvals/{id}/decide`. | ||
|
|
||
| ## ADK 2.0 Compatibility | ||
|
|
||
| This project currently uses ADK 1.x conventions and event triggers. Because it strictly implements an `adapters` layer, all the Pydantic API schemas and Streamlit logic are completely forward-compatible with ADK 2.0 `RequestInput` workflow yielding. You'll simply need to switch the adapter layer translation once ADK 2.0 exits Alpha. The `ADK_HITL_API_URL` and `ADK_HITL_POLL_INTERVAL_S` environment variables remain valid across both adapter versions. |
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from . import agent |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """Credit agent — external supervisor HITL demo. | ||
|
|
||
| This agent demonstrates the cross-user approval pattern: | ||
| - Customer chats in ADK web (:8080) | ||
| - Agent wants to apply a credit → submits request to HITL API (:8000) | ||
| - Agent blocks (non-blocking async poll) waiting for a decision | ||
| - Supervisor opens Streamlit dashboard (:8501), reviews and approves/rejects | ||
| - Agent resumes and informs the customer of the outcome | ||
|
|
||
| Make sure all three services are running before chatting (see start_servers.sh): | ||
| HITL API: uvicorn google.adk_community.services.hitl_approval.api:app --port 8000 | ||
| Dashboard: streamlit run dashboard/app.py --server.headless true | ||
| ADK web: adk web credit_agent/ --port 8080 | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| import sys | ||
|
|
||
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../..")) | ||
|
|
||
| from google.adk.agents import Agent | ||
| from google.adk.tools import FunctionTool | ||
|
|
||
| from google.adk_community.tools.hitl.gateway import hitl_tool | ||
|
|
||
|
|
||
| @hitl_tool(agent_name="credit_agent") | ||
| async def apply_account_credit(account_id: str, amount: float, reason: str) -> dict: | ||
| """Apply a credit to a customer account. Requires supervisor approval. | ||
|
|
||
| Args: | ||
| account_id: The customer account ID to credit. | ||
| amount: Credit amount in USD. | ||
| reason: Business justification for the credit. | ||
|
|
||
| Returns: | ||
| Confirmation with the updated account balance. | ||
| """ | ||
| # Real implementation would call your billing/CRM API here | ||
| return { | ||
| "status": "credited", | ||
| "account_id": account_id, | ||
| "amount_credited": amount, | ||
| "new_balance": f"${amount:.2f} credit applied successfully.", | ||
| } | ||
|
|
||
|
|
||
| root_agent = Agent( | ||
| name="credit_agent", | ||
| model="gemini-2.5-flash", | ||
| description=( | ||
| "Customer support agent that can apply account credits. " | ||
| "Every credit requires supervisor approval via the HITL dashboard." | ||
| ), | ||
| instruction=( | ||
| "You are a customer support agent. When a customer requests an account credit, " | ||
| "call apply_account_credit with their account ID, the amount, and the reason. " | ||
| "Let them know their request is being reviewed by a supervisor and that you will " | ||
| "update them once a decision is made. " | ||
| "If the credit is approved, confirm it to the customer. " | ||
| "If rejected, apologise and explain that the supervisor did not approve it." | ||
| ), | ||
| tools=[FunctionTool(apply_account_credit)], | ||
| ) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to check in the .adk folder?