Architecture

How GapSmith is built.

A pragmatic stack — Next.js for UX, a FastAPI engine for the heavy AI pipelines, Supabase for state, and x402 for AI-agent commerce on Solana.

Takeaway

Two-process model: a stateless web app handles auth, payments, and the agent API surface; a separate Python engine runs the long-running multi-LLM pipelines. Both share Supabase for state and Realtime progress.

System diagram

┌─────────────────────────────────────────────────────────────────────────┐
│  HUMAN BROWSER  ←─── Phantom wallet ──── Solana mainnet (USDC)         │
│       │                                       ▲                         │
│       ▼                                       │                         │
│  ┌──────────────────────────────┐    ┌─────────────────────────────┐    │
│  │  Next.js (Railway)            │    │  AI AGENT (any wallet)      │    │
│  │   /pricing  /scout  /forge    │    │   probe → 402 → sign → 200  │    │
│  │   /prove    /settings         │    └──────────┬──────────────────┘    │
│  │   /api/checkout/x402/verify   │               │                       │
│  │   /api/v1/* (agent API)       │◄──────────────┘                       │
│  └────┬───────────┬──────────────┘                                       │
│       │           │                                                      │
│       │ Supabase  │ ENGINE_URL                                           │
│       ▼           ▼                                                      │
│  ┌─────────┐ ┌─────────────────────────────────────────────────┐         │
│  │Supabase │ │  FastAPI engine (Railway, separate service)      │        │
│  │ Auth    │ │   /api/engine/scout   ─→ run_scout()             │        │
│  │ Postgres│ │   /api/engine/forge   ─→ run_ideation()          │        │
│  │ Storage │ │   /api/engine/prove   ─→ run_debate()            │        │
│  │ Realtime│ │   LiteLLM → Claude / GPT / Gemini / MiniMax …    │        │
│  └─────────┘ │   Tavily for search-less providers               │        │
│              │   Daily ingestion cron (RSS + Reddit + HN)       │        │
│              └─────────────────────────────────────────────────┘         │
└─────────────────────────────────────────────────────────────────────────┘

Components

1. Next.js web app

Single Next.js 15 app on Railway behind gapsmith.draftlabs.org. Owns:

  • All UX — pricing, settings, run pages, report viewers
  • Auth (Supabase, Google OAuth + magic-link)
  • Payment paths — Stripe webhooks and on-chain x402 verification
  • The Agent API surface (/api/v1/*) that wraps the engine

The web app never makes LLM calls itself. It dispatches to the engine and subscribes to Supabase Realtime for progress updates.

2. Python engine (FastAPI)

Separate Railway service running uvicorn engine.api:app. Three endpoints, one per pipeline, all backgrounded via FastAPI BackgroundTasks:

text
POST /api/engine/scout    → ~6 min,  ~$1.50  (3 sectors)
POST /api/engine/forge    → ~30 min, ~$0.45  (5 rounds + screening, MiniMax)
POST /api/engine/prove    → ~20 min, ~$5.50  (multi-agent debate, GPT-5.5)

Why a separate process: the pipelines run 30+ LLM calls per session, with adaptive token budgets and retry-on-rate-limit. Keeping them out of the Next.js request path means the web app stays snappy and we can scale the engine independently.

LLM routing goes through litellm so swapping models is one config change. Search routing: Gemini uses native googleSearchRetrieval; everyone else (Claude, GPT, MiniMax) falls back to Tavily injected into the prompt.

3. Supabase (Postgres + Auth + Realtime)

Schema in plain English:

  • auth.users — Supabase-managed
  • api_keys — encrypted BYOK keys, one per (user, provider)
  • purchases — one row per SKU bought (scout / forge / prove / bundle / cli)
  • usage_counters — 365-day rolling quota per purchase
  • scout_reports, forge_sessions, prove_sessions — pipeline state + final output
  • agent_jobs — async x402 job records (status, result, webhook)
  • x402_pending_payments — idempotency by tx hash

Realtime channels stream progress + progress_message column updates so the run pages animate live without polling.

4. x402 layer

The pay-per-call wrapper at src/lib/x402-server.ts — covered in detail on the x402 page. TL;DR: every /api/v1/* route is wrapped in withX402Payment(handler, { priceUsdcAtomic, sku }); the wrapper parses the X-Payment header, validates the on-chain SPL transfer with memo binding, and 402s when missing.

5. Daily ingestion cron

Every morning a scheduled task fetches:

  • 79 RSS sources across 20 industry sectors
  • 100 community-pain sources (Reddit, HN, Lobsters, GitHub Issues, Twitter)

Stored in data/feeds/news/ + data/feeds/pain/, with confidence scoring (A/B/C/D) and per-sector deduplication. Scout runs read this daily snapshot — that's how a $1.50 Scout report covers ~70 articles + ~250 pain signals: ingestion is amortized.

Deployment

Both services live on Railway:

  • web — Node 20 + Next.js 15, deploys on every push to main
  • engine — Python 3.12 + FastAPI, separate Dockerfile in engine/Dockerfile

DNS is Cloudflare (gapsmith.draftlabs.org → Railway CNAME). Email is Cloudflare email-routing forwarding gapsmith@draftlabs.orgto the founder's personal inbox.

Security & trust

  • BYOK keys are AES-GCM encrypted at rest with ENCRYPTION_SECRET (Railway env). Keys are decrypted only in the start-route process, passed once over the internal Railway network to the engine, and never logged.
  • x402 verification is server-side. The browser pre-flight only checks SOL/USDC balance via Helius. The actual payment validation re-fetches the tx from the Solana RPC, asserts source/destination/amount/memo, and writes an idempotency row keyed on tx_hash UNIQUE.
  • Row-level security on every user-scoped table. Service-role bypass only in webhooks (Stripe + agent_jobs status), not in user-facing routes.
Web app lives under src/, engine under engine/. Source access available on request — see /contact.