The Architecture of This Site
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:
about_harshreturns a curated public summary.search_experiencereturns bounded hybrid-retrieval results with canonical URLs.get_projectreturns a structured brief derived from a published docs page.analyze_job_fitruns the same validated, rate-limited Fit pipeline as the web UI.get_resumereturns JSON Resume data or Markdown.get_availabilityreturns curated public availability and contact fields.
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
| Layer | Choice | Why this repository uses it |
|---|---|---|
| Application and hosting | Next.js on Vercel | Static MDX, streaming UI, and Node route handlers ship as one small deployable. |
| Data and retrieval | Supabase Postgres + pgvector | One service provides operational tables, full-text search, and cosine vector search with familiar SQL. |
| Model gateway | OpenRouter | Per-call Sonnet/Haiku selection, spend controls, and a switching boundary without binding the app to a direct Anthropic account (ADR 0006). |
| Embeddings | Voyage | The 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). |
| Protocol | MCP TypeScript SDK | It 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.