An autonomous AI research agent that searches the web, analyzes content with Claude, and builds a living knowledge base — powered by Render Workflows.
Contents: What you'll learn Architecture How it works Project structure Deploy Local development Environment variables Key patterns Tech stack Why Render? More templates Learn more
- How to define and orchestrate multi-step workflow tasks with
@renderinc/sdk - The stateless workflow pattern: compute in workflows, persistence in callers
- Fan-out parallelism with
Promise.all()across search and summarization tasks - Blueprint + Dashboard hybrid deployment for workflow services
- Building SSR pages with Hono JSX — no React, no frontend build step
graph TD
CRON["Cron Job (daily)"] -->|startTask| ORCH["orchestrate_research"]
WEB["Web Service"] -->|startTask| ORCH
ORCH --> EXA["search_exa"]
ORCH --> HN["search_hackernews"]
EXA --> SUM["summarize_source × N"]
HN --> SUM
SUM -->|return results| CRON
CRON -->|persist| DB[(PostgreSQL)]
WEB -->|read| DB
style ORCH fill:#6c47ff,color:#fff
style EXA fill:#2d3748,color:#fff
style HN fill:#2d3748,color:#fff
style SUM fill:#2d3748,color:#fff
Four services, one repo:
| Service | Type | Blueprint? | Role |
|---|---|---|---|
researcher-web |
Web Service | Yes | Hono SSR reader UI + JSON API |
researcher-cron |
Cron Job | Yes | Triggers the workflow daily via SDK |
researcher-db |
PostgreSQL | Yes | Sources, insights, run history |
researcher-workflow |
Workflow | Dashboard | Stateless research pipeline |
Note: Workflows are created via the Render Dashboard, not
render.yaml. The Blueprint deploys the web service, cron job, and database automatically. You'll create the workflow service separately in Step 2.
- The cron job fires daily at 1 PM UTC (or you trigger manually from the web UI)
- It calls
startTask("researcher-workflow/orchestrate_research")via the Render SDK, passing the research topic and config, and polls for completion - The workflow fans out:
search_exaandsearch_hackernewsrun as parallel subtasks - For each result,
summarize_sourcecalls Claude via Vercel AI SDK'sgenerateObjectto extract structured insights with guaranteed schema compliance - Results flow back to the cron job, which deduplicates by URL hash and persists new sources + insights to Postgres
- The web service reads from Postgres and renders the knowledge base as server-side HTML
Workflow tasks are pure compute — they receive arguments, do work, and return JSON-serializable results. No database connections, no side effects, no shared state. The caller (cron job or web service) owns persistence.
This pattern keeps workflows portable, testable, and cacheable. It matches the convention across all official Render workflow examples.
render-researcher-workflow/
├── render.yaml # Blueprint: web, cron, postgres
├── shared/
│ └── types.ts # Cross-service type contracts
├── web/ # Web + cron service package
│ ├── src/
│ │ ├── index.tsx # Hono server with JSX SSR
│ │ ├── trigger.ts # Cron entry point
│ │ ├── config.ts # Zod env validation
│ │ ├── db/
│ │ │ ├── schema.ts # Drizzle schema (3 tables)
│ │ │ └── client.ts # Postgres client
│ │ └── pages/
│ │ ├── layout.tsx # Dark-themed HTML shell
│ │ ├── home.tsx # Insight cards with tags + confidence
│ │ └── status.tsx # Run history + manual trigger
│ ├── drizzle.config.ts
│ ├── package.json
│ └── tsconfig.json
├── workflows/ # Workflow service package
│ ├── src/
│ │ ├── main.ts # orchestrate_research (root task)
│ │ ├── search.ts # search_exa + search_hackernews
│ │ ├── summarize.ts # summarize_source (Claude)
│ │ └── types.ts # Re-exports from shared/
│ ├── package.json
│ └── tsconfig.json
└── .env.example
The web package sets rootDir: ".." in tsconfig.json to import from shared/types.ts. The workflows package uses rootDir: "src" with inlined types for a flat dist/ layout.
- A Render account
- An Anthropic API key for Claude
- An Exa API key for web search
- A Render API key for triggering workflows from the cron job
Click the Deploy to Render button above, or:
# Fork and clone the repo
git clone https://github.com/starmorph/render-researcher-workflow.git
cd render-researcher-workflowThen create a new Blueprint in the Render Dashboard pointing to your repo. This deploys:
researcher-web— the Hono web serviceresearcher-cron— the daily cron triggerresearcher-db— managed PostgreSQL
Set these environment variables on the web and cron services:
| Variable | Value |
|---|---|
RENDER_API_KEY |
Your Render API key |
WORKFLOW_SLUG |
researcher-workflow (or whatever you name the workflow service) |
DATABASE_URLis injected automatically by the Blueprint.
In the Render Dashboard:
- Click New > Workflow
- Connect the same repository
- Set Root Directory to
workflows/ - Build Command:
pnpm install && pnpm run build - Start Command:
pnpm start - Add environment variables:
| Variable | Value |
|---|---|
ANTHROPIC_API_KEY |
Your Anthropic API key |
EXA_API_KEY |
Your Exa API key |
- Click Deploy Workflow
Once deployed, the cron job will trigger your first research run at the next scheduled time. Or hit the "Trigger Run" button on the /status page.
Use the Render CLI to run the workflow dev server locally:
cd workflows
pnpm install
render workflows dev -- pnpm startIn a separate terminal, verify your tasks registered:
render workflows tasks list --localYou should see four tasks: orchestrate_research, search_exa, search_hackernews, and summarize_source.
The web service requires a PostgreSQL connection:
cd web
pnpm install
cp ../.env.example .env # Fill in DATABASE_URL
npx drizzle-kit push # Create tables
pnpm run dev # Starts on http://localhost:3000| Variable | Services | Required | Description |
|---|---|---|---|
ANTHROPIC_API_KEY |
Workflow | Yes | Claude API key for summarization |
EXA_API_KEY |
Workflow | Yes | Exa neural search API key |
DATABASE_URL |
Web, Cron | Yes | Postgres connection string (auto-injected by Blueprint) |
RENDER_API_KEY |
Web, Cron | Yes | Render API key for triggering workflow runs |
WORKFLOW_SLUG |
Web, Cron | Yes | Name of your workflow service (e.g., researcher-workflow) |
RESEARCH_TOPIC |
Web, Cron | No | Search topic (default: agentic AI frameworks MCP autonomous workflows) |
PORT |
Web | No | Server port (default: 3000) |
The orchestrator task calls search and summarization subtasks in parallel. Each subtask runs in its own isolated container with independent retry logic:
// workflows/src/main.ts
const searchPromises = [
Promise.resolve(searchExa(topic, config.lookbackHours)),
Promise.resolve(searchHackerNews(topic, config.lookbackHours)),
];
const allSources = (await Promise.all(searchPromises)).flat();
const summaryPromises = topSources.map((source) =>
Promise.resolve(
summarizeSource(source.title, source.url, source.content, topic),
),
);
const summaries = await Promise.all(summaryPromises);
task()returnsTResult | Promise<TResult>, so wrap calls inPromise.resolve()forPromise.all()compatibility.
The summarize_source task uses Vercel AI SDK's generateObject with a Zod schema to guarantee the LLM returns valid, typed data — no prompt engineering for JSON formatting, no runtime parsing:
// workflows/src/summarize.ts
const result = await generateObject({
model: anthropic("claude-sonnet-4-20250514"),
schema: insightSchema, // Zod schema → guaranteed compliance
prompt: `Analyze this article...`,
});
return result.object; // Fully typed InsightSummaryBoth the cron job and web endpoint use startTask followed by polling — the SDK's runTask uses SSE internally, which has a known event-name mismatch in v0.5.1. The polling pattern is more reliable:
// Start the task (returns immediately)
const started = await render.workflows.startTask(
"researcher-workflow/orchestrate_research",
[topic, config],
);
// Poll until terminal status
let result;
for (let i = 0; i < 120; i++) {
await new Promise((r) => setTimeout(r, 2000));
const status = await render.workflows.getTaskRun(started.taskRunId);
if (status.status === "succeeded") { result = status; break; }
if (status.status === "failed") { throw new Error(`Workflow failed`); }
}The cron job exits with code 1 on failure so Render marks the run as failed. The web endpoint catches errors, persists them to the runs table, and redirects to
/status.
| Layer | Choice | Why |
|---|---|---|
| Runtime | Node.js, TypeScript | Render Workflows SDK is TypeScript-native |
| Web framework | Hono | ~14kb, JSX SSR without React or a build step |
| ORM | Drizzle | Type-safe SQL, zero-codegen schema |
| LLM | Claude Sonnet via Vercel AI SDK | generateObject for guaranteed structured output |
| Search | Exa + HN Algolia | Neural search + community signal |
| Validation | Zod | Shared schemas between LLM output and env config |
| Infra | Render Blueprint | One-click deploy for web, cron, and Postgres |
| Capability | What it means |
|---|---|
| Durable Execution | Tasks retry automatically with configurable backoff. Your agent never loses progress mid-run. |
| Scale to Zero | Pay nothing when idle. Per-second billing when running — no reserved instances. |
| Sub-Second Cold Starts | Each task runs in an isolated container that spins up in under a second. No cold start tax. |
| Parallel Fan-Out | Scatter work across hundreds of concurrent tasks with Promise.all(). The platform handles orchestration. |
| Built-in Observability | Logs, metrics, and per-task run status in the Dashboard — no Datadog or Grafana setup required. |
| Infrastructure as Code | Define your web service, cron job, and database in one render.yaml and deploy with a click. |
Read more about Render Workflows →
Deploy these to see other Render Workflows patterns in action:
| Template | What it demonstrates |
|---|---|
| Data Processor | Parallel processing of 400K records using hash-based sharding |
| SEO Audit | Parallel page crawling with 5-point SEO analysis |
| Voice Agent | LiveKit voice AI with backend workflow processing |
| Webhook Workflow | Async webhook processing without blocking responses |
| Blog Thumbnail Generator | Parallel AI image generation across multiple models |
- Render Workflows documentation
- Defining workflow tasks —
task(), retry, timeout, compute plans - Triggering task runs —
startTask,runTask, SDK client - TypeScript SDK reference
- Local development —
render workflows dev - Blueprint spec —
render.yamlreference - Limits and pricing — concurrency, timeouts, billing

