Skip to content

Commit 51775f4

Browse files
authored
fix: agent confirmation flow delegates to caller_may_confirm (2/5)
No behaviour change on this path; the gate keeps rewriting refused confirmations to confirmed=False and now shares the decision with the tool node.
1 parent 0fc5ab2 commit 51775f4

1 file changed

Lines changed: 8 additions & 48 deletions

File tree

‎src/google/adk/flows/llm_flows/tools/_confirmation.py‎

Lines changed: 8 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@
2222
from google.genai import types
2323
from typing_extensions import override
2424

25+
from ....agents._caller_principal import caller_may_confirm
2526
from ....agents.invocation_context import InvocationContext
2627
from ....agents.readonly_context import ReadonlyContext
2728
from ....events.event import Event
28-
from ....features import FeatureName
29-
from ....features import is_feature_enabled
3029
from ....models.llm_request import LlmRequest
3130
from ....tools.base_tool import BaseTool
3231
from ....tools.tool_confirmation import ToolConfirmation
@@ -264,32 +263,12 @@ def _apply_caller_principal_gate(
264263
) -> dict[str, ToolConfirmation]:
265264
"""Decides whether the caller of this invocation may approve a tool call.
266265
267-
Three states, and only the middle one is a refusal:
268-
269-
- No principal: nothing vouched for the caller because nothing had to. The
270-
invocation was started in process, so the caller is the operator.
271-
- Principal present, not authenticated: a serving layer handled this request
272-
and could not say who sent it, so the approval is not known to be the
273-
operator's.
274-
- Principal present and authenticated: the serving layer verified the caller.
275-
276-
The question asked here is deliberately about authentication and not about
277-
transport. A transport is a proxy for identity, and a proxy for identity
278-
fails in both directions: it refuses authenticated peers that happen to
279-
arrive over the wire, and it admits anyone who reaches an ungated path.
280-
281-
A refusal rewrites the confirmation to ``confirmed=False`` instead of
282-
dropping it. Dropping it leaves the ``adk_request_confirmation`` call pending
283-
with nothing left to resolve it, which is what made an earlier attempt at
284-
this guard stall every human-in-the-loop tool. ``confirmed=False`` is a state
285-
the framework already has a contract for -- it is what a human decline
286-
produces -- so the tool returns its rejection response and the turn ends with
287-
a reason the caller can see.
288-
289-
Strict mode is the STRICT_CALLER_PRINCIPAL feature, off by default for now
290-
so that upgrading cannot break a working deployment that runs its A2A server
291-
without an authenticator; those get a warning instead. The intent is to flip
292-
the registry default at the next major version.
266+
The decision itself is ``caller_may_confirm``, shared with the workflow tool
267+
node so that both consumers of a human-in-the-loop answer refuse the same
268+
callers. A refusal rewrites each confirmation to ``confirmed=False`` instead
269+
of dropping it: dropping it leaves the ``adk_request_confirmation`` call
270+
pending with nothing left to resolve it, which is what made an earlier
271+
attempt at this guard stall every human-in-the-loop tool.
293272
294273
Args:
295274
invocation_context: Current invocation context.
@@ -299,27 +278,8 @@ def _apply_caller_principal_gate(
299278
The confirmations to act on, with refused ones forced to
300279
``confirmed=False``.
301280
"""
302-
principal = invocation_context.caller_principal
303-
if principal is None or principal.authenticated:
281+
if caller_may_confirm(invocation_context.caller_principal):
304282
return confirmations_by_fc_id
305-
306-
if not is_feature_enabled(FeatureName.STRICT_CALLER_PRINCIPAL):
307-
logger.warning(
308-
"Honoring a tool confirmation from an unauthenticated caller"
309-
" (principal source %r). The serving layer could not say who sent this"
310-
" approval, so it is not known to be the operator's. Enable the"
311-
" STRICT_CALLER_PRINCIPAL feature to refuse these instead; that is"
312-
" intended to become the default in a future major version.",
313-
principal.source,
314-
)
315-
return confirmations_by_fc_id
316-
317-
logger.error(
318-
"Refusing a tool confirmation from an unauthenticated caller (principal"
319-
" source %r). Enable authentication on the serving layer, or disable the"
320-
" STRICT_CALLER_PRINCIPAL feature to downgrade this to a warning.",
321-
principal.source,
322-
)
323283
return {
324284
confirmation_fc_id: confirmation.model_copy(update={"confirmed": False})
325285
for confirmation_fc_id, confirmation in confirmations_by_fc_id.items()

0 commit comments

Comments
 (0)