Build a web research agent that searches the web, extracts findings from each source, and returns a cited report without losing progress when a crash, restart, or deployment interrupts a run that can loop many times. You’ll use AI SDK with Workflow SDK to run a research loop not as one long request but as a sequence of durable, journaled steps, so the search results and findings already recorded survive any interruption.
In this guide, you'll build a Next.js app that hands a user's question to an agent running inside Vercel Workflows, performs web searches across various sources, and extracts findings from each. Along with this, you'll learn to stream the agent's progress to the browser in real time and return a final report with citations.
Deploy the template now, or read on for a deeper look at how it all works.
Durable web research agent with Workflow SDK
Ask a question and the app researches the web, fetches source pages, extracts cited findings, and synthesizes a grounded research brief while streaming each step to the browser in real time.
This guide covers the core implementation. The AI assistant prompt above covers all the details your coding agent needs, and you can see the full implementation in the template repository.
Copy link to headingQuick start with an AI coding agent
If you are working with an AI coding agent, hand it the project and this prompt:
I want to build a durable web research agent using the durable web research agent template. Read the setup instructions at https://agent-resources.dev/durable-web-research-agent-template.md and follow them. They will cover deploying the template, building with the Workflow SDK, how everything works overall, and more.
Copy link to headingVercel Plugin
Turn your agent into a Vercel expert with this plugin. It gives your coding agent current knowledge of the Vercel products this template uses, including Vercel Workflows, AI Gateway, and AI SDK. The plugin is optional; it is not required to use this template or for this guide.
Copy link to headingOverview
The request flow looks like this:
Copy link to headingPrerequisites
Before you begin, make sure you have:
- Node.js 22 or later
- pnpm (or npm/yarn)
- A Vercel account
Copy link to headingConfigure credentials
For local development, link the Vercel project and pull environment variables:
AI Gateway authenticates requests using Vercel OIDC tokens, which Vercel generates and links to your project automatically.
Copy link to headingCreate the project
Create a new Next.js app with create-next-app :
Then install the Workflow and AI SDK packages:
Copy link to headingConfigure Next.js for Workflow
Wrap your Next.js config with withWorkflow:
Copy link to headingDefine the data types
Keep the workflow state compact. Store findings, URLs, snippets, and short page text. Avoid writing the entire crawled websites into the workflow event log.
Copy link to headingCreate durable research steps
The Workflow SDK gives the app durable control flow and the AI SDK gives the app model calls, AI Gateway tools, and structured outputs. Implement the two branches, as follows:
- Put
generateText(),fetch(), Gateway search tools, and any other side effects inside"use step"functions. - Keep the
"use workflow"function focused on orchestration, loops over already-returned data, and calls to step functions. - Return compact typed objects from steps so Workflow can journal them and replay the run without calling the model again.
The research steps do the work with side effects:
searchWeb()calls an AI Gateway search tool.extractFindingsFromPages()calls an AI Gateway model withOutput.object.fetchSourcePage()helps when your search tool returns URLs without enough page text.
The example below uses AI Gateway's parallelSearch tool. Search belongs in a step because it is external I/O: it can fail, retry, rate-limit, or return different results over time. Once the step completes, Workflow records the selected sources, and replay can continue from those recorded sources instead of searching again.
parallelSearch returns source-oriented search results with excerpts that are already useful to an LLM. If your project prefers Perplexity-backed search, swap in gateway.tools.perplexitySearch({ maxResults: 5 }).
Copy link to headingFetch pages when search only returns URLs
Some search tools return URLs and snippets but not enough source text. In that case, add a fetchSourcePage step and call it before extraction. Keep it as a workflow step because page fetches are side effects and can fail or need retries.
Copy link to headingExtract findings with structured output
Extraction is separate from search on purpose: search finds candidate source material and extraction decides which claims are useful enough to keep and turns messy page text into compact, typed research artifacts.
Keeping extraction separate gives you three benefits:
- The final report can depend on a stable
Finding[]instead of raw search prose. - The UI can stream findings as soon as each page is processed.
- If synthesis fails, Workflow reuses the already-extracted findings instead of asking the search model to recreate them.
Use Output.object() so the extraction step returns predictable data.
The URL filter after generation prevents the model from inventing citations that were not in the fetched source set.
Copy link to headingOrchestrate the workflow
The workflow body should mostly coordinate steps. It can loop over the bounded search results, but it should not perform network requests or model calls directly.
This workflow also writes progress events to getWritable(). Those events look like AI SDK model-call stream parts, so the UI can render tool starts and finishes.
To keep the run predictable even if a search provider returns more results than requested, this adds the following guardrails:
- the Gateway search tool receives
maxResults: 5 - the workflow slices
searchResult.sourcestoMAX_SOURCE_PAGES
Copy link to headingSynthesize the final report
Synthesis should read only the extracted findings. This keeps the report grounded in durable evidence instead of the model's memory of earlier search text.
That design also makes failure recovery cheaper. If the final report step retries, it receives the same Finding[] from the event log and does not repeat search or extraction. If there are no findings, return an explicit empty-state report instead of asking the model to improvise.
The deterministic citation fallback matters. Structured output improves shape, but it does not guarantee the model will populate every optional-looking field the way your UI expects.
Copy link to headingStart the workflow from an API route
Start the workflow with a POST request and return the run ID. This route returns immediately. The browser can then subscribe to the readable stream and poll status by runId.
Copy link to headingStream workflow progress
Workflow streams are read by run ID. The code below converts the workflow stream into AI SDK UI chunks and sends them as Server-Sent Events.
The custom transform preserves toolName on output chunks. The UI needs that metadata to decide which tool outputs contain findings.
Copy link to headingRead status and the final report
The final report is the workflow return value.
Copy link to headingConnect a client
The backend is intentionally UI-agnostic. A React console, a CLI, or an internal dashboard can all use the same three contracts:
| Endpoint | Client responsibility |
|---|---|
POST /api/research | Send { question }, receive { runId } |
GET /api/readable/:runId | Subscribe to streamed UI chunks |
GET /api/run/:runId | Poll status and read the final returnValue |
The stream route already converts workflow events into AI SDK UI chunks. A client does not need to know how Workflow stores the run. It only needs to interpret these events:
tool-input-availablemeans a workflow phase started, such assearchWeb,fetchPage, orextractFindings.tool-output-availablemeans that phase finished.tool-output-availablewithtoolName: "extractFindings"contains{ findings: Finding[] }.finishmeans the readable stream ended.
Dedupe streamed findings by toolCallId and finding index. A reconnect or retry can replay chunks that the browser has already seen.
You now have a web research agent ready to answer your questions. The complete template includes a polished frontend with real-time streaming for findings and workflow steps. To get started, clone this template’s GitHub repository, configure credentials, and run the local app:
Open http://localhost:3000 and submit:
Copy link to headingTroubleshooting
Copy link to headingSearch returns no source pages
Inspect result.toolResults from the Gateway search call. Gateway tools can differ in output shape. Normalize results, sources, excerpts, and URLs before passing data to extraction.
Copy link to headingSearch provider access is restricted
Use a Gateway-executed search tool that is enabled for your team. If parallelSearch is not available, try perplexitySearch, or configure provider access in AI Gateway.
Copy link to headingThe final report has no citations
Make sure synthesis receives extracted findings, not raw search text. Add the deterministic citation fallback shown above so the UI always has source URLs when findings exist.
Copy link to headingLive findings stay empty
Check that the stream route preserves toolName on tool-output-available chunks and that the UI reads findings from extractFindings outputs.
Copy link to headingSteps rerun on every replay
Move side effects out of the workflow body and into "use step" functions. Native fetch, provider calls, timestamps, random values, and writable stream writes should be inside steps.
Copy link to headingResources
- Vercel Workflows documentation
- Workflow SDK documentation
- AI SDK documentation
- AI Gateway provider documentation
Copy link to headingFAQ
Do I need provider-specific API keys for search or model calls?
No. The guide uses AI Gateway model strings and Gateway search tools, so authentication goes through the linked Vercel project and OIDC. You do not need to add OpenAI, Anthropic, or Perplexity keys to the app.
Do I need a database to store findings?
Not for this example. Findings are step outputs in the workflow run, so they are durable for the life of that run. Add a database only if you want long-term storage, cross-run search, or product analytics.
What happens if the workflow crashes halfway through a research run?
Completed steps are already recorded in the Workflow event log. When the app resumes, Workflow can continue from the last completed step instead of starting the whole research run over.
Why use a deterministic workflow pipeline instead of an autonomous agent loop?
A pipeline gives the backend a predictable budget and failure model. The model still performs search, extraction, and synthesis, but Workflow owns the order of operations: search once, process up to five sources, extract findings, then write the report.
Why is extraction separate from search?
Search finds candidate sources; extraction turns source text into durable findings. Keeping them separate lets the UI stream findings as they are recorded, and lets retries reuse extracted findings without repeating search.