Skip to main content
DeepResearch tasks support optional human-in-the-loop checkpoints that pause execution at key decision points, allowing users to review and guide the research process.
HITL is only available for individual deep research tasks. It is not available for batch requests.

Available checkpoints

Enable any combination of four checkpoints that fire in order during the research lifecycle:
CheckpointPhaseWhen it fires
planning_questionsPre-researchBefore research begins — the agent asks clarifying questions
plan_reviewPre-researchAfter planning — user reviews the research plan
source_reviewPost-researchAfter research — user filters sources by domain
outline_reviewPost-researchAfter source filtering — user reviews the report outline

Quick start

from valyu import Valyu

client = Valyu()

# Create a task with HITL checkpoints
task = client.deepresearch.create(
    query="Analyze the competitive landscape of AI chip manufacturers",
    mode="heavy",
    hitl={
        "planning_questions": True,
        "plan_review": True,
        "source_review": True,
        "outline_review": True,
    },
)

task_id = task.deepresearch_id

# Poll until a checkpoint fires or task completes
import time

while True:
    status = client.deepresearch.status(task_id)

    if status.status == "awaiting_input":
        interaction = status.interaction
        print(f"Checkpoint: {interaction.type}")
        print(f"Data: {interaction.data}")

        # Build your response based on the checkpoint type
        if interaction.type == "planning_questions":
            response = {
                "answers": [
                    {"question": q["question"], "answer": "Focus on NVIDIA, AMD, and Intel"}
                    for q in interaction.data["questions"]
                ]
            }
        elif interaction.type in ("plan_review", "outline_review"):
            response = {"approved": True}
        elif interaction.type == "source_review":
            response = {
                "included_domains": ["sec.gov", "plos.org"],
                "excluded_domains": [],
            }

        # Respond to the checkpoint
        client.deepresearch.respond(
            task_id,
            interaction_id=interaction.interaction_id,
            response=response,
        )

    elif status.status in ("completed", "failed", "cancelled"):
        break

    time.sleep(5)

print(status.output)

Task lifecycle with HITL

When a checkpoint fires, the task transitions through these statuses:
running → awaiting_input → (user responds) → running → ... → completed
If the user doesn’t respond within 5 minutes, the task transitions to paused:
running → awaiting_input → (5 min timeout) → paused → (user responds later) → queued → running

Status values

StatusMeaning
awaiting_inputCheckpoint active, container holding capacity, fast resume on response
pausedCheckpoint timed out, state saved, respond anytime to resume
runningResearch or writing in progress
queuedRe-enqueued after responding to a paused task
Responding to a paused task still works — the task re-enqueues at highest priority. The only difference is a brief cold-start delay as the container restarts.

Checkpoint response shapes

Each checkpoint type expects a specific response format.

Planning questions

The agent asks clarifying questions before starting research. Interaction data:
{
  "questions": [
    {
      "question": "What geographic regions should the research focus on?",
      "context": "The query mentions global markets — narrowing scope improves depth"
    },
    {
      "question": "Are there specific competitors you want analyzed?"
    }
  ]
}
Response:
{
  "answers": [
    { "question": "What geographic regions?", "answer": "North America and EU" },
    { "question": "Specific competitors?", "answer": "Tesla, BYD, Rivian" }
  ]
}
FieldTypeRequired
answersArray<{ question, answer }>Yes
answers[].questionstringYes
answers[].answerstringYes

Plan review

Review the research plan before execution. Interaction data:
{
  "plan": "I'll research this topic by first examining...",
  "estimated_steps": 15,
  "research_areas": ["Market size analysis", "Competitive landscape", "Regulatory environment"]
}
Response (approve):
{ "approved": true }
Response (request modifications):
{
  "approved": false,
  "modifications": "Focus more on battery supply chains and less on historical context"
}
FieldTypeRequired
approvedbooleanYes
modificationsstringNo — free-text guidance for the model

Source review

