Skip to main content
Enable checkpoints that pause deep research at key decision points, allowing users to review and guide the research process.
For conceptual overview, checkpoint response shapes, and lifecycle details, see the HITL Guide. This page focuses on TypeScript SDK usage.
HITL is only available for individual deep research tasks. It is not available for batch requests.

Quick start

import Valyu from "valyu-js";

const client = new Valyu();

// Create a task with all checkpoints enabled
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!;
console.log(`Task created: ${taskId}`);

Enabling HITL

Pass a HitlConfig object to create(). All fields are optional booleans (default false):
// Enable selective checkpoints
const task = await client.deepresearch.create({
  query: "...",
  mode: "heavy",
  hitl: {
    planningQuestions: true,
    sourceReview: true,
  },
});

// Enable all checkpoints
const task2 = await client.deepresearch.create({
  query: "...",
  mode: "heavy",
  hitl: {
    planningQuestions: true,
    planReview: true,
    sourceReview: true,
    outlineReview: true,
  },
});

Available checkpoints

CheckpointPhaseWhen it fires
planningQuestionsPre-researchBefore research — clarifying questions for the user
planReviewPre-researchAfter planning — user reviews the research plan
sourceReviewPost-researchAfter research — user filters sources by domain
outlineReviewPost-researchAfter source review — user reviews the report outline
Checkpoints fire in order. Only enabled checkpoints fire.

Polling for checkpoints

Poll with status() and check for awaiting_input or paused:
while (true) {
  const status = await client.deepresearch.status(taskId);

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

    // Build and send response (see sections below)
    const response = buildResponse(type, data);
    await client.deepresearch.respond(taskId, interaction_id, response);
  } else if (
    status.status === "completed" ||
    status.status === "failed" ||
    status.status === "cancelled"
  ) {
    break;
  }

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

Status values

StatusMeaning
"awaiting_input"Checkpoint active, container holding capacity, fast resume
"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

Responding to checkpoints

respond()

const result = await client.deepresearch.respond(
  taskId,
  "int_abc123",
  { approved: true }
);
console.log(result.status); // "running" or "queued"
Parameters:
ParameterTypeDescription
taskIdstringThe deep research task ID
interactionIdstringFrom status.interaction.interaction_id
responseRecord<string, any>Response data matching the checkpoint type (see below)
Returns: DeepResearchRespondResponse with success, status, deepresearch_id, error.

Planning questions response

// interaction.data contains:
// {
//   questions: [
//     { question: "What regions?", context: "..." },
//     { question: "Specific competitors?" }
//   ]
// }

await client.deepresearch.respond(taskId, interaction_id, {
  answers: [
    { question: "What regions?", answer: "North America and EU" },
    { question: "Specific competitors?", answer: "NVIDIA, AMD, Intel" },
  ],
});

Plan review response

// interaction.data contains:
// {
//   plan: "I'll research this topic by...",
//   estimated_steps: 15,
//   research_areas: ["Market size", "Competitive landscape", ...]
// }

// Approve
await client.deepresearch.respond(taskId, interaction_id, {
  approved: true,
});

// Request modifications
await client.deepresearch.respond(taskId, interaction_id, {
  approved: false,
  modifications: "Focus more on supply chain analysis",
});

Source review response

// interaction.data contains:
// {
//   domains: [
//     {
//       domain: "sec.gov",
//       source_count: 8,
//       avg_relevance_score: 0.87,
//       sources: [{ source_id: 1, title: "...", url: "...", relevance_score: 0.92 }],
//       ai_recommendation: "include"
//     },
//     ...
//   ],
//   total_sources: 42
// }

await client.deepresearch.respond(taskId, interaction_id, {
  included_domains: ["sec.gov", "plos.org"],
  excluded_domains: ["example.com"],
});
Domains not listed fall back to the AI recommendation. Pass empty arrays [] to accept all AI recommendations.

Outline review response

// interaction.data contains:
// {
//   outline: "1. Introduction\n2. Market Analysis\n...",
//   sections: [
//     { title: "Introduction", description: "...", estimated_length: "short" },
//     { title: "Market Analysis", description: "...", estimated_length: "long" },
//     ...
//   ]
// }

// Approve
await client.deepresearch.respond(taskId, interaction_id, {
  approved: true,
});

// Request modifications
await client.deepresearch.respond(taskId, interaction_id, {
  approved: false,
  modifications: "Add a regulatory risks section after Market Analysis",
});

Complete example

import Valyu from "valyu-js";

const client = new Valyu();

async function runWithHitl() {
  // Create task with selective checkpoints
  const task = await client.deepresearch.create({
    query: "Due diligence report on renewable energy company SunPower",
    mode: "heavy",
    hitl: {
      planReview: true,
      sourceReview: true,
    },
  });

  const taskId = task.deepresearch_id!;
  console.log(`Task: ${taskId}`);

  while (true) {
    const status = await client.deepresearch.status(taskId);
    console.log(`Status: ${status.status}`);

    if (
      (status.status === "awaiting_input" || status.status === "paused") &&
      status.interaction
    ) {
      const { type, data, interaction_id } = status.interaction;

      if (type === "plan_review") {
        console.log(`\nResearch plan:\n${data.plan}`);
        console.log(`Areas: ${data.research_areas?.join(", ")}`);

        // Auto-approve with a tweak
        await client.deepresearch.respond(taskId, interaction_id, {
          approved: false,
          modifications: "Include analysis of their patent portfolio",
        });
      } else if (type === "source_review") {
        console.log(`\nFound ${data.total_sources} sources:`);
        for (const domain of data.domains) {
          console.log(
            `  ${domain.domain}: ${domain.source_count} sources (AI: ${domain.ai_recommendation})`
          );
        }

        // Accept AI recommendations
        await client.deepresearch.respond(taskId, interaction_id, {
          included_domains: [],
          excluded_domains: [],
        });
      }
    } else if (status.status === "completed") {
      console.log(`\nReport complete! Cost: $${status.cost}`);
      console.log(String(status.output).substring(0, 500));
      break;
    } else if (status.status === "failed" || status.status === "cancelled") {
      console.log(`Task ${status.status}: ${status.error}`);
      break;
    }

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

runWithHitl();

HITL history

After checkpoints complete, the hitl_history field on the status response contains a record of each interaction:
const status = await client.deepresearch.status(taskId);

if (status.hitl_history) {
  for (const entry of status.hitl_history) {
    console.log(`Checkpoint: ${entry.type}`);
    console.log(`  Auto-continued: ${entry.auto_continued}`);
    if (entry.responded_at) {
      const responseTime = (entry.responded_at - entry.created_at) / 1000;
      console.log(`  Response time: ${responseTime.toFixed(1)}s`);
    }
  }
}

Types reference

TypeDescription
HitlConfigConfiguration with 4 optional boolean fields
InteractionTypeUnion: "planning_questions" | "plan_review" | "source_review" | "outline_review"
InteractionCheckpoint payload with interaction_id, type, data, created_at, timeout_ms
InteractionHistoryEntryCompleted checkpoint record with auto_continued, responded_at, response
DeepResearchRespondResponseResponse from respond() with success, status, deepresearch_id