Framework guides
How to add human approvals to OpenAI Agents SDK
Map OpenAI Agents SDK interruptions to Contro1 requests so humans can approve high-risk actions before execution continues.
OpenAI Agents SDK is well suited to tool-level approval flows where you need to gate sensitive actions but keep low-risk actions fast.
Use the integration skill
Copy this skill link into your code agent to add OpenAI Agents SDK and Contro1 to your system.
Key takeaways
- Mark risky tools with needs_approval=True so Runner.run() yields an interruption instead of executing.
- Each interruption becomes one Contro1 request keyed by run_id + call_id for idempotency.
- Wrap Runner.run() in a try/except to escalate unexpected errors to a human on call.
- In the system prompt, tell the agent which tool names always require approval even if obvious.
When to reach for Contro1 with OpenAI Agents
The Agents SDK already exposes a clean seam - function_tool(needs_approval=True) - for pausing before a tool call executes. That seam is perfect for Contro1: we turn each interruption into a routable approval and return the operator's decision back into the run.
Use this when you want fine-grained tool-by-tool control rather than pausing the whole agent. Low-risk tools keep running at full speed; high-risk ones route through the right human every time.
Installation
Basic integration
Case continuity
Use one correlation_id per OpenAI Agents run - f"openai-{run_id}" works well. This groups every tool interruption, operator decision, and audit record for the run into one case timeline.
Keep call_id in external_request_id so duplicate interruptions return the original request instead of creating a new one.
Logging autonomous actions
Use log_action for tool outputs or model-side actions that were allowed to run without a human. This gives compliance and support teams evidence without slowing down low-risk work.
If the log describes what happened after an approved interruption, set in_reply_to to the Contro1 request id.
Gate the tool before it executes
The tool function itself is the right place to require approval for irreversible actions. The first line of a destructive tool calls Contro1 and blocks until an operator decides. Nothing runs until the human says yes - no prompt engineering needed.
Pause the agent on system error - orchestrator level
Wrap Runner.run() in an outer try/except so an unhandled agent-loop failure becomes a Contro1 request instead of a stack trace in logs. The operator chooses whether to retry the run or mark it as terminally failed.
Escalate tool errors to a human
Inside each risky tool, catch domain errors (provider outage, validation failure, rate limit) and escalate via Contro1 before the exception propagates back into the agent loop. The operator picks retry / skip / cancel.
Prompt engineering: force the agent to pause
needs_approval=True gates a tool at the SDK level, but the model still decides whether to call the tool. Use the system prompt to teach it when to call - and when NOT to look for workarounds.
See our GitHub integration repo
Our open-source OpenAI Agents connector shows the full bridge in production style, including signature verification on the callback path.
centcom-openai-agents on GitHub · openai_agents_bridge.py - interruption → approval mapping
Frequently asked questions
Should every tool require approval?
No. Gate only the high-risk, irreversible, or policy-sensitive tools so the agent remains useful without becoming unsafe. A good starting set is anything that touches money, customers, or production data.
How do I persist run state across the human wait?
The SDK lets you serialize result.state. Store it keyed by run_id before calling wait_for_response, and rehydrate it in the webhook handler if the process restarts.
Can the operator pass arguments back to the tool?
Yes. The approve call takes an overridden arguments payload - Contro1's response.comment is a natural place to put that override if you want the operator to adjust before approving.
Does this work with the Assistants API?
The pattern is the same: map required_action → Contro1 request → submit_tool_outputs with the operator decision. See our managed-agents example for Claude and adapt it.
Why the external_request_id with run_id + call_id?
It guarantees idempotency. If your wrapper crashes and the loop retries the same interruption, Contro1 returns the original request instead of creating a duplicate.