Filter sources by domain after the research phase. Interaction data:
{
  "domains": [
    {
      "domain": "sec.gov",
      "source_count": 8,
      "avg_relevance_score": 0.87,
      "sources": [
        { "source_id": 1, "title": "SEC Filing: Tesla Annual Report", "url": "https://sec.gov/...", "relevance_score": 0.92 }
      ],
      "ai_recommendation": "include"
    },
    {
      "domain": "example.com",
      "source_count": 2,
      "avg_relevance_score": 0.31,
      "sources": [
        { "source_id": 14, "title": "...", "url": "https://example.com/...", "relevance_score": 0.31 }
      ],
      "ai_recommendation": "exclude"
    }
  ],
  "total_sources": 42
}
Response:
{
  "included_domains": ["sec.gov", "plos.org"],
  "excluded_domains": ["example.com"]
}
FieldTypeRequired
included_domainsstring[]Yes (can be empty [] to accept AI recommendations)
excluded_domainsstring[]Yes (can be empty [])
Domains not listed in either array fall back to the AI recommendation.

Outline review

Review the report outline before writing begins. Interaction data:
{
  "outline": "1. Introduction\n2. Market Analysis\n3. Competitive Landscape\n4. Conclusion",
  "sections": [
    { "title": "Introduction", "description": "Overview of the research topic", "estimated_length": "short" },
    { "title": "Market Analysis", "description": "Market size and growth analysis", "estimated_length": "long" },
    { "title": "Competitive Landscape", "description": "Key players and positioning", "estimated_length": "long" },
    { "title": "Conclusion", "description": "Summary and implications", "estimated_length": "short" }
  ]
}
Response (approve):
{ "approved": true }
Response (request modifications):
{
  "approved": false,
  "modifications": "Add a section on regulatory risks between Market Analysis and Competitive Landscape"
}
FieldTypeRequired
approvedbooleanYes
modificationsstringNo — free-text guidance for the model

Respond endpoint

POST /v1/deepresearch/tasks/:id/respond
Headers:
HeaderValue
x-api-keyYour API key
Content-Typeapplication/json
Body:
{
  "interaction_id": "string (must match task.interaction.interaction_id)",
  "response": { }
}

Response codes

CodeMeaningExample
200Accepted (hot path){ "success": true, "status": "running" }
200Accepted (cold path){ "success": true, "status": "queued" }
400Validation error{ "error": "response.answers must be an array" }
409Wrong status or ID mismatch{ "error": "Cannot respond to task with status: running" }

Interaction fields on task status

When HITL is enabled, these additional fields appear on the task status response:
FieldTypePresent when
hitl_configobjectAlways (mirrors the request hitl param)
interactionobjectStatus is awaiting_input or paused
hitl_historyarrayAfter any checkpoint completes

Interaction history entry

Each entry in hitl_history contains:
FieldTypeDescription
interaction_idstringUnique checkpoint ID
typestringCheckpoint type
created_atintegerWhen the checkpoint fired (ms)
responded_atintegerWhen the user responded (ms) — absent if timed out
auto_continuedbooleantrue if timed out, false if user responded
responseobjectThe user’s response — absent if timed out

Polling pattern

Standard HITL polling flow:
1. POST /v1/deepresearch/tasks          → { deepresearch_id }
2. GET  .../status                       → status: "running"
3. GET  .../status                       → status: "awaiting_input", interaction: { ... }
4. POST .../respond                      → { status: "running" }
5. GET  .../status                       → status: "running"
   ... (repeats for each enabled checkpoint)
6. GET  .../status                       → status: "completed"
If a checkpoint times out:
3. GET  .../status                       → status: "awaiting_input"
   ... (5 minutes pass)
4. GET  .../status                       → status: "paused"
   ... (user responds later)
5. POST .../respond                      → { status: "queued" }
6. GET  .../status                       → status: "running"

Best practices

Poll frequently during checkpoints

Use a 2-3 second poll interval when expecting HITL checkpoints. Switch to standard intervals (5-10s) during research phases.

Handle both statuses

Always check for both awaiting_input and paused — the user experience is identical, only resume speed differs.

Enable selectively

Only enable checkpoints that add value for your use case. Each checkpoint adds latency equal to the user’s response time.

Use with heavy or max modes

HITL works best with heavy or max modes where the research is substantial enough to benefit from human guidance.