← docs
docs · v1 · last updated 2026-07

The Architecture of This Site

v1 · last updated 2026-07 · last updated 2026-07published

In plain terms — This website demonstrates Harsh's work instead of merely describing it. A chat agent answers from his real projects and cites its evidence, a Fit tool reads a job description and gives an honest assessment, and a connector brings the same knowledge into another AI. It is a chef handing you a tasting menu instead of a printed résumé.


The thesis

patelharsh.dev is an instance of the work, not a description of it. The site does not merely claim that Harsh builds grounded agent systems: it exposes a grounded agent, an evidence-constrained job analysis, and the same capabilities as tools inside an external AI client. The public architecture is part of the product because the implementation is evidence for the claim.

This page is the stable implementation reference. The companion post, I Productized Myself, tells the build story; this page documents the system that exists in the repository.

System architecture

flowchart LR
  visitor["Visitor"] --> app["Next.js on Vercel"]
  client["Claude client"] --> app
  app --> chat["/api/chat"]
  app --> fit["/api/fit"]
  app --> mcp["/api/mcp"]
  chat --> router["OpenRouter (Claude Sonnet and Haiku)"]
  chat --> voyage["Voyage embeddings"]
  chat --> database["Supabase Postgres + pgvector"]
  fit --> router
  fit --> voyage
  fit --> database
  mcp --> router
  mcp --> voyage
  mcp --> database

The App Router keeps static content and three server-side product surfaces in one deployable. /api/chat rewrites, retrieves, and streams an answer; /api/fit validates a job description and streams a structured report; and /api/mcp mounts the same retrieval and Fit capabilities on Streamable HTTP. Model and database credentials are parsed only in a server-only environment module and are never exposed through public environment variables.

The initial migration is deliberately small. corpus_chunks stores content, source metadata, a SHA-256 content hash, and a 1,024-dimensional embedding. An HNSW index serves cosine search and a GIN index serves English full-text search. The remaining tables hold web or MCP conversations, messages and citations, Fit Reports, feedback, events, and fixed-window rate-limit counters; row-level security is enabled on all of them. See the schema.

The diagram renderer itself follows a recorded constraint: it waits for fonts and uses SVG text labels rather than HTML foreignObject labels, so the engine that measures labels is also the engine that draws them (ADR 0028).

RAG grounding design

Corpus and ingestion

The corpus is assembled from the repository's architecture docs and writing, plus curated facts, the structured resume, and approved question-and-answer seeds. Those sources are the site content and the agent's evidence; there is no separate hand-maintained search copy. Unpublished MDX remains ingestible unless its frontmatter explicitly sets ingest: false.

Ingestion is markdown-aware. It splits MDX at headings, preserves the complete heading path, slug, source type, anchor, and URL, and targets 400–700 estimated tokens with 15 percent overlap. Structured YAML becomes small atomic chunks. That metadata lets retrieval return a section-sized argument and lets a citation deep-link to the relevant heading.

Every assembled chunk passes a denylist scan before any database or embedding call. The gate covers internal infrastructure names, confidential identifier shapes, cloud-account material, credentials, and private contact data; one match exits ingestion nonzero. Clean content is normalized and hashed with SHA-256. A run embeds only hashes absent from Postgres, deduplicates identical new chunks, upserts new rows, and deletes stale rows. The result is a governed, content-hash-idempotent corpus rather than an append-only vector dump. The implementation is in scripts/ingest.ts and lib/ingest.

Hybrid retrieval and citation assembly

For a chat turn, a Haiku-class model rewrites recent conversation context into a standalone query and classifies the intent as work, logistics, Fit, site, off-topic, or adversarial. Work, logistics, and site queries enter retrieval; Fit requests are pointed at the dedicated pipeline; off-topic and adversarial requests return a bounded guardrail response without retrieval or generation.

The retriever obtains 20 pgvector cosine candidates and 20 Postgres full-text candidates. Reciprocal-rank fusion adds 1 / (60 + rank) contributions from each list, avoids pretending the two engines' raw scores are comparable, and returns the best eight unique chunks as S1 through S8. An embedding or vector-search failure degrades to full-text results. A full-text failure is fatal because the intended hybrid contract is no longer available. The exact ranking and degradation behavior lives in lib/rag/retrieve.ts.

Generation receives each label with its content, URL, and anchor. After the answer streams, citation assembly extracts only labels actually present in the text, discards labels that do not map to retrieved chunks, and emits citation objects with a bounded excerpt for the UI. Curated facts are injected separately and take precedence over retrieved content if the two conflict.

Guardrails

