Architecture
Flow
text
question ─▶ POST /api/research ─▶ createSession ─▶ runSession (in-process)
│
┌─────────────────────────────────────────┤
│ orchestrator loop (packages/agent) │
│ Claude decides: search / fetch / │
│ read_source / claims / evidence / │
│ contradictions / finish │
│ App enforces: budgets, schemas, SSRF │
└───────────────┬─────────────────────────┘
▼
ResearchState (packages/research)
every mutation = 1 DB write + 1 event
▼
PostgreSQL (packages/db)
▼
GET /api/research/:id/stream (SSE, Last-Event-ID replay)
▼
event-sourced UI (apps/web)Key decisions
- No fixed pipeline. The model chooses strategy via tool use; the app owns safety
(budgets in
packages/shared/src/budgets.ts, zod-validated tool inputs, SSRF guard). - ResearchState is durable and lives in PostgreSQL, outside the model's context window.
The
ResearchStateservice is the single mutation path: state write and event append always happen together, so the UI can never show fabricated progress. - Event-sourced UI. The client rebuilds its entire view by replaying the event stream
from seq 0 — the same mechanism gives live streaming, reconnection (
Last-Event-ID), and full session replay for debugging. - Citations are mechanical. At synthesis time, sources carrying evidence receive stable
indices (
sources.assignCitationIndices, ordered by first evidence use). The synthesis model may only use the[n]markers provided; the UI links them back to sources. - In-process runner (
apps/web/lib/runner.ts): sessions run inside the Next.js server process for the MVP. A queue (e.g. Redis-backed) only gets added when scale demands it.
Model roles
Configured via env (CLAUDE_ORCHESTRATOR_MODEL, CLAUDE_SYNTHESIS_MODEL, …), all defaulting
to claude-opus-5. The researcher/verifier roles are reserved for the post-MVP multi-agent
phase (Explorer, Skeptic, Verifier, …) which only begins once the single-orchestrator engine
is demonstrably reliable.