> ## Documentation Index
> Fetch the complete documentation index at: https://docs.valyu.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Human-in-the-Loop (HITL)

> Pause deep research at key decision points for user review and guidance

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.

<Note>
  HITL is only available for individual deep research tasks. It is **not available for batch requests**.
</Note>

## Available checkpoints

Enable any combination of four checkpoints that fire in order during the research lifecycle:

| Checkpoint           | Phase         | When it fires                                                |
| -------------------- | ------------- | ------------------------------------------------------------ |
| `planning_questions` | Pre-research  | Before research begins — the agent asks clarifying questions |
| `plan_review`        | Pre-research  | After planning — user reviews the research plan              |
| `source_review`      | Post-research | After research — user filters sources by domain              |
| `outline_review`     | Post-research | After source filtering — user reviews the report outline     |

## Quick start

<CodeGroup>
  ```python Python theme={null}
  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)
  ```

  ```typescript TypeScript theme={null}
  import Valyu from "valyu-js";

  const client = new Valyu();

  // Create a task with HITL checkpoints
  const task = await client.deepresearch.create({
    query: "Analyze the competitive landscape of AI chip manufacturers",
    mode: "heavy",
    hitl: {
      planningQuestions: true,
      planReview: true,
      sourceReview: true,
      outlineReview: true,
    },
  });

  const taskId = task.deepresearch_id!;

  // Poll until a checkpoint fires or task completes
  while (true) {
    const status = await client.deepresearch.status(taskId);

    if (status.status === "awaiting_input" && status.interaction) {
      const { type, data, interaction_id } = status.interaction;
      console.log(`Checkpoint: ${type}`, data);

      let response: Record<string, any>;

      if (type === "planning_questions") {
        response = {
          answers: data.questions.map((q: any) => ({
            question: q.question,
            answer: "Focus on NVIDIA, AMD, and Intel",
          })),
        };
      } else if (type === "plan_review" || type === "outline_review") {
        response = { approved: true };
      } else {
        // source_review
        response = {
          included_domains: ["sec.gov", "plos.org"],
          excluded_domains: [],
        };
      }

      await client.deepresearch.respond(taskId, interaction_id, response);
    } else if (
      status.status === "completed" ||
      status.status === "failed" ||
      status.status === "cancelled"
    ) {
      console.log(status.output);
      break;
    }

    await new Promise((r) => setTimeout(r, 5000));
  }
  ```

  ```bash cURL theme={null}
  # 1. Create task with HITL
  curl -X POST https://api.valyu.ai/v1/deepresearch/tasks \
    -H "Content-Type: application/json" \
    -H "x-api-key: $VALYU_API_KEY" \
    -d '{
      "query": "Analyze the competitive landscape of AI chip manufacturers",
      "mode": "heavy",
      "hitl": {
        "planning_questions": true,
        "plan_review": true
      }
    }'

  # 2. Poll for status
  curl https://api.valyu.ai/v1/deepresearch/tasks/{id}/status \
    -H "x-api-key: $VALYU_API_KEY"

  # 3. When status is "awaiting_input", respond
  curl -X POST https://api.valyu.ai/v1/deepresearch/tasks/{id}/respond \
    -H "Content-Type: application/json" \
    -H "x-api-key: $VALYU_API_KEY" \
    -d '{
      "interaction_id": "int_abc123",
      "response": {
        "answers": [
          {"question": "What geographic regions?", "answer": "North America and EU"}
        ]
      }
    }'
  ```
</CodeGroup>

## How HITL fits into the research pipeline

Each checkpoint maps to a stage in the research process. Only enabled checkpoints fire — the rest are skipped automatically.

