Repository navigation
Let an agent ask for tool approval in more than one step - #74686
Merged
Merged
Conversation
kaxil
added this pull request to stack #74687
October 14, 2026 02:59
This was referenced Oct 14, 2026
On Airflow 3.4 and later, an AgentOperator run pauses for approval at every step that calls a marked tool, instead of failing the task on its second request. A real agent decides each gated call after it sees the previous result, so the once-per-Dag-run rule failed it on its second approval. The rule existed because Airflow 3.3 keeps the first request's subject and body on a repeat request. Airflow 3.4 replaces them and clears the earlier answer (#73235), so a second pause shows the reviewer only its own calls. On 3.3 the rule stays. Core still accepts an answer without knowing which request it was for, so each request now carries its number in a hidden form field and in its subject. An answer that names an earlier request, from a form left open on that request, is not applied: the task asks about the waiting calls again. Each resume gets its own run_id, since pydantic-ai refuses a run_id already in the history, and keeps the transcript when the resumed run pauses again. Per-tool approval moves to the stable table.
kaxil
force-pushed
the
commonai-tool-approval-repeat
branch
from
October 14, 2026 07:35
474626c to
4599b4f
Compare
The test resumes two pauses but relied on the real gate, so it failed on the Airflow 3.3 compat job, where a second request is refused. Patch the gate open as the other multi-pause tests do; the decorator had been left on the stale-answer test above it.
vatsrahul1001
approved these changes
Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #74679.
Today an
AgentOperatortask can pause for tool approval only once per Dag run; a second request fails the task withToolApprovalAlreadyRequestedError. An agent decides its next call after it sees the last result, so a refund agent that approves order 1, reads the result and then asks to refund order 2 fails on the second ask.The limit came from core: on Airflow 3.3, a repeat approval request for the same task instance keeps the first request's subject and body, so the reviewer would approve a new call while reading the old one. #73235 (in 3.4) makes a repeat request replace those details and clear the earlier answer. On Airflow 3.4 and later, this PR lets the run pause at every step that calls a marked tool. On 3.3 the rule stays, with an error message that names the upgrade.
Run end to end on Airflow main with a scripted agent that refunds three orders, one per step: the task paused three times in one try, each Required Actions request showed only its own order, each refund ran once, and the task returned all three results.
Design rationale
A stale answer can still reach a later request, so each request is numbered. Core accepts an answer without knowing which request it was for. If one reviewer approves request 1 and the agent pauses for request 2, a second reviewer who still has request 1's form open could approve refund 2 while reading refund 1. Each request now carries its number in its subject and in a hidden
constform field, which the review form sends back with the answer. An answer naming an earlier request is not applied: the task asks about the waiting calls again under a new number. A REST API answer without the field is applied to the waiting request, so existing integrations keep working. In the end-to-end run, a deliberately stale answer for request 1 while request 2 was waiting produced request 3 for the same order, and nothing ran twice. A core change that ties every answer to its request revision is the proper fix for all HITL operators; this guard can go once that ships.Why gate on Airflow 3.4 rather than detect the core behaviour? The behaviour is a property of the core version,
AIRFLOW_V_3_4_PLUSis already in thecommon.compatfloor, and #73235 was not backported to a 3.3 release.Each resumed run gets its own
run_id,<ti id>-resumed-<n>. pydantic-ai refuses arun_idthat already appears in the history, so a second resume with the old fixed<ti id>-resumedfailed.Gotchas
usage_limitscounts across every pause, so a run with many gated steps can reachrequest_limit(50 by default) partway through.run_idXCom of a resumed run changes from<ti id>-resumedto<ti id>-resumed-<n>.Per-tool approval moves to the stable table in
stability.rst, since the once-per-run limit was the reason #74679 keeps it experimental.