DocsPlatformArchitecture

Architecture, laid bare.

Essarion's three products are built on services that compose, not services that compete. This page is the wiring diagram — where data lives, where control lives, and what actually happens between the moment a query arrives and the moment a citation comes back.

§ 01The chained gateway

From the outside the platform looks like one API. From the inside it is two services in a line: a Next.js gateway in front, and a FastAPI research engine behind it.

The gateway — essarion_api — owns identity. It holds the users table, the keys table, the usage ledger, and the plan logic. Every external request hits it first. It authenticates the caller, decides whether the call is allowed, records the usage, and proxies the work upstream.

The engine — research-agent-deploy — owns the work. It runs the phases, scores the sources, drafts the citations, and persists the run. It does not ask who you are; the gateway has already decided that. It only accepts traffic that arrives with a valid service token, and it trusts the user attribution headers the gateway adds.

request shape
browser / SDK
   │
   ▼
essarion_api  (Next.js)        ← identity, keys, usage, plans
   │  Authorization: Bearer <service-token>
   │  X-Essarion-User-Id: <uuid>
   │  X-Essarion-Key-Id: <uuid>
   ▼
research-agent-deploy  (FastAPI) ← phases, sources, citations, runs

That separation is the load-bearing decision. It means the engine never has to know about pricing, sessions, or tenancy logic; the gateway never has to know about search providers, model routing, or the twelve-phase pipeline. Each side stays opinionated about exactly one job.

§ 02The research engine

The engine is a FastAPI service in Python. It owns the deep-research pipeline — up to twelve phases per run — and persists everything it produces.

State lives in Postgres: runs, steps, sources, citations, reasoning chunks. Web search is delegated to the You.com Smart Search API, fronted by a knowledge store and a response cache that answer from the shared corpus before any metered call is made. LLM calls are routed through OpenRouter as the primary provider with XAI as a fallback, so a single upstream outage doesn't take a run with it. Long-running operations expose two streams: WebSocket for fully bidirectional progress and SSE for one-way event feeds. The latter is what most callers use.

The pipeline runs phases in a defined order. A typical deep run touches: analyze (rewrite the question), plan (split into sub-queries), search (corpus first, then the web), scrape (fetch and extract), screen (score and prune), analyze (deep read), cite (build bibliographic records), synthesize (compose the final answer), with finishing passes around them. Every phase writes a step row; every chunk of reasoning writes a reasoning row; every URL writes a source row. By the time the run is done, the timeline is complete and queryable.

§ 03The corpus service

Sourcipedia is both the public reading experience and the compute host for the corpus auto-indexing pipeline. The worker drains the linker queue, the auto-generator drives pending topics through the research model, and a cleanup coordinator runs an always-on duplicate purger plus an opt-in nightly checker.

The two services share one Postgres database. The research engine owns the migrations and the corpus schema; the background compute that writes knowledge cards and concepts lives on Sourcipedia. The corpus HTTP surface is still proxied through the research engine, so the URL contract the client depends on never changed when the compute moved.

The relationship runs both ways. The corpus is written by ResearchAnything, and it is also read by it: because every knowledge card carries the citations behind it, the corpus doubles as a large index of vetted sources. The search orchestrator queries it before it queries the web, and when the corpus covers part of a target the paid round is steered away from domains it already supplied.

§ 04The science service

Essarion Science ships as a single FastAPI web service: /api/* is served by FastAPI and the built client SPA is served from the same process. Inside it sits the biomedical workbench — a reactive planner loop, a per-run notebook kernel, thirteen key-free database connectors, and the verification stack.

State lives in Postgres (or SQLite for small deployments): accounts, run ownership, papers, watches, and the falsification ledger. Model calls route through OpenRouter with XAI as the fallback, matching the research engine. Run progress streams over SSE as biomed.* events.

Science does not depend on the corpus service, and it does not use Google OAuth. It authenticates with its own accounts, optionally delegating to a shared Essarion ID provider — see Access & accounts.

§ 05Shared identity

One user account spans the platform. The mechanism depends on the caller.

The rule is strict: the engine never accepts caller-supplied user IDs without a valid service token. Identity is decided at the gateway, signed into the headers, and trusted only because the underlying transport is.

§ 06Data flow for a query

Concretely, this is what happens when a developer sends a query.

  1. The caller sends POST /api/v1/query to essarion_api with an esk_ key in the Authorization header.
  2. resolveApiKey() hashes the key, looks it up in Postgres, checks status, and returns the owning user. Invalid or revoked keys are rejected immediately.
  3. The gateway calls callResearchAnything() to proxy the request upstream. It attaches the service bearer token and the X-Essarion-User-Id / X-Essarion-Key-Id headers.
  4. The research engine accepts the request, creates a run row with a fresh request_id, and starts working through phases. Each phase persists steps, reasoning chunks, sources, and (eventually) citations.
  5. The engine returns a response envelope containing the answer, the citations, and the request_id.
  6. The gateway inserts a usage row keyed to the user and the key, then returns the envelope to the caller.
  7. Later, the caller can fetch the full timeline with GET /api/v1/runs/{request_id} — the same run, fully persisted.
envelope (abbreviated)
{
  "request_id": "...",
  "answer": "...",
  "citations": [ ... ],
  "sources":   [ ... ]
}

§ 07Storage

Three stores, each with one job.

§ 08Streaming

The platform exposes two stream shapes, each chosen for the workload it serves.

Server-Sent Events (SSE) back the query stream. A research run emits five event types in order: start (run created), message (incremental reasoning), sources (sources discovered and scored), reasoning (chunks of model thinking attached to a step), and final (the synthesized answer with citations). SSE is one-way and survives reconnection cleanly, which is what a long research run actually needs.

WebSocket backs the live event subscription on a research run — phase transitions, reasoning chunks, and partial outputs pushed as they happen, with the event-history endpoint available to backfill any gap after a disconnect.

TipFor most integrations, SSE is the right choice. See Streaming with SSE for a worked example.