google / google/adk-python

RequestInput with constant interrupt_id breaks dev-ui input prompt on @node(rerun_on_resume=True) loops

Open
#5,617 6 comments 0 reactions 2 assignees Claimed by @wyf7107 View on GitHub
needs review request clarification v2 web
Dominant language
Python
Stars
21.5k
Forks
4k
Avg merge
1d 14h
Merged PRs (30d)
37

Description

## Summary
When a `@node(rerun_on_resume=True)` node yields `RequestInput` more than once across rerun loops using the **same** `interrupt_id` (as shown in the official `request_input_rerun` sample), the dev-ui fails to render the input prompt for the second and subsequent requests. The workflow appears stuck even though the backend is correctly waiting for input.

## Environment
- `google-adk` `2.0.0b1`
- Python 3.14
- macOS, `adk web` (built-in dev-ui)

## Reproduction
Use the official sample `request_input_rerun/agent.py` verbatim:

```python
@node(rerun_on_resume=True)
def human_review(draft: str, ctx: Context):
resume_input = ctx.resume_inputs.get("human_review")
if not resume_input:
yield RequestInput(
interrupt_id="human_review",
message="Please review ... 'approve', 'reject', or feedback to revise.\n\n---\n{draft}\n---",
)
return

if resume_input == "reject":
yield Event(route="rejected")
elif resume_input == "approve":
yield Event(route="approved")
else:
yield Event(state={"feedback": resume_input}, route="revise")
```

Steps:
1. Start session with any complaint, e.g. `I received the wrong item`.
2. At the first `adk_request_input` prompt, type `Please add a more empathetic tone` (triggers `revise`).
3. Wait for the second draft + the second `adk_request_input` event.

## Expected
The dev-ui renders the inline `Enter your response...` input box for the second `adk_request_input`, allowing the user to submit `approve` / `reject` / more feedback.

## Actual
The second `adk_request_input` event shows up only as a collapsed badge — no message body, no input box. Typing into the main `Type a message...` box submits as a *new* user turn (#9) instead of resuming the interrupt, and the workflow restarts the graph from `process_input`.

## Root cause analysis
Both `RequestInput` events are persisted with the same `function_call.id == "human_review"`. The dev-ui treats a `function_call.id` as "already responded" once any matching `function_response` has been seen in the session, regardless of whether a *newer* `function_call` with the same id is now pending. So the second pending interrupt is silently classified as a historical/already-resolved request.

Notably, the framework itself does allow multiple interrupts from the same node when `rerun_on_resume=True` — `_node_runner.py` only raises when `not node.rerun_on_resume and node_interrupted`:
```python
if is_interrupt and (
data_event_count > 0
or (not node.rerun_on_resume and node_interrupted)
):
raise ValueError(...)
```
So the backend contract permits the loop, but the dev-ui contract does not.

## Workaround
Make `interrupt_id` unique per run by appending `ctx.run_id`:

```python
interrupt_id = f"human_review:{ctx.run_id}"
resume_input = ctx.resume_inputs.get(interrupt_id)
if not resume_input:
yield RequestInput(interrupt_id=interrupt_id, message=...)
return
```

With this change the full loop (`revise → approve → send_email`) works end-to-end.

## Suggested fixes (any one)
1. **Framework auto-disambiguation:** transparently append `run_id` / `attempt_count` to the user-supplied `interrupt_id` for `rerun_on_resume=True` nodes when persisting the event.
2. **Validation:** when `rerun_on_resume=True`, validate or warn that the `interrupt_id` was already used by a prior completed interrupt in the same node lineage.
3. **Dev-ui fix:** match `function_response` to the **most recent** `function_call` with that id, instead of marking the id as "responded forever".
4. **Docs/sample:** at minimum, update the `request_input_rerun` sample (and the "How To" code snippet) to use a unique `interrupt_id` per run, since the current sample reproduces the bug.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.