Description
In the Python core, a tool configured with approval_mode="always_require" can be silently bypassed (executed or returned as user input without surfacing an approval request) when another call precedes it in the same model tool-call batch. The approval outcome depends on the position of the call within the batch rather than on the call itself.
Root cause
_try_execute_function_call_groups in python/packages/core/agent_framework/_tools.py classifies the batch with a loop that stops at the first matching call:
for function_call in actionable_calls:
function_name = function_call.name
if function_name in approval_tool_names:
requires_approval = True
break
if function_name in declaration_only_tool_names or function_name in additional_tool_names:
has_declaration_only_call = True
break
if config.get("terminate_on_unknown_calls", False) and function_name not in tool_map:
raise KeyError(f'Error: Requested function "{function_name}" not found.')
Because each branch breaks (or raises) at the first hit, classification travels with the call's position, not with the call:
- A declaration-only call appearing before an
always_require call makes the whole batch classified as declaration-only user input, so the approval gate is never surfaced.
- With
terminate_on_unknown_calls=True, an unknown call appearing before an always_require call raises KeyError first, so the higher-priority approval pause is never reached.
Reproduction
Model returns one batch with two calls where a declaration-only (or unknown) call is ordered first and an always_require tool is second. Expected: an approval request is surfaced for the always_require tool. Actual: no approval request; the batch is returned as user input (declaration-first) or terminated with KeyError (unknown-first).
Expected invariant
Approval enforcement must be independent of call order within a batch. Classification should scan the whole batch and apply a fixed priority: approval pause > declaration-only user-input > unknown-call termination.
Scope
python/packages/core/agent_framework/_tools.py batch classification in _try_execute_function_call_groups. Verified present on current main.
Note: this is distinct from #6385 (which concerned the opposite direction, never_require tools being over-approved) and from #7881 (a concurrency-groups feature that retains the current order-dependent classification).
I'm working on a fix with regression tests and will open a PR shortly with Fixes #NNNN.
Description
In the Python core, a tool configured with
approval_mode="always_require"can be silently bypassed (executed or returned as user input without surfacing an approval request) when another call precedes it in the same model tool-call batch. The approval outcome depends on the position of the call within the batch rather than on the call itself.Root cause
_try_execute_function_call_groupsinpython/packages/core/agent_framework/_tools.pyclassifies the batch with a loop that stops at the first matching call:Because each branch
breaks (or raises) at the first hit, classification travels with the call's position, not with the call:always_requirecall makes the whole batch classified as declaration-only user input, so the approval gate is never surfaced.terminate_on_unknown_calls=True, an unknown call appearing before analways_requirecall raisesKeyErrorfirst, so the higher-priority approval pause is never reached.Reproduction
Model returns one batch with two calls where a declaration-only (or unknown) call is ordered first and an
always_requiretool is second. Expected: an approval request is surfaced for thealways_requiretool. Actual: no approval request; the batch is returned as user input (declaration-first) or terminated withKeyError(unknown-first).Expected invariant
Approval enforcement must be independent of call order within a batch. Classification should scan the whole batch and apply a fixed priority: approval pause > declaration-only user-input > unknown-call termination.
Scope
python/packages/core/agent_framework/_tools.pybatch classification in_try_execute_function_call_groups. Verified present on currentmain.Note: this is distinct from #6385 (which concerned the opposite direction,
never_requiretools being over-approved) and from #7881 (a concurrency-groups feature that retains the current order-dependent classification).I'm working on a fix with regression tests and will open a PR shortly with
Fixes #NNNN.