<Steps>
  <Step title="Query analysis" icon="magnifying-glass">
    The agent analyzes the query and prepares to research.
  </Step>

  <Step title="planning_questions checkpoint" icon="circle-pause">
    **Pause:** The agent asks clarifying questions before starting research. You provide answers to guide scope and focus.
  </Step>

  <Step title="Research planning" icon="map">
    The agent builds a research plan — areas to investigate, estimated steps, and methodology.
  </Step>

  <Step title="plan_review checkpoint" icon="circle-pause">
    **Pause:** You review the research plan. Approve it or request modifications (e.g., "focus more on supply chain").
  </Step>

  <Step title="Research execution" icon="book-open">
    The agent searches, reads sources, and gathers information.
  </Step>

  <Step title="source_review checkpoint" icon="circle-pause">
    **Pause:** You review the sources grouped by domain. Include or exclude domains to control what goes into the report.
  </Step>

  <Step title="Outline generation" icon="list">
    The agent generates a structured outline for the report.
  </Step>

  <Step title="outline_review checkpoint" icon="circle-pause">
    **Pause:** You review the outline. Approve it or request structural changes (e.g., "add a regulatory risks section").
  </Step>

  <Step title="Report writing" icon="pen">
    The agent writes the final report using approved sources and outline.
  </Step>

  <Step title="Completed" icon="check">
    The report is ready. Retrieve it from the status response.
  </Step>
</Steps>

Each pause sets the task status to `awaiting_input`. If you don't respond within 5 minutes, the status transitions to `paused` — but you can still respond at any time to resume.

### Status values

| Status           | Meaning                                                                |
| ---------------- | ---------------------------------------------------------------------- |
| `awaiting_input` | Checkpoint active, container holding capacity, fast resume on response |
| `paused`         | Checkpoint timed out (5 min), state saved, respond anytime to resume   |
| `running`        | Research or writing in progress                                        |
| `queued`         | Re-enqueued after responding to a paused task                          |

<Tip>
  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.
</Tip>

## Checkpoint response shapes

Each checkpoint type expects a specific response format.

### Planning questions

The agent asks clarifying questions before starting research.

**Interaction data:**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "answers": [
    { "question": "What geographic regions?", "answer": "North America and EU" },
    { "question": "Specific competitors?", "answer": "Tesla, BYD, Rivian" }
  ]
}
```

| Field                | Type                          | Required |
| -------------------- | ----------------------------- | -------- |
| `answers`            | `Array<{ question, answer }>` | Yes      |
| `answers[].question` | `string`                      | Yes      |
| `answers[].answer`   | `string`                      | Yes      |

### Plan review

Review the research plan before execution.

**Interaction data:**

```json theme={null}
{
  "plan": "I'll research this topic by first examining...",
  "estimated_steps": 15,
  "research_areas": ["Market size analysis", "Competitive landscape", "Regulatory environment"]
}
```

**Response (approve):**

```json theme={null}
{ "approved": true }
```

**Response (request modifications):**

```json theme={null}
{
  "approved": false,
  "modifications": "Focus more on battery supply chains and less on historical context"
}
```

| Field           | Type      | Required                              |
| --------------- | --------- | ------------------------------------- |
| `approved`      | `boolean` | Yes                                   |
| `modifications` | `string`  | No — free-text guidance for the model |

### Source review

Filter sources by domain after the research phase.

**Interaction data:**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "included_domains": ["sec.gov", "plos.org"],
  "excluded_domains": ["example.com"]
}
```

| Field              | Type       | Required                                             |
| ------------------ | ---------- | ---------------------------------------------------- |
| `included_domains` | `string[]` | Yes (can be empty `[]` to accept AI recommendations) |
| `excluded_domains` | `string[]` | 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:**

