Human-in-the-Loop

By default, runs are autonomous. Turn on human in the loop when you want checkpoints before the agent continues.

This guide covers AskUserQuestion, tool approval, and plan mode in one place.

Quick start

Use runs.wait() with callbacks to handle approval pauses inline — no manual polling loop needed:

Python

Both callbacks receive the full PermissionRequest so you can inspect what's being asked:

Python

AskUserQuestion

AskUserQuestion answers always go through runs.answer().

Behavior

When an agent calls AskUserQuestion, the run immediately pauses to awaiting_approval. It stays paused indefinitely — the user can answer after seconds, minutes, or days.

  • If the run is awaiting_approval, your answer resumes the run.
  • If the run is terminal (completed, failed, cancelled), runs.answer() returns 409.

Submit an answer

Python

Find pending AskUserQuestion requests

runs.permissions() can include AskUserQuestion entries. Each has tool_name == "AskUserQuestion" and tool_input["questions"] with the full question list:

Python

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

Use human_in_the_loop=True with approval or plan. Without it, those modes return a validation error.

Approve or deny tool calls

Use runs.permissions() to fetch pending requests, then runs.approve().

remember=true applies to subsequent matching requests in the current run. For persistent cross-run policies, use client.permissions.create().

Plan mode

Plan mode uses AskUserQuestion with a Plan Approval question before execution.

Use req.is_plan_approval and req.plan_text to detect and display the plan:

Python

After plan approval, execution continues with approval-style checks for non-safe tools.

Plan mode in your product

If you're embedding plan approval in a UI, fetch the plan text and show it to your user before calling runs.answer():

Python

Switch permission mode mid-run

You can change the permission mode on a running or paused run without restarting it.

Python

When switching to autonomous, all currently pending tool approval requests are auto-approved immediately.

Pre-approve trusted tools

Create per-user permission policies so trusted tools skip approval pauses.

Python

Now runs for cust_123 in approval mode skip pauses for those tools.

Commonly pre-approved tools (read-only, low-risk):

ToolWhat it does
slackPost messages
linearCreate/update issues
google-sheetsRead and write sheets
gmailRead emails, send drafts
notionRead and write pages

Set policies at user creation time:

Python

Tip for internal teams: In approval mode, pre-approve read-only tools (search, fetch, list) and only require approval for write/send/delete actions. This keeps the agent productive while still letting you review high-impact operations.

Production pattern: webhooks

For production, prefer webhooks over polling:

  1. Subscribe to run.awaiting_input
  2. For AskUserQuestion pauses, questions are in the webhook payload — no extra call needed:
JSON

For tool approval pauses, questions is absent — fetch with runs.permissions(). 3. Show them in your UI 4. Send decisions back with answer or approve

See Webhook Events for payloads and signature verification.

Important reply behavior

runs.reply() always runs as non-interactive. It does not enable AskUserQuestion, approval mode, or plan mode.

If you need HITL handling on a follow-up, use reply_and_wait() instead:

Python

What's next

  • Runs for run lifecycle and streaming
  • Users for per-user isolation and policy scoping
  • Tools for integration setup
  • See examples/plan-mode.py for a complete interactive and automated approval example