Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions contributing/samples/a2a/a2a_human_in_loop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ The A2A Human-in-the-Loop sample consists of:

```bash
# Start the remote a2a server that serves the human-in-the-loop approval agent on port 8001
adk api_server --a2a --port 8001 contributing/samples/a2a_human_in_loop/remote_a2a
adk api_server --a2a --port 8001 contributing/samples/a2a/a2a_human_in_loop/remote_a2a
```

1. **Run the Main Agent**:
2. **Run the Main Agent**:

```bash
# In a separate terminal, run the adk web server
adk web contributing/samples/
adk web contributing/samples/a2a
```

### Example Interactions
Expand All @@ -82,10 +82,24 @@ Agent: ✅ Reimbursement approved and processed: $50 for meals
User: Please reimburse $200 for conference travel
Agent: I'll process your reimbursement request for $200 for conference travel. Since this amount exceeds $100, I need to get manager approval.
Agent: 🔄 Request submitted for approval (Ticket: reimbursement-ticket-001). Please wait for manager review.
[Human manager interacts with root agent to approve the request]
[Human manager approves the pending request from the ADK Web UI]
Agent: ✅ Great news! Your reimbursement has been approved by the manager. Processing $200 for conference travel.
```

> **Approving from the ADK Web UI:** The approval is a *long-running tool* call
> that runs on the remote approval agent. The pending call is surfaced in the
> Web UI as a function call awaiting a response. To approve (or reject), hover
> over the pending `ask_for_approval` function response in the UI and use
> **"Send another response"** to send back an updated response such as
> `{"status": "approved", "ticketId": "reimbursement-ticket-001"}`. Simply
> typing "I approve" as a chat message will **not** resume the pending request,
> because the framework needs a `FunctionResponse` that carries the same call
> `id` to resume the long-running tool.
>
> For this resume to be routed back to the remote approval agent (rather than
> restarting at the root agent), the sample is exposed as an `App` with
> `ResumabilityConfig(is_resumable=True)` in `agent.py`.

## Code Structure

### Main Agent (`agent.py`)
Expand Down
17 changes: 17 additions & 0 deletions contributing/samples/a2a/a2a_human_in_loop/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from google.adk.agents.llm_agent import Agent
from google.adk.agents.remote_a2a_agent import AGENT_CARD_WELL_KNOWN_PATH
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
from google.adk.apps import App
from google.adk.apps import ResumabilityConfig
from google.genai import types


Expand Down Expand Up @@ -49,3 +51,18 @@ def reimburse(purpose: str, amount: float) -> str:
sub_agents=[approval_agent],
generate_content_config=types.GenerateContentConfig(temperature=0.1),
)

# The human-in-the-loop approval runs as a long-running tool on the remote
# approval_agent. When the manager approves (or rejects) the request, the ADK
# Web UI sends back a FunctionResponse for that pending long-running call. For
# the next turn to be routed back to the (remote) approval_agent so it can
# resume the paused tool instead of restarting at the root reimbursement_agent,
# the app must be resumable. Without this, the confirmation is delivered to the
# root agent, which has no pending call, and nothing happens (see issue #5871).
app = App(
name='a2a_human_in_loop',
root_agent=root_agent,
resumability_config=ResumabilityConfig(
is_resumable=True,
),
)