```json theme={null}
{
  "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):**

```json theme={null}
{ "approved": true }
```

**Response (request modifications):**

```json theme={null}
{
  "approved": false,
  "modifications": "Add a section on regulatory risks between Market Analysis and Competitive Landscape"
}
```

| Field           | Type      | Required                              |
| --------------- | --------- | ------------------------------------- |
| `approved`      | `boolean` | Yes                                   |
| `modifications` | `string`  | No — free-text guidance for the model |

## Respond endpoint

```
POST /v1/deepresearch/tasks/:id/respond
```

**Headers:**

| Header         | Value              |
| -------------- | ------------------ |
| `x-api-key`    | Your API key       |
| `Content-Type` | `application/json` |

**Body:**

```json theme={null}
{
  "interaction_id": "string (must match task.interaction.interaction_id)",
  "response": { }
}
```

### Response codes

| Code    | Meaning                     | Example                                                      |
| ------- | --------------------------- | ------------------------------------------------------------ |
| **200** | Accepted (hot path)         | `{ "success": true, "status": "running" }`                   |
| **200** | Accepted (cold path)        | `{ "success": true, "status": "queued" }`                    |
| **400** | Validation error            | `{ "error": "response.answers must be an array" }`           |
| **409** | Wrong 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:

| Field          | Type     | Present when                              |
| -------------- | -------- | ----------------------------------------- |
| `hitl_config`  | `object` | Always (mirrors the request `hitl` param) |
| `interaction`  | `object` | Status is `awaiting_input` or `paused`    |
| `hitl_history` | `array`  | After any checkpoint completes            |

### Interaction history entry

Each entry in `hitl_history` contains:

| Field            | Type      | Description                                        |
| ---------------- | --------- | -------------------------------------------------- |
| `interaction_id` | `string`  | Unique checkpoint ID                               |
| `type`           | `string`  | Checkpoint type                                    |
| `created_at`     | `integer` | When the checkpoint fired (ms)                     |
| `responded_at`   | `integer` | When the user responded (ms) — absent if timed out |
| `auto_continued` | `boolean` | `true` if timed out, `false` if user responded     |
| `response`       | `object`  | The 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"
```

## Using `wait()` with HITL

Instead of manual polling, use the `wait()` method with a HITL callback to handle checkpoints automatically:

<CodeGroup>
  ```python Python theme={null}
  from valyu import Valyu

  client = Valyu()

  task = client.deepresearch.create(
      query="Analyze the competitive landscape of AI chip manufacturers",
      mode="heavy",
      hitl={"plan_review": True, "source_review": True},
  )

  def handle_interaction(interaction):
      if interaction.type in ("plan_review", "outline_review"):
          return {"approved": True}
      elif interaction.type == "source_review":
          return {"included_domains": [], "excluded_domains": []}
      elif interaction.type == "planning_questions":
          return {
              "answers": [
                  {"question": q["question"], "answer": "Use your best judgment"}
                  for q in interaction.data["questions"]
              ]
          }
      return None

  result = client.deepresearch.wait(
      task.deepresearch_id,
      on_interaction=handle_interaction,
  )
  print(result.output)
  ```

  ```typescript TypeScript theme={null}
  import Valyu from "valyu-js";

  const client = new Valyu();

  const task = await client.deepresearch.create({
    query: "Analyze the competitive landscape of AI chip manufacturers",
    mode: "heavy",
    hitl: { planReview: true, sourceReview: true },
  });

  const result = await client.deepresearch.wait(task.deepresearch_id!, {
    onInteraction: async (interaction) => {
      if (
        interaction.type === "plan_review" ||
        interaction.type === "outline_review"
      ) {
        return { approved: true };
      } else if (interaction.type === "source_review") {
        return { included_domains: [], excluded_domains: [] };
      } else if (interaction.type === "planning_questions") {
        return {
          answers: interaction.data.questions.map((q: any) => ({
            question: q.question,
            answer: "Use your best judgment",
          })),
        };
      }
      return null;
    },
  });

  console.log(result.output);
  ```
</CodeGroup>

Both SDKs also provide convenience helpers for type-safe responses: `respond_planning_questions()` / `respondPlanningQuestions()`, `approve_plan()` / `approvePlan()`, `respond_source_review()` / `respondSourceReview()`, and `approve_outline()` / `approveOutline()`. See the [Python SDK](/sdk/python-sdk/deepresearch-hitl) and [TypeScript SDK](/sdk/typescript-sdk/deepresearch-hitl) references for details.

## Best practices

<CardGroup cols={2}>
  <Card title="Poll frequently during checkpoints" icon="clock">
    Use a 2-3 second poll interval when expecting HITL checkpoints. Switch to standard intervals (5-10s) during research phases.
  </Card>

  <Card title="Handle both statuses" icon="code">
    Always check for both `awaiting_input` and `paused` — the user experience is identical, only resume speed differs.
  </Card>

  <Card title="Enable selectively" icon="filter">
    Only enable checkpoints that add value for your use case. Each checkpoint adds latency equal to the user's response time.
  </Card>

  <Card title="Use with heavy or max modes" icon="gauge-high">
    HITL works best with `heavy` or `max` modes where the research is substantial enough to benefit from human guidance.
  </Card>
</CardGroup>
