Skip to content

Reject duplicate durable toolset ids and fail deferred tools with UnsupportedToolDeferralError - #74902

Merged
kaxil merged 2 commits into
apache:mainfrom
astronomer:durable-toolset-checks
Oct 17, 2026
Merged

kaxil merged 2 commits into
apache:mainfrom
astronomer:durable-toolset-checks

Conversation

@kaxil

@kaxil kaxil commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Found while testing common.ai 1.0.0rc1 (#74741). AgentOperator(durable=True) documents two behaviours it did not have.

Duplicate toolset ids. The 1.0.0 changelog and the durable execution page say every FunctionToolset, MCPToolset and DynamicToolset needs a unique id=, checked when the operator is created. Construction only checked that an id was present. Two toolsets sharing id="orders" loaded fine and then failed at run time with pydantic-ai's UserError: Two toolsets have the same id 'orders'. The operator now raises ValueError when it is created. It applies the same rule pydantic-ai's durable backend does (durable_exec/_base.py): only distinct instances of those three kinds collide, and one instance passed in two places counts once. The check covers the toolsets the agent actually runs, so agent_params["toolsets"] doesn't count when toolsets= replaces it.

A tool that defers under durable=True. The durable page says such a call fails the task with UnsupportedToolDeferralError. It actually failed with pydantic-ai's UserError: A deferred tool call was present, but DeferredToolRequests is not among output types. The operator added DeferredToolRequests to the output types only when approval was supported, so its own check was never reached. Durable runs now include it, so a deferral that ends the run fails the task with UnsupportedToolDeferralError and pushes the attempt's usage and run_id XComs, as other failed attempts do.

Why approval tools aren't refused when the Dag is parsed. A declared approval tool often never pauses a durable run. A HandleDeferredToolCalls judge can settle every call inline, conditional approval may not trigger, CodeMode reports the call back to the model, or the model may never call the tool. All of these ran to success on main, and a parse-time check would have broken them. The new tests cover the judge case under durable=True.

Without the source change, the new tests fail with DID NOT RAISE ValueError, KeyError: 'usage' and the UserError above.

… durable=True

AgentOperator(durable=True) checked only that each FunctionToolset, MCPToolset
and DynamicToolset had an id, so two toolsets with the same id passed
construction and failed at run time with pydantic-ai's "Two toolsets have the
same id" UserError. It now refuses them with ValueError when the Dag is parsed.

A tool needing approval under durable=True failed with pydantic-ai's
"DeferredToolRequests is not among output types" UserError, after the model
had been called, instead of the documented UnsupportedToolDeferralError.
Toolsets that declare approval (.approval_required(), requires_approval=True,
ExternalToolset) are now refused at construction, and a tool that raises
ApprovalRequired or CallDeferred itself ends the run and fails the task with
UnsupportedToolDeferralError.
Drop the parse-time refusal of approval and external tools under
durable=True. It rejected configurations that ran to success: a
HandleDeferredToolCalls judge that settles calls inline, conditional
approval that is not triggered, CodeMode, and marked tools the model never
calls. A deferral that does end the run still fails the task with
UnsupportedToolDeferralError.

The duplicate-id check now looks only at the toolsets the agent runs, so
agent_params["toolsets"] no longer counts when toolsets= replaces it. A
deferral that fails the task now pushes the attempt's usage and run_id
XComs, as other failed attempts do.
@kaxil kaxil changed the title Reject duplicate toolset ids and approval tools at construction under durable=True Reject duplicate durable toolset ids and fail deferred tools with UnsupportedToolDeferralError Oct 17, 2026
@kaxil
kaxil merged commit db1397a into apache:main Oct 17, 2026
86 checks passed
@kaxil
kaxil deleted the durable-toolset-checks branch October 17, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants