From 84db6e567b6eaec7122e33e921152c2405a52e94 Mon Sep 17 00:00:00 2001 From: Vivien Ramahandry <56304555+vramahandry@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:59:13 +0200 Subject: [PATCH] fix(kagent-adk): surface the sub-agent's ask_user question in remote_hitl_hint remote_hitl_hint() in the Python kagent-adk runtime only ever listed the paused tool's name (e.g. "ask_user"), never the actual question text, even though AskUserRequest.questions already carries it (set directly on the request in build_hitl_status_message, whether or not nested is populated). A human relaying a bubbled-up sub-agent HITL pause saw "requires approval for tool(s): ask_user" with no way to know what was actually being asked. This mirrors the same fix already made in go/adk/pkg/a2a/hitl.go (#2475) for the Go runtime's RemoteHitlHint(); the two runtimes maintain independent implementations of this hint logic and the Python side never had question-surfacing added. Fixes #2473 Signed-off-by: Vivien Ramahandry <56304555+vramahandry@users.noreply.github.com> --- .../kagent-adk/src/kagent/adk/_hitl.py | 13 ++++- .../kagent-adk/tests/unittests/test_hitl.py | 50 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/python/packages/kagent-adk/src/kagent/adk/_hitl.py b/python/packages/kagent-adk/src/kagent/adk/_hitl.py index e8bdcfc62..8f5efbc7a 100644 --- a/python/packages/kagent-adk/src/kagent/adk/_hitl.py +++ b/python/packages/kagent-adk/src/kagent/adk/_hitl.py @@ -104,7 +104,18 @@ def visible_tools(request: ToolApprovalRequest | AskUserRequest) -> list[HitlToo def remote_hitl_hint(state: RemoteHitlState) -> str: """Build the parent confirmation hint for a paused child task.""" - names = [tool.name for tool in visible_tools(state.hitl_request)] + request = state.hitl_request + if isinstance(request, AskUserRequest): + # questions carries the real text whether or not nested is set (see + # build_hitl_status_message), so prefer it over the bare tool name. + question_text = " ".join( + question["question"] + for question in request.questions + if isinstance(question.get("question"), str) and question["question"] + ) + if question_text: + return f"Remote agent '{state.subagent_name}' asks: {question_text}" + names = [tool.name for tool in visible_tools(request)] if names: return f"Remote agent '{state.subagent_name}' requires approval for tool(s): {', '.join(names)}" return f"Remote agent '{state.subagent_name}' requires human input before continuing." diff --git a/python/packages/kagent-adk/tests/unittests/test_hitl.py b/python/packages/kagent-adk/tests/unittests/test_hitl.py index 2ff15b6a5..b24843647 100644 --- a/python/packages/kagent-adk/tests/unittests/test_hitl.py +++ b/python/packages/kagent-adk/tests/unittests/test_hitl.py @@ -27,7 +27,9 @@ from kagent.adk._hitl import ( RemoteHitlState, build_hitl_status_message, + build_remote_hitl_state, build_resume_hitl_message, + remote_hitl_hint, ) @@ -285,6 +287,54 @@ def test_resume_rejects_non_input_required_task(): ) +def test_remote_hitl_hint_tool_approval(): + task = _stored_task(ToolApprovalRequest(tools=[_tool("child-confirm", "delete_pod")])) + state = build_remote_hitl_state(task, "k8s_agent") + + assert state is not None + assert remote_hitl_hint(state) == "Remote agent 'k8s_agent' requires approval for tool(s): delete_pod" + + +def test_remote_hitl_hint_ask_user(): + task = _stored_task( + AskUserRequest(id="confirm-1", questions=[{"question": "What is the GitHub owner/org for the repo?"}]) + ) + state = build_remote_hitl_state(task, "github_agent") + + assert state is not None + assert remote_hitl_hint(state) == "Remote agent 'github_agent' asks: What is the GitHub owner/org for the repo?" + + +def test_remote_hitl_hint_ask_user_nested(): + """A two-level nested ask_user pause should also surface the real question + from the top-level questions field, not just the bare 'ask_user' tool name.""" + question = "What is the GitHub owner/org for the repo?" + task = _stored_task( + AskUserRequest( + id="confirm-1", + questions=[{"question": question}], + nested=NestedHitlRequest( + subagent_name="grandchild_agent", + task_id="grandchild-task", + context_id="grandchild-context", + tools=[ + HitlTool( + id="confirm-2", + call_id="confirm-2", + name="ask_user", + args={"questions": [{"question": question}]}, + ) + ], + ), + ) + ) + state = build_remote_hitl_state(task, "github_agent") + + assert state is not None + assert state.hitl_request.nested is not None + assert remote_hitl_hint(state) == f"Remote agent 'github_agent' asks: {question}" + + def test_resume_rejects_input_required_task_without_public_hitl_request(): task = Task( id="task-1",