Skip to main content
An Agents SDK agent has a refund tool. When the policy says a human must approve, the run should stop with the SDK’s own 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 needs pip 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, because protected_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. With effect= declared, the retry is refused with AmbiguousEffect, which gate.run returns as itself; resolve with ctrlrun resolve refund:txn_2 --committed or --failed.

Next