ToolApprovalItem, where this SDK’s users already answer, and
a refusal must reach your code as an exception rather than the model as text to retry.
The policy
runnable
The code
This is the adapter’s own example. It needspip install ctrlrun-openai-agents and the SDK,
which the harness that runs the other recipes does not have; the adapter’s tests run this shape
against a real openai-agents in this repository’s CI.
What the agent sees
The SDK asks before invoking, becauseprotected_tool answers its needs_approval from the
policy; the run returns with one interruption naming the tool and its arguments. After
state.approve(item) the resumed run invokes the tool with exactly that call’s arguments, and
CTRLRun records openai-agents:tool-approval as the approver. A refusal by CTRLRun, a
duplicate for instance, reaches your except as DuplicateEffect through gate.run, not the
model as “please try again”.
The receipt
approve/committed. The binding across the interrupt is the SDK’s, keyed by call_id, so the
receipt attributes the approval and cannot re-check the arguments against the hash: that is
attribution, and the adapter’s README says so in that word. A rejection leaves no CTRLRun
evidence at all, because the SDK never invokes a rejected tool; record it where you call
state.reject(item).
When an AMBIGUOUS appears
The SDK’s default would surface a lost reply to the model as text, and the measured behaviour is that the model retries until the refund lands three or four times. Witheffect= declared,
the retry is refused with AmbiguousEffect, which gate.run returns as itself; resolve with
ctrlrun resolve refund:txn_2 --committed or --failed.
Next
- Use the OpenAI Agents SDK adapter: where the SDK shows through, and
ApprovalNotAsked. - Three ways in · Get started · Why.