[AAASM-2923] 🐛 (adapters): Forward-compat tool interception for ADK 1.x & Pydantic AI >=0.3.0#119
Conversation
Tool._run was removed/restructured in Pydantic AI >=0.3.0, so the old patch raised AttributeError. Detect the available hook: Tool._run on <0.3.0, else AbstractToolset.call_tool on >=0.3.0. When neither exists, apply() no-ops and returns False instead of raising. revert() stays symmetric and idempotent for whichever class was patched. Refs AAASM-2923 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add offline fake-class tests modelling Pydantic AI >=0.3.0: a Tool with no _run plus an AbstractToolset.call_tool hook. Cover allow/deny flows, idempotency, revert symmetry, and the no-known-hook fail-soft path. Refs AAASM-2923 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Concrete ADK 1.x tools (e.g. FunctionTool) override run_async on the subclass, so a patch on BaseTool.run_async never runs for them. Discover concrete tool classes in google.adk.tools that define their own run_async and patch each in addition to BaseTool. Track patch state in the class's own __dict__ so an already-patched base does not mask a subclass patch, keeping per-class apply/revert symmetric and idempotent. Refs AAASM-2923 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add offline fake-class tests modelling an ADK 1.x FunctionTool subclass that overrides run_async. Cover concrete-class discovery, that the subclass override is patched and intercepted on deny, and symmetric revert of both base and subclass. Refs AAASM-2923 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
✅ Claude Code review — ready to mergeCI: All checks green — Scope vs AAASM-2923 acceptance criteria:
Good catch beyond the AC: fixed a latent masking bug — the patched-flag check now reads the class's own Residual risk (acceptable, documented):
Follow-up (not this PR): the adapter is now forward-compatible, so the Verdict: Scope fully covered, CI green, correct and well-tested. Approving for merge. — Claude Code |



Description
Two framework adapters intercepted tool execution at a hook point that newer library versions no longer route through, so interception silently broke (or raised
AttributeError) on current releases. This makes both adapters version-tolerant and fail-soft.Pydantic AI (
agent_assembly/adapters/pydantic_ai/patch.py) —Tool._runwas removed/restructured in Pydantic AI>=0.3.0, so the old patch raisedAttributeError(the example had to pin<0.3.0). The adapter now detects the available hook across versions:Tool._runon<0.3.0, elseAbstractToolset.call_tool(self, name, tool_args, ctx, tool)on>=0.3.0(the actual>=0.3.0tool-execution path, confirmed against current Pydantic AI docs). When neither hook point exists,apply()is a no-op that returnsFalseinstead of raising.revert()stays symmetric and idempotent for whichever class was patched.Google ADK (
agent_assembly/adapters/google_adk/patch.py) — concrete ADK 1.x tools (e.g.FunctionTool, confirmed via ADK docs to inheritBaseTooland overriderun_async) define their ownrun_asyncon the subclass, so a patch onBaseTool.run_asyncnever ran for them. The adapter now discovers concrete tool classes ingoogle.adk.toolsthat define their ownrun_asyncand patches each in addition toBaseTool. Patch state is tracked in each class's own__dict__so an already-patched base does not mask a subclass patch, keeping per-class apply/revert symmetric and idempotent.All existing governance behavior is preserved on both new paths:
check_tool_start→ pending/approval → deny raisesPolicyViolationError→ spawn context → record result.Type of Change
Breaking Changes
Does this PR introduce any breaking changes?
No public API change. New behavior is additive: older library versions keep working via the existing hooks; newer versions are now intercepted instead of failing.
Related Issues
Testing
Describe the testing performed for this PR:
New offline fake-class unit tests (no real libraries required) model the new API shapes:
Toolwith no_runexposing the>=0.3.0AbstractToolset.call_toolhook — allow/deny flows, idempotency, revert symmetry, and the no-known-hook fail-soft path.FunctionToolsubclass that overridesrun_async— concrete-class discovery, that the subclass override is patched and intercepted on deny, and symmetric revert of base + subclass.Existing
pytest.importorskipintegration tests are unchanged.Validation gate (run before push, all green for changed code):
ruff check .— pre-existing unrelated failures only; the four changed files are clean.ruff format --check .— pre-existing unrelated reformat candidates only; the four changed files are formatted.mypy agent_assembly— pre-existing unrelated errors only (incl. native_corestub); the two changed impl files are clean.pytest test/— 427 passed, 10 skipped, 0 failed (incl. 9 new tests).Checklist
🤖 Generated with Claude Code