Human-in-the-Loop
Three mechanisms pause a run for a human:
- AskUserQuestion: the agent asks a question and waits for an answer.
- Tool approval: the agent waits for allow or deny before a gated tool call.
- 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:
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
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:
Behavior depends on how the agent framed the question:
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()returns409. - If the question already auto-continued,
runs.answer()returns409withauto_continued; send a follow-up viaruns.reply()to redirect. - Auto-continued requests have
auto_resolved == Trueand their answer values intool_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_idruns). 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:
Tool approval
Fetch pending requests with runs.permissions(), then decide with runs.approve():
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/nofor a tool gate). A bareyes/noto 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:
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:
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.
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
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: falseon 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:
Next: Runs · Users · Tools · Webhook Events
