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
| Checkpoint | Phase | When it fires |
|---|
planningQuestions | Pre-research | Before research — clarifying questions for the user |
planReview | Pre-research | After planning — user reviews the research plan |
sourceReview | Post-research | After research — user filters sources by domain |
outlineReview | Post-research | After 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
| Status | Meaning |
|---|
"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:
| Parameter | Type | Description |
|---|
taskId | string | The deep research task ID |
interactionId | string | From status.interaction.interaction_id |
response | Record<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
| Type | Description |
|---|
HitlConfig | Configuration with 4 optional boolean fields |
InteractionType | Union: "planning_questions" | "plan_review" | "source_review" | "outline_review" |
Interaction | Checkpoint payload with interaction_id, type, data, created_at, timeout_ms |
InteractionHistoryEntry | Completed checkpoint record with auto_continued, responded_at, response |
DeepResearchRespondResponse | Response from respond() with success, status, deepresearch_id |