ResearchX
A deep-research studio where you bring your own model, storage, and memory; choose a research methodology before a single query fires; and every claim in the final report is anchored to a visited, verified, quotable source.
Status: MVP in development (milestone v0.2) — the approved v0.1 prototype frontend (mock-backed) plus the FastAPI backend, research engine, adapter layer, and trust loop. The frontend runs standalone on mock data; live backend mode arrives with v0.2 phase 3.
Quickstart
# frontend (prototype, mock-backed — works today)
cd frontend
corepack pnpm install
corepack pnpm dev # http://localhost:3000
# backend (MVP — uv manages Python 3.12 and the venv)
cd backend
uv sync # installs deps + editable researchx package
uv run uvicorn researchx.api.app:app --reload --port 8000
uv run pytest # full suite, zero API keys
Three clicks from cold start to initiated research.
Monorepo command map
| Layer | Install | Dev | Gates |
|---|---|---|---|
| backend (uv, Python 3.12) | uv sync |
uv run uvicorn researchx.api.app:app --reload --port 8000 |
uv run ruff check src tests · uv run mypy src · uv run pytest |
| frontend (pnpm 12.6) | corepack pnpm install |
corepack pnpm dev |
corepack pnpm typecheck · corepack pnpm lint · corepack pnpm test · corepack pnpm build |
Engine architecture (v0.2 P2)
The research engine is a hand-rolled asyncio state machine:
PLANNING → AWAITING_APPROVAL → SEARCHING → EXTRACTING → REFLECTING ─┐
(halt until ↑ depth loop (dials.depth) │
approved) └────────────────────────────┘
→ SYNTHESIZING → VERIFYING → DONE (+ CANCELLED / FAILED terminals)
- Durable state:
runs/<id>/state.jsonrewritten atomically after every transition; resume re-enters at the recorded cursor with the persisted plan - Event bus: typed events (
run_started,query_fired,source_visited,passage_extracted,state_changed,run_done,run_error) — trace per run - Visited-only constraint: the writer's context is the evidence store and nothing else
- Trust loop: SSRF-guarded liveness (live/stale/dead), quote anchoring (normalize + fuzzy ≥ 0.85, weak matches flagged), citation ledger JSON, exportable verification checklist, single-source vs corroborated confidence labels
Run artifacts per run: state.json, report.md, ledger.json, checklist.md.
Try it on fakes (no keys): uv run python -m researchx.engine.demo
Adversarial guarantees (v0.2 P4)
Every trust-loop claim is attacked by the adversarial suite (backend/tests/test_adversarial.py) — these are tests that try to fool the verifier, and their passing proves the defenses:
| Attack | Defense (proven) |
|---|---|
| Writer hallucinates a citation to a passage that was never stored | Unknown passage IDs count as unanchored claims in the ledger; the export gate fails the report |
| Fabricated quote (matches nothing) | Anchoring verdict: no match, flagged — never silently accepted |
| Near-miss quote (0.80–0.85 similarity) | Flagged weak_match in the ledger — surfaced, not accepted |
| Liveness probe redirected to a private/metadata IP | Per-hop redirect re-guarding classifies it dead (SSRF refusal) |
| Redirect chain ending at a gone page (410) | Classified dead |
| Oversized page (5MB) response | 2MB read cap — classifies without unbounded memory |
| Pause during the depth loop | Durable state persists; resume completes the run over the HTTP API |
| Cancel mid-loop | Terminal CANCELLED state persisted, never swallowed |
REQ-23 (≥99% citation liveness) is a per-run export gate enforced by the verification checklist, plus an opt-in keyed live tier (cd backend && uv run pytest -m live) — the zero-key CI suite never asserts it vacuously.
The E2E happy path (≤3 clicks: ask, choose, approve) is validated against the real FastAPI + engine stack, defaults only — not the mock (frontend/src/lib/api/e2e-real-stack.test.ts).
Adapter configuration (v0.2)
All keys resolve from env files only (.env, .env.secrets, .env.* — never the shell environment, never git):
| Variable | Scope | Used by |
|---|---|---|
OLLAMA_API_KEY |
model (cloud) | Ollama cloud endpoint https://ollama.com/v1; absent/keyless falls back to http://localhost:11434/v1 automatically |
OLLAMA_BASE_URL |
model | Overrides the default cloud base URL |
TAVILY_API_KEY |
search | Tavily default search; absent falls back to the keyless DuckDuckGo adapter (degraded mode — surfaced in the run trace) |
The test suite needs no keys: fake model/search adapters (REQ-31) drive every engine test.
Live mode (v0.2 P3)
The frontend runs mock-backed by default (works standalone, zero keys). To run the core research loop against the real backend:
# terminal 1 — backend (fake adapters by default; live adapters via env-file keys)
cd backend && uv run uvicorn researchx.api.app:app --reload --port 8000
# terminal 2 — frontend in live mode
cd frontend && RESEARCHX_API_MODE=live corepack pnpm dev
Live mode covers the core loop (Ask → Choose → approve plan → live trace → report + ledger); the library, settings persistence, suggestions, refine, and graph surfaces stay mock-backed until v0.3. The API surface: POST /api/runs, GET /api/methodologies, run lifecycle (approve/pause/cancel/resume), GET /api/runs/{id}/events (SSE), GET /api/config, report + ledger artifacts.
What's in the prototype
| Screen | What it does |
|---|---|
/ask |
One question box + one optional context box — plus two opt-in helpers below and beside them: open-question suggestions (follow-ups from your past runs, one click fills the box) and the Refine question dialogue (a few clarifying answers → a composed, sharper question). Neither ever auto-starts. |
/choose |
All 8 locked methodology cards — mock ETA, cost estimate, why-recommended, and the 12 methodology dials behind each card's Advanced drawer. |
/run/[id] |
Plan & Run: approve the research plan (sub-questions, source universe, outline), watch the live trace stream, pause/resume or cancel. |
/run/[id]/report |
Reading Room: the report with page-anchored citations — hover or focus any [n] marker for the exact quoted passage — plus the citation ledger and Markdown/JSON exports. |
/library |
Stored reports filed into auto-categorized folders (sortable, links into the Reading Room), the Connections panel (shared sources ⇄, related topics →), plus v2-preview project graph and task inbox. |
/settings |
API-key status (masked, never raw), the reports storage directory, methodology defaults with the resolved 12 dials, appearance, and about/version. |
The persistent shell (every screen): sidebar nav + adapter connection-status dots, top bar with the ⌘K navigation palette (screens + stored reports) and the avatar menu (local profile, Settings, About).
All data flows through a typed ApiClient interface backed by MockApiClient — the same components will talk to the FastAPI backend via the OpenAPI-generated client in the MVP milestone, with zero rewrites.
Scripts
Run inside frontend/:
| Command | Purpose |
|---|---|
pnpm dev |
Dev server on :3000 |
pnpm build |
Production build |
pnpm lint |
ESLint (flat config) |
pnpm typecheck |
tsc --noEmit (strict) |
pnpm test |
Vitest + Testing Library (jsdom) |
Repository layout
frontend/ Next.js 15 App Router, TypeScript strict, Tailwind v4, Vitest
.ciagent/ Pipeline state (roadmap, requirements, plans, checkpoints)
Milestone v0.2 (MVP: FastAPI backend, research engine, adapter layer, trust loop) begins only after this prototype is approved.