Skip to main content
This example project is derived from the brilliant deep research guide by Nico Albanese.

Overview

This full-stack Next.js project is an intelligent deep research agent that autonomously conducts multi-layered web research, generates a comprehensive structured report, converts it to a PDF, and uploads it to cloud storage — all with real-time progress updates on the frontend. Tech stack:

GitHub repo

View the deep research agent repo

Fork this repo to use it as a starting point for your own research agent.

How the agent works

Task orchestration

Three Trigger.dev tasks are chained together using triggerAndWait, each handling a distinct phase of the pipeline:
  1. deepResearchOrchestrator — coordinates the entire research workflow
  2. generateReport — converts accumulated research into a structured HTML report using GPT-4o
  3. generatePdfAndUpload — converts the HTML to PDF with LibreOffice and uploads to R2
src/trigger/deepResearch.ts
import { task, metadata } from "@trigger.dev/sdk";
import { generateReport } from "./generateReport";
import { generatePdfAndUpload } from "./generatePdfAndUpload";

export const deepResearchOrchestrator = task({
  id: "deep-research",
  maxDuration: 300, // 5 minutes
  run: async (payload: { query: string; depth?: number; breadth?: number }) => {
    const depth = payload.depth ?? 2;
    const breadth = payload.breadth ?? 2;

    metadata.set("status", { progress: 0, label: "Starting research..." });

    // Recursively research the query
    const { learnings, sources } = await deepResearch({
      query: payload.query,
      depth,
      breadth,
    });

    metadata.set("status", { progress: 75, label: "Generating report..." });

    // Chain to the report generation task
    const reportResult = await generateReport.triggerAndWait({
      query: payload.query,
      learnings,
      sources,
    });

    if (!reportResult.ok) throw new Error("Report generation failed");

    metadata.set("status", { progress: 90, label: "Converting to PDF..." });

    // Chain to the PDF generation task
    const pdfResult = await generatePdfAndUpload.triggerAndWait({
      html: reportResult.output.html,
      filename: `research-${Date.now()}.pdf`,
    });

    if (!pdfResult.ok) throw new Error("PDF generation failed");

    metadata.set("status", { progress: 100, label: "Done!" });

    return { pdfUrl: pdfResult.output.url, sources };
  },
});

Recursive research algorithm

The core research logic uses a recursive depth-first approach. At each level the agent:
  1. Generates multiple search queries from the current question using GPT-4o
  2. Searches the web via the Exa API
  3. Evaluates the relevance of each result
  4. Extracts key learnings and follow-up questions
  5. Recurses on the follow-up questions at the next depth level
Level 0: "AI safety in autonomous vehicles"
├── Level 1 (breadth=2):
│   ├── "Machine learning safety protocols in self-driving cars"
│   │   └── Follow-up: "How do neural networks handle edge cases?"
│   └── "Regulatory frameworks for autonomous vehicle testing"
│       └── Follow-up: "What are current safety certification requirements?"
└── Level 2 (breadth=1, depth limit):
    ├── "Neural network edge case handling" → STOP
    └── "Safety certification requirements" → STOP

Real-time progress with Trigger.dev Realtime

The frontend uses the useRealtimeTaskTrigger hook to both trigger the task and subscribe to progress updates without polling:
src/components/DeepResearchAgent.tsx
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { deepResearchOrchestrator } from "../trigger/deepResearch";

export function DeepResearchAgent({ accessToken }: { accessToken: string }) {
  const triggerInstance = useRealtimeTaskTrigger<typeof deepResearchOrchestrator>(
    "deep-research",
    { accessToken }
  );

  const status = triggerInstance.run?.metadata?.status;
  const progress = status?.progress ?? 0;
  const label = status?.label ?? "";

  async function handleSubmit(query: string) {
    await triggerInstance.submit({ query, depth: 2, breadth: 2 });
  }

  return (
    <div>
      <button onClick={() => handleSubmit("Your research question here")}>
        Start research
      </button>
      {triggerInstance.run && (
        <div>
          <p>{label}</p>
          <progress value={progress} max={100} />
        </div>
      )}
    </div>
  );
}
As research progresses, the task updates its metadata and the frontend receives each update automatically:
src/trigger/deepResearch.ts
metadata.set("status", {
  progress: 25,
  label: `Searching the web for: "${query}"`,
});

Relevant code files

FileDescription
src/trigger/deepResearch.tsCore recursive research logic and orchestrator task
src/trigger/generateReport.tsConverts research data into a structured HTML report
src/trigger/generatePdfAndUpload.tsConverts HTML to PDF with LibreOffice and uploads to R2
src/components/DeepResearchAgent.tsxFrontend form and real-time progress display

Learn more

Trigger.dev Realtime

Subscribe to runs and stream live progress to your frontend

React hooks

Trigger a task and subscribe to its updates from a React component

Run metadata

Store and broadcast arbitrary data from inside a running task

triggerAndWait

Chain tasks together and wait for each to complete before continuing

Build docs developers (and LLMs) love