Skip to main content
Human-in-the-loop (HITL) allows the agent to pause mid-execution, ask a question, and wait for a human response before continuing. This is useful when the agent encounters ambiguity — for example, choosing between plan options, confirming a selection, or providing information not in the original form data.

How it works

  1. Start a job with human_in_the_loop: true
  2. The agent runs normally until it needs input
  3. The agent calls its ask_human tool — job status becomes waiting_for_input
  4. Your system detects the question (via polling or webhook)
  5. You submit an answer via the Answer Question endpoint
  6. The agent resumes with the answer
running → waiting_for_input → running → ... → completed
The agent can ask multiple questions during a single job. Each question has a unique ID.

Starting a HITL job

Pass human_in_the_loop: true when starting a job:
curl -X POST https://agent.usedari.com/jobs/my-org/insurance_form/start \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "form_data": {"name": "Jane Smith"},
    "human_in_the_loop": true
  }'
Without this flag, the agent cannot ask questions and will use its best judgment instead.

Configuring the prompt

By default, the agent uses a generic hint about when to call ask_human. You can customize this with a portal-level human_in_the_loop_prompt to tell the agent exactly when to pause — for example, “Ask the user before submitting each form page” or “Confirm the quote amount before proceeding.” Set this when creating or updating a portal:
curl -X POST https://agent.usedari.com/portals/my-org \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint_name": "insurance_form",
    "start_url": "https://example.com/quote",
    "form_schema": {"type": "object", "properties": {}},
    "human_in_the_loop_prompt": "Ask the user to confirm the quote amount before submitting the form."
  }'
You can also set this in the dashboard under the portal’s Settings tab. When human_in_the_loop_prompt is set and a job is started with human_in_the_loop: true, the custom prompt replaces the generic hint in the agent’s system prompt. If no custom prompt is set, the generic fallback is used.

Detecting questions

Configure a webhook URL on your portal. When the agent asks a question, you’ll receive a question webhook:
{
  "event_type": "question",
  "status": "waiting_for_input",
  "browser_url": "https://hyperbrowser.ai/view/abc123",
  "question": {
    "batch_id": "c3f1a2b4-5678-4def-9abc-123456789012",
    "questions": [
      {
        "question_id": "aae818ac-6f33-4a3e-8981-e6e5c303d1f1",
        "question": "There are two plan options: Basic ($50/mo) and Premium ($120/mo). Which one should I select?",
        "options": ["Basic ($50/mo)", "Premium ($120/mo)"]
      }
    ]
  }
}
The questions array always contains one or more questions. A single question is represented as a one-element array.

Option 2: Polling

Poll Get Job Status and check for status: "waiting_for_input". The question details are in the latest question event:
import time
import requests

while True:
    resp = requests.get(
        "https://agent.usedari.com/jobs/my-org/form/status",
        headers={"X-API-Key": "YOUR_API_KEY"},
        params={"job_id": job_id},
    )
    status = resp.json()

    if status["status"] == "waiting_for_input":
        question_event = next(
            e for e in reversed(status["events"])
            if e["event_type"] == "question"
        )
        data = question_event["data"]
        for q in data["questions"]:
            print(f"Question: {q['question']}")
            if q.get("options"):
                print(f"  Options: {q['options']}")
        break

    if status["status"] in ("completed", "failed", "stopped"):
        break

    time.sleep(3)

Submitting answers

Use the Answer Question endpoint with the batch_id and answers for each question:
curl -X POST "https://agent.usedari.com/jobs/my-org/form/answer?job_id=JOB_ID" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "batch_id": "c3f1a2b4-5678-4def-9abc-123456789012",
    "answers": [
      {
        "question_id": "aae818ac-6f33-4a3e-8981-e6e5c303d1f1",
        "answer": "Basic ($50/mo)"
      }
    ]
  }'
All questions must be answered in a single request. The agent resumes immediately after the answers are submitted.

Timeouts

If no answer is submitted, the agent times out after the job’s timeout_minutes and continues with its best judgment. You can also stop the job while it’s waiting for input.

Full example

import time
import requests

API = "https://agent.usedari.com"
HEADERS = {"X-API-Key": "YOUR_API_KEY"}

# 1. Start job with HITL
resp = requests.post(
    f"{API}/jobs/my-org/insurance_form/start",
    headers=HEADERS,
    json={
        "form_data": {"name": "Jane Smith", "state": "CA"},
        "human_in_the_loop": True,
    },
)
job_id = resp.json()["job_id"]

# 2. Poll for questions
while True:
    status = requests.get(
        f"{API}/jobs/my-org/insurance_form/status",
        headers=HEADERS,
        params={"job_id": job_id},
    ).json()

    if status["status"] == "waiting_for_input":
        data = next(
            e["data"] for e in reversed(status["events"])
            if e["event_type"] == "question"
        )
        answers = []
        for q in data["questions"]:
            print(f"Agent asks: {q['question']}")
            if q.get("options"):
                for i, opt in enumerate(q["options"]):
                    print(f"  {i + 1}. {opt}")
            ans = input("Your answer: ")
            answers.append({
                "question_id": q["question_id"],
                "answer": ans,
            })

        # 3. Submit answers
        requests.post(
            f"{API}/jobs/my-org/insurance_form/answer",
            headers=HEADERS,
            params={"job_id": job_id},
            json={
                "batch_id": data["batch_id"],
                "answers": answers,
            },
        )
        continue

    if status["status"] in ("completed", "failed", "stopped"):
        print(f"Job finished: {status['status']}")
        break

    time.sleep(3)