This is a small “deep research” agent. A user submits a query in the web UI, a Temporal workflow orchestrates tool calls (search, relevance marking, page fetch), and the UI shows the live status and results.
- Temporal is the only orchestration system
- Each tool call maps to Temporal activity execution (the web search tool is a composite child workflow that still uses activities for each external call)
- Workflow code is deterministic
- Activities are isolated
- Frontend starts the workflow and shows status/results
- Frontend: Next.js (App Router)
- Backend: Next.js API routes
- Workflow engine: Temporal (Node SDK)
- Worker: Single unified worker (
deep-researchtask queue) with all activities and workflows - DB: Postgres with Prisma
Note: More details and diagrams are in
ARCHITECTURE.md.
- Clear separation: UI, API, workflow orchestration, activities, and persistence are isolated.
- State is stored as message blocks so the UI can show progress without extra schemas.
- Copy env file:
cp .env.example .envand fill in API keys. - Start Docker desktop
- Start Postgres:
npm run docker:up - Install deps:
npm install - Run Prisma migrate:
npm run prisma:migrate - Start Temporal:
temporal server start-dev - Start Mini Deep Research UI and Worker:
npm run dev
- User submits a query in
/chat/new. - Frontend creates a
conversationIdand POSTs to/api/conversations/:id/messages. - API stores the user message and starts
deepResearchWorkflowin Temporal. - Workflow:
- Runs a main LLM step to decide tool calls.
- For web search calls, spawns child workflows for each sub-query.
- Scores relevance and fetches page text as needed.
- Writes each step to the DB as structured message blocks.
- UI polls
/api/conversations/:iduntil the status iscompletedorfailed.
- Workflow orchestration:
apps/worker/src/workflows/deepResearchWorkflow.ts - Child search workflow:
apps/worker/src/workflows/subSearchWorkflow.ts - Activity timeouts/retries:
apps/worker/src/workflows/activityProxies.ts - Activities:
- Web search:
apps/worker/src/activities/executeWebSearch.ts - Relevance scoring:
apps/worker/src/activities/assessResultRelevance.ts - Page fetch:
apps/worker/src/activities/executeFetchWebPage.ts - DB writes:
apps/worker/src/activities/writeMessage.ts+updateConversationStatus.ts
- Web search:
- Workflow start API:
apps/web/app/api/conversations/[id]/messages/route.ts
- The workflow owns the plan and state: it decides which tools to call, in what order, and when to stop.
- Activities do side-effectful work (LLM calls, DB writes, web search/fetch). This keeps workflows deterministic.
- The web-search tool is a composite activity: the main workflow starts a child workflow that fans out to multiple activities. This keeps parallelism and retries inside a deterministic boundary.
- Web search sub-workflows run in parallel and use
Promise.allSettled. - If fewer than 50% of sub-searches succeed, the tool returns a structured error.
- The workflow marks the conversation as
failedonly if the workflow throws or ends without a valid final response.
- Activities have explicit timeouts and retry policies in
apps/worker/src/workflows/activityProxies.ts. - Web, LLM, and DB operations each use different retry settings to match their failure modes.
- Workflow code only calls activities and child workflows.
- IDs are based on
conversationIdand message index (no randomness in workflow code). - All side effects (LLM calls, API calls, DB writes) live in activities.
- Split workers by activity type and apply per-queue limits (LLM vs web vs DB).
- Add auth, rate limiting, input validation, and request size limits.
- Replace polling with push updates and add tracing/metrics.
- Implement realtime streaming via Redis pub/sub or stream (SSE).
- Add a new tool by creating an activity, adding a tool schema in
runMainLlmStep, and a UI block renderer. - Add new workflows (e.g., summarization, extraction) that reuse existing activities.
- Add new content block types without changing the DB schema.