The core guardrail is an evidence contract, not a tone instruction. The system prompt requires an adjacent [S#] citation for every factual claim about Harsh; forbids invented projects, dates, metrics, credentials, preferences, and personal details; and requires the exact honest-gap response when the corpus cannot answer. It distinguishes shipped or operated work from exposure, and forbids unsupported superlatives.

Prompt injection is constrained at two layers. Rewrite and Fit prompts treat user text as data, never instructions. The chat route bypasses model generation entirely for classified adversarial and off-topic inputs. The agent never impersonates Harsh, never reveals its prompt, and stays within professional work, availability, and this site. These clauses are visible in lib/agent/system.ts.

Citation compliance remains a generated behavior, so it is tested rather than misrepresented as a post-generation proof. Unknown-answer, adversarial, and off-topic behavior is likewise part of the executable eval contract. Rate limits bound chat, Fit, and MCP traffic; a rate-limited eval response is always a failure, never a skipped assertion (ADR 0016).

Fit Report pipeline

Fit is a validate → extract → assess pipeline. Validation first recognizes obvious job-description structure and otherwise asks the rewrite model to classify the input. Non-JD text is rejected before expensive assessment. Extraction produces at most 15 distinct requirements and preserves must-have versus nice-to-have language.

Assessment runs hybrid retrieval independently for each requirement, keeps up to four chunks per requirement, deduplicates them, and assigns stable source labels across the report. Claude produces a 2–3 sentence summary, the ordered requirements matrix, exactly three highlights, honest gaps, and exactly three interview questions. Ratings are limited to Strong, Solid, Partial, and No evidence.

The application, not the model, owns the trust-critical invariants. A requirement with no retrieved chunks is forcibly changed to No evidence and its citations are emptied. Honest gaps cannot be omitted; missing gaps are synthesized from the weakest assessment. Provider-facing structured-output schemas omit size and range constraints that Claude rejects, then application code normalizes and validates the result against the strict Zod schema (ADR 0022).

Completed reports are stored for shareable URLs, emit a completion event, and can trigger a best-effort notification. PDF export deliberately reuses the web report through window.print() and dedicated print CSS instead of maintaining a second renderer (ADR 0023).

MCP server

The TypeScript MCP SDK is mounted directly in a dynamic Next.js route with its Web-standard Streamable HTTP transport. It exposes six tools:

All six carry the SDK's read-only annotation. At the product boundary they do not mutate user or site resources; the server still records operational calls, persists generated Fit Reports, and applies a 60-tool-call-per-hour IP limit. The transport accepts GET, POST, and DELETE, and tool schemas are validated with Zod. See lib/mcp and the native route decision.

The eval harness is the trust mechanism

scripts/smoke-eval.ts runs 40 cases against the live HTTP surfaces: 15 factual cases, five unknown-answer cases, five adversarial cases, five off-topic cases, five logistics cases, and five Fit cases. Factual assertions require the expected value and a citation. The other categories check honest gaps plus /contact, refusal and persona boundaries, short redirects, curated facts, Fit schema validity, unsupported requirements, and non-JD rejection.

The harness consumes the actual streaming protocol, retries a request once, aborts after three consecutive 429 responses, writes eval-results.json, and computes one overall pass rate. At least 90 percent exits zero; anything below sets exit code 1. That process exit is the deploy-blocking contract for a required pre-deploy check—a failed or rate-limited run cannot look healthy. The code is scripts/smoke-eval.ts.

Stack and why

LayerChoiceWhy this repository uses it
Application and hostingNext.js on VercelStatic MDX, streaming UI, and Node route handlers ship as one small deployable.
Data and retrievalSupabase Postgres + pgvectorOne service provides operational tables, full-text search, and cosine vector search with familiar SQL.
Model gatewayOpenRouterPer-call Sonnet/Haiku selection, spend controls, and a switching boundary without binding the app to a direct Anthropic account (ADR 0006).
EmbeddingsVoyageThe 1,024-dimensional Voyage family matches the stored vector shape; the Atlas-provisioned credential uses the Atlas endpoint, while an environment-configurable base URL also supports native Voyage (ADR 0001, ADR 0012).
ProtocolMCP TypeScript SDKIt supplies the native Streamable HTTP server and typed read-only tool contracts inside the existing Next.js deployment.

What generalizes

The reusable design is not the vendor list. Treat the corpus as a governed build artifact. Combine semantic and lexical retrieval when evidence contains both concepts and exact identifiers. Make facts precedence, citations, honest gaps, and refusal behavior explicit contracts. Enforce trust-critical negative claims in application code. Expose one core capability through multiple interfaces, and give the behavior gate a real nonzero exit. The meta-demo works because its architecture is the same discipline it claims Harsh brings to other systems.