You can get the full code for this example by running:

pickaxe create deep-research-agent

And selecting the “Deep Research Agent” template.

Codewalk

This research agent runs multiple search iterations, extracts facts from sources, and evaluates when it has enough information to provide a comprehensive answer. It uses prompt chaining for sequential research steps and evaluator-optimizer patterns for quality assessment.

Client
Research Agent
Search Planner
Web Search
Fact Extractor
Quality Judge
research query
plan searches
search queries
execute search
search results
extract facts
structured facts
evaluate completeness
continue/summarize
comprehensive report

Architecture Overview

The research agent runs up to 3 iterations of search, fact extraction, and evaluation. Each iteration plans new searches based on what information is still missing, then judges whether the collected facts are sufficient before continuing or generating a final summary.

Implementation

This example shows how to build research agents that improve their results through multiple iterations, using quality gates to determine when they have enough information.

Research Agent

import { pickaxe } from "@hatchet-dev/pickaxe";
import z from "zod";
import { planSearchTool } from "@tools/plan-search";
import { searchTool } from "@tools/search";
import { websiteToMdTool } from "@tools/website-to-md";
import { extractFactsTool } from "@tools/extract-facts";
import { judgeFactsTool } from "@tools/judge-facts";
import { judgeResultsTool } from "@tools/judge-results";
import { summarizeTool } from "@tools/summarize";

const ResearchInput = z.object({
  query: z.string().describe("The research question or topic to investigate"),
});

const ResearchOutput = z.object({
  summary: z.string().describe("Comprehensive research summary with citations"),
  sources: z
    .array(
      z.object({
        index: z.number(),
        url: z.string(),
        title: z.string(),
      })
    )
    .describe("Sources used in the research"),
  iterations: z.number().describe("Number of research iterations performed"),
});

export const researchAgent = pickaxe.agent({
  name: "deep-research-agent",
  inputSchema: ResearchInput,
  outputSchema: ResearchOutput,
  description: "Conducts comprehensive research with iterative refinement",
  executionTimeout: "15m",
  fn: async (input, ctx) => {
    let allFacts: any[] = [];
    let allSources: any[] = [];
    let iteration = 0;
    const maxIterations = 3;

    while (iteration < maxIterations) {
      iteration++;
      ctx.logger.info(`Starting research iteration ${iteration}`);

      // Plan searches based on existing knowledge gaps
      const searchPlan = await planSearchTool.run({
        query: input.query,
        existingFacts: allFacts,
        iteration,
      });

      // Execute planned searches
      for (const searchQuery of searchPlan.queries) {
        const searchResults = await searchTool.run({
          query: searchQuery,
          context: input.query,
        });

        // Convert websites to markdown and extract facts
        for (const result of searchResults.results) {
          if (allSources.some((s) => s.url === result.url)) continue;

          const markdown = await websiteToMdTool.run({
            url: result.url,
            context: input.query,
          });

          const facts = await extractFactsTool.run({
            content: markdown.content,
            query: input.query,
            sourceUrl: result.url,
            sourceTitle: result.title,
          });

          allFacts.push(...facts.facts);
          allSources.push({
            index: allSources.length + 1,
            url: result.url,
            title: result.title,
          });
        }
      }

      // Evaluate if we have sufficient information
      const factJudgment = await judgeFactsTool.run({
        query: input.query,
        facts: allFacts,
      });

      if (factJudgment.sufficient || iteration >= maxIterations) {
        break;
      }
    }

    // Generate comprehensive summary
    const summary = await summarizeTool.run({
      query: input.query,
      facts: allFacts,
      sources: allSources,
    });

    // Final quality check
    const resultJudgment = await judgeResultsTool.run({
      query: input.query,
      summary: summary.summary,
      sources: allSources,
    });

    return {
      summary: summary.summary,
      sources: allSources,
      iterations: iteration,
    };
  },
});

Key Features

Multiple Iterations: Runs up to 3 research cycles, planning each search based on what information is missing from previous rounds.

Quality Gates: Uses the evaluator-optimizer pattern to judge whether collected facts are sufficient before continuing to the next iteration.

Source Tracking: Prevents duplicate sources and maintains citation indexes throughout the research process.

Gap-Based Planning: Each search iteration focuses on filling specific knowledge gaps rather than collecting redundant information.

Usage

const result = await researchAgent.run({
  query: "What are the latest developments in quantum computing hardware?",
});

console.log(`Research completed in ${result.iterations} iterations`);
console.log(`Sources consulted: ${result.sources.length}`);
console.log(`Summary: ${result.summary}`);

This example combines prompt chaining for sequential research steps with evaluator-optimizer for quality assessment, showing how to build research agents that improve through iteration.