You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 9abc918
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/human_in_the_loop.md
+14-4Lines changed: 14 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -49,12 +49,22 @@ For `ComputerTool`, use `on_safety_check` to accept or reject safety checks retu
49
49
## How the approval flow works
50
50
51
51
1. When the model emits a tool call, the runner evaluates its approval rule (`needs_approval`, `require_approval`, or the hosted MCP equivalent).
52
-
2. If an approval decision for that tool call is already stored in the [`RunContextWrapper`][agents.run_context.RunContextWrapper], the runner proceeds without prompting. Per-call approvals are scoped to the specific call ID; pass `always_approve=True` or `always_reject=True` to persist the same decision for future calls to the same tool identity during the rest of the run.
52
+
2. If an approval decision for that tool call is already stored in the [`RunContextWrapper`][agents.run_context.RunContextWrapper], the runner proceeds without prompting. Per-call approvals are scoped to the specific call ID; pass `always_approve=True` or `always_reject=True` to persist the same decision for future calls to the same tool identity.
53
53
3. If the approval rule requires approval and no decision for that tool call is stored, execution pauses, and `RunResult.interruptions` (or `RunResultStreaming.interruptions`) contains [`ToolApprovalItem`][agents.items.ToolApprovalItem] entries with details such as `agent.name`, `tool_name`, and `arguments`. This includes approvals raised after a handoff or inside nested `Agent.as_tool()` executions.
54
54
4. Convert the result to a `RunState` with `result.to_state()`, call `state.approve(...)` or `state.reject(...)`, and then resume with `Runner.run(agent, state)` or `Runner.run_streamed(agent, state)`, where `agent` is the original top-level agent for the run.
55
55
5. The resumed run continues where it left off and will re-enter this flow if new approvals are needed.
56
56
57
-
Sticky decisions created with `always_approve=True` or `always_reject=True` are stored in the run state, so they survive `state.to_string()` / `RunState.from_string(...)` and `state.to_json()` / `RunState.from_json(...)` when you resume the same paused run later.
57
+
Sticky decisions created with `always_approve=True` or `always_reject=True` remain active while the approval-bearing `RunContextWrapper` is reused, including across separate `Runner.run(...)` or `Runner.run_streamed(...)` calls. Passing a plain application context object to a new run creates a fresh wrapper; reusing that object alone does not carry approvals forward. `RunState` also stores sticky decisions, so they survive `state.to_string()` / `RunState.from_string(...)` and `state.to_json()` / `RunState.from_json(...)` when you resume the paused run later.
58
+
59
+
For function tools, sticky decisions are scoped to the agent that requested approval. A different agent's function tool does not inherit a decision merely because the tool has the same name.
60
+
61
+
!!! warning "Persistent shell and file-edit decisions are not agent-scoped"
62
+
63
+
For local [`ShellTool`][agents.tool.ShellTool] and [`ApplyPatchTool`][agents.tool.ApplyPatchTool], sticky decisions match the tool type and name, not the owning agent, executor, or editor. When agents share approval state, an `always_approve=True` decision can apply to another agent's tool of the same type and name after a handoff, even when the executors or editors have different permissions. The default names are `shell` and `apply_patch`, respectively. The SDK does not prompt again or re-evaluate the destination tool's `needs_approval` policy or `on_approval` callback when that sticky approval applies. `always_reject=True` decisions are shared in the same way.
64
+
65
+
When executors or editors have different permissions, use per-call decisions: call `state.approve(interruption)` or `state.reject(interruption)`, leaving `always_approve` and `always_reject` at their default `False` values. To require a human decision for every new shell or file-edit call, also set `needs_approval=True` and omit `on_approval`. A per-call decision does not clear an existing sticky decision.
66
+
67
+
When starting a new run with different execution permissions, use a fresh `RunContextWrapper` rather than reusing a wrapper that holds approvals. Resuming an existing `RunState` preserves its approvals, even if you pass a new context object or wrapper; a context override does not reset approval state.
58
68
59
69
For approval requests from [`HostedMCPTool`][agents.tool.HostedMCPTool], the Agents SDK identifies a sticky tool decision by the combination of `server_label` and tool name. An always-approve decision for `lookup_account` on one hosted MCP server does not approve a tool with the same name on another server. The Agents SDK persists an always-approve or always-reject decision only when the hosted MCP approval request includes both non-empty identity fields.
60
70
@@ -182,10 +192,10 @@ To use streaming in a run that may pause for approvals, call `Runner.run_streame
182
192
-**Streaming approvals**: `examples/agent_patterns/human_in_the_loop_stream.py` shows how to drain `stream_events()` and then approve pending tool calls before resuming with `Runner.run_streamed(agent, state)`.
183
193
-**Custom rejection text**: `examples/agent_patterns/human_in_the_loop_custom_rejection.py` shows how to combine run-level `tool_error_formatter` with per-call `rejection_message` overrides when approvals are rejected.
184
194
-**Agent as tool approvals**: `Agent.as_tool(..., needs_approval=...)` applies the same interruption flow when delegated agent tasks need review. Nested interruptions still surface on the outer run, so resume the original top-level agent rather than the nested one.
185
-
-**Local shell and apply_patch tools**: `ShellTool` and `ApplyPatchTool` also support `needs_approval`. Use `state.approve(interruption, always_approve=True)` or `state.reject(..., always_reject=True)` to cache the decision for future calls to that tool during the rest of the run. To resolve approval inside a callback, provide `on_approval`; `examples/tools/shell.py` demonstrates an interactive callback that prompts the operator by default. For an automatic policy that rejects every shell call, set `needs_approval=True` and `on_approval=lambda _context, _item: {"approve": False, "reason": "Disabled by policy"}` on `ShellTool`. To let the application review a paused run instead, handle interruptions (see `examples/tools/shell_human_in_the_loop.py`). Hosted shell environments do not support `needs_approval` or `on_approval`; see the [tools guide](tools.md).
195
+
-**Local shell and apply_patch tools**: `ShellTool` and `ApplyPatchTool` also support `needs_approval`. Use `state.approve(interruption)` or `state.reject(interruption)` for per-call decisions. The `always_approve=True` and `always_reject=True` options persist decisions in the `RunContextWrapper` and `RunState`; review the [approval scope warning](#how-the-approval-flow-works) before using these options. To resolve approval inside a callback, provide `on_approval`; `examples/tools/shell.py` demonstrates an interactive callback that prompts the operator by default. To automatically reject shell calls that have no existing approval decision, set `needs_approval=True` and `on_approval=lambda _context, _item: {"approve": False, "reason": "Disabled by policy"}` on `ShellTool`. To let the application review a paused run instead, handle interruptions (see `examples/tools/shell_human_in_the_loop.py`). Hosted shell environments do not support `needs_approval` or `on_approval`; see the [tools guide](tools.md).
186
196
-**Local MCP servers**: Use `require_approval` on `MCPServerStdio` / `MCPServerSse` / `MCPServerStreamableHttp` to gate MCP tool calls (see `examples/mcp/get_all_mcp_tools_example/main.py` and `examples/mcp/tool_filter_example/main.py`).
187
197
-**Hosted MCP servers**: Set `tool_config={"require_approval": "always"}` on `HostedMCPTool` to force HITL, optionally providing `on_approval_request` to auto-approve or reject (see `examples/hosted_mcp/human_in_the_loop.py` and `examples/hosted_mcp/on_approval.py`). Use `"never"` for trusted servers (`examples/hosted_mcp/simple.py`).
188
-
-**Sessions and memory**: Pass a session to `Runner.run`so approvals and conversation history survive multiple turns. SQLite and OpenAI Conversations session variants are in `examples/memory/memory_session_hitl_example.py` and `examples/memory/openai_session_hitl_example.py`.
198
+
-**Sessions and memory**: Pass a session to `Runner.run`to preserve conversation history across turns, and use `RunState` to preserve approval decisions when resuming a paused run. SQLite and OpenAI Conversations session variants are in `examples/memory/memory_session_hitl_example.py` and `examples/memory/openai_session_hitl_example.py`.
189
199
-**Realtime agents**: The realtime demo exposes WebSocket messages that approve or reject tool calls via `approve_tool_call` / `reject_tool_call` on the `RealtimeSession` (see `examples/realtime/app/server.py` for the server-side handlers and [Realtime guide](realtime/guide.md#tool-approvals) for the API surface).
Copy file name to clipboardExpand all lines: docs/tools.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -257,6 +257,8 @@ For commands or file edits that require review, set `needs_approval=True` on the
257
257
258
258
To decide immediately in application code, set both `needs_approval` and `on_approval`. The SDK invokes `on_approval` only when the call requires approval and has no existing approval decision; setting the callback alone does not enable approval. See `examples/tools/shell.py` for a CLI prompt and `examples/tools/shell_human_in_the_loop.py` for manual interruption handling. The `examples/tools/apply_patch.py` example instead prompts inside its editor before changing files.
259
259
260
+
Persistent decisions for local `ShellTool` and `ApplyPatchTool` can carry across handoffs to another agent's tool of the same type and name, or across separate runs that reuse the same `RunContextWrapper`. Use per-call decisions when executor or editor permissions differ, and a fresh wrapper when starting a new run with different execution permissions; see the [approval scope warning](human_in_the_loop.md#how-the-approval-flow-works) for details, including why per-call decisions and context overrides during resume do not clear stored approvals.
261
+
260
262
Keep `needs_approval=False` when your application intentionally authorizes automatic execution, for example through an executor that enforces your sandbox policy. Hosted shell environments do not support the SDK's local `needs_approval` or `on_approval` settings.
0 commit comments