Human-in-the-Loop

Three mechanisms pause a run for a human:

  1. AskUserQuestion: the agent asks a question and waits for an answer.
  2. Tool approval: the agent waits for allow or deny before a gated tool call.
  3. Plan mode: the agent proposes a plan and waits for approval before executing.

Runs inherit their permission mode from the explicit request override or the saved agent/task default. Turn on human_in_the_loop when you want checkpoints before the agent continues.

Quick start

runs.wait() handles all three inline, no manual polling loop:

Python

Both callbacks receive the full PermissionRequest: inspect req.tool_name and req.tool_input (for questions, req.tool_input["questions"] is the full list) before returning your decision.

Permission modes

ModeWhat happens
autonomousAgent runs without approval pauses
approvalAgent pauses before tool use and asks for allow or deny
planAgent proposes a plan first and waits for approval

If a run resolves to approval or plan and you omit human_in_the_loop, the API enables it automatically. Explicitly setting human_in_the_loop=False with those modes returns a validation error.

AskUserQuestion

Answers always go through runs.answer(). The answer key is the question text:

Python

Behavior depends on how the agent framed the question:

QuestionBehavior
Has a recommended option (label ending in (Recommended))Pauses to awaiting_approval; unanswered after ~10 minutes, the run auto-continues with the recommendation and notifies the owner
Recommended + non-blocking (metadata.source == "non-blocking")Never pauses; proceeds with the recommendation immediately and notifies the owner
No recommended optionPauses indefinitely (a question the agent genuinely can't default)

Tool approval gates and plan approvals never auto-continue.

Advanced: auto-continue markers, 409s, and finding pending questions
  • If the run is awaiting_approval, your answer resumes the run.
  • If the run is terminal (completed, failed, cancelled), runs.answer() returns 409.
  • If the question already auto-continued, runs.answer() returns 409 with auto_continued; send a follow-up via runs.reply() to redirect.
  • Auto-continued requests have auto_resolved == True and their answer values in tool_input["answers"] end with an [auto-selected ...] marker, so you can always tell a platform default from a human answer.
  • Non-blocking asks never enter awaiting_approval; deferrable ones pause up to ~10 minutes, then resume on their own. A run can complete with questions nobody answered.
  • A blocking pause unanswered for ~10 minutes on a first-party run triggers a "waiting on you" email to the owner (skipped for multi-tenant user_id runs). A present user who answers in-app never triggers it.

Find pending questions with runs.permissions(). Entries have tool_name == "AskUserQuestion" and tool_input["questions"] with the full question list:

Python

Tool approval

Fetch pending requests with runs.permissions(), then decide with runs.approve():

Python

For persistent cross-run policies, see pre-approve trusted tools.

Approve from your inbox or Slack

A paused run (a tool gate or an AskUserQuestion) can be answered without opening the app:

  • Email: reply to the pause email with the option number or option name (or yes/no for a tool gate). A bare yes/no to a question is dropped.
  • Slack: when the run originated in Slack, click Approve/Deny for a tool gate or the per-option button for a single-question AskUserQuestion in the thread. The clicker must be the installer or an allowlisted Slack id.

Email is the universal fallback when a Slack prompt can't deliver. These are the human-friendly equivalents of runs.approve() (tool gates) and runs.answer() (AskUserQuestion).

Switch permission mode mid-run

Change the permission mode on a running or paused run without restarting it:

Python

Switching to autonomous auto-approves pending tool approval requests and resumes a paused tool approval run immediately. AskUserQuestion and plan approvals still wait for client.runs.answer().

Pre-approve trusted tools

Create per-user permission policies so trusted tools skip approval pauses across runs. Pre-approve read-only tools (search, fetch, list) and keep write/send/delete actions gated:

Python

Some actions can't be pre-approved. Anything that spends money, changes access, or destroys work asks a human on every call, in every permission mode. Trying to pre-approve one returns 400 TOOL_REQUIRES_HUMAN_APPROVAL rather than a policy that would be silently ignored.

Onboarding pattern

Set policies at user creation time so the agent isn't interrupted constantly. Low-risk candidates: slack, linear, google-sheets, gmail, notion.

Python

Plan mode

The agent proposes a plan and waits for approval before executing. It has its own page: Plan Mode.

Production pattern: webhooks

Prefer webhooks over polling: subscribe to run.awaiting_input, show the pause in your UI, send the decision back with answer or approve. For AskUserQuestion pauses the questions are in the webhook payload; for tool approval pauses fetch them with runs.permissions(). See Webhook Events for payloads and signature verification.

Sample run.awaiting_input payload
JSON
Replies on human-in-the-loop runs

runs.reply() inherits the run's persisted settings: the permission mode keeps applying on follow-ups, and AskUserQuestion stays enabled when the run was created with human_in_the_loop: true (or a mode that defaults it on). Two consequences:

  • Unattended reply loops: a reply on a HITL run can pause on a question. If your server-side code replies without a human watching, pass human_in_the_loop: false on the reply to pin the always-non-interactive behavior. Runs created before this setting was persisted stay non-interactive.
  • Handling gates on follow-ups: use reply_and_wait() to answer approvals and questions inline:
Python

Next: Runs · Users · Tools · Webhook Events

Was this page helpful?