Build a 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 20 to 50 times. AI SDK's WorkflowAgent runs the reasoning loop to search a query, read a source, record a finding, decide what to search next, and Workflow SDK runs that 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.
You'll build a Next.js app that hands a user's question to a WorkflowAgent running inside Vercel Workflows, performs a web search, which searches source pages, and extracts findings from each. Along the way, you'll learn to stream the agent's progress to the browser in real time and return a final report with citations.
To get started, clone this template’s GitHub repository, configure credentials, and run the local app:
Open http://localhost:3000 and submit:
You can also deploy it to Vercel with one click:
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.
If you are working with an AI coding agent, hand it the project and this prompt:
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 Connect, Vercel Workflows, Vercel Cron, AI Gateway, and Chat SDK. The plugin is optional; it is not required to use this template or for this guide.
The request flow looks like this:
Before you begin, make sure you have:
- Node.js 20 or later
- pnpm (or npm/yarn)
- A Vercel account
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.
Create a new Next.js app with create-next-app :
Then install the Workflow and AI SDK packages from the current canary release line:
Wrap your Next.js config with withWorkflow:
Keep the workflow state compact. Store findings, URLs, snippets, and short page text. Avoid writing the entire crawled websites into the workflow event log.
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 }).
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.
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.
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
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.
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.
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.
The final report is the workflow return value.
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.
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.
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.
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.
Check that the stream route preserves toolName on tool-output-available chunks and that the UI reads findings from extractFindings outputs.
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.
- Vercel Workflows documentation
- Workflow SDK documentation
- AI SDK documentation
- AI Gateway provider documentation
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.
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.
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.
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.
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.