[AAASM-3528] 🐛 (adapters): Fix OpenAI Agents adapter to govern current API#158
Conversation
The OpenAI Agents adapter targeted a stale framework API and was a silent no-op, so agents on the OpenAI Agents SDK ran ungoverned (fail-open). It patched `openai.agents` + `FunctionTool.__call__`, but the shipped framework is the top-level `agents` package whose tools run via a per-instance `on_invoke_tool(ctx, json)` coroutine — there is no `__call__`, so the class-level patch never applied. Fix: - Detect and patch the `agents` package (not `openai.agents`). - Wrap `FunctionTool.__init__` / `Handoff.__init__` so every instance's `on_invoke_tool` / `on_invoke_handoff` coroutine is wrapped with the pre-execution governance check, deny/pending handling, result recording, and topology-edge emission. Deny returns the framework's documented string error, blocking the call without aborting the run. - Key `is_available()` on the real `agents` module. - Add `openai-agents` as a dev/test dependency so the importorskip integration test drives a REAL tool call in CI. Tests now drive the per-instance `on_invoke_tool` contract (and the real framework via importorskip) and fail if the patch reverts to a no-op, asserting genuine governance rather than just that apply() ran. Closes AAASM-3528 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
🤖 Claude Code — PR review (record)CI: ✅ all green/skipped. Scope vs AAASM-3528: ✅ fully covers it — and it's a real security fix (silent fail-open → governed). The adapter targeted a stale API ( Verdict: ✅ Ready to approve & merge. |
|
This is the ref: |



Description
The Python SDK's OpenAI Agents adapter (
agent_assembly/adapters/openai_agents/) targeted a stale framework API, so applying it was a silent no-op — agents running on the OpenAI Agents SDK were governed only in name and actually ran ungoverned (fail-open).Root cause: the adapter patched
openai.agents+FunctionTool.__call__. Neither exists in the shippedopenai-agentsframework: the package is the top-levelagentsmodule, and a tool is executed by the runner calling each tool instance'son_invoke_tool(ctx, args_json)coroutine — there is no__call__. Because that class attribute did not exist, the class-level patch never applied, and no pre-execution allow/deny or event emission ever fired.Fix:
agentspackage (notopenai.agents);is_available()now keys onagents.FunctionTool.on_invoke_toolis a per-instance callable, not a class method, so it cannot be patched once on the class. The adapter now wrapsFunctionTool.__init__(andHandoff.__init__) so every constructed instance has itson_invoke_tool/on_invoke_handoffcoroutine wrapped with the governance pre-execution check, deny/pending-approval handling, audit result recording, spawn-context tracking, anddelegates_totopology-edge emission.Runner.run(a real classmethod) is still wrapped for spawn context.OpenAIAgentsAdapter/OpenAIAgentsPatchwithapply()/revert()/register_hooks()/unregister_hooks()) is unchanged and stays consistent with the LangChain/LangGraph adapters.Type of Change
Breaking Changes
Related Issues
Testing
The unit and integration tests now drive the real per-instance
on_invoke_toolcontract — the integration suite installs the realagentsframework (importorskip-guarded;openai-agentsadded as a dev/test dep) and drives a genuinefunction_toolexactly as the runner does. The tests assert governance actually fires (a denied tool returns a policy error and its body does not run; an allowed tool runs and is recorded), and they fail if the patch is reverted to a no-op — verified locally by stubbing the wrapper to a no-op and confirming both the unit and integration governance tests go red.Local checks (all clean for the change):
uv run pytest— 584 passed, 13 skipped.uv run ruff check/ruff format --check— clean for changed files (pre-existingARG002noise intest/bench/**predates this change and was reduced, not added).uv run mypy agent_assembly— adapter clean; the 4 remaining package-wide errors are the pre-existing_corenative-shim / grpc-stub baseline, unrelated to this change.pre-commit run(isort/autoflake/black/mypy) — all pass.Checklist
Closes AAASM-3528
🤖 Generated with Claude Code