← Home

This site: how it's built

A portfolio whose chat answers from a markdown knowledge base.

A web app and a Python Application Programming Interface — A defined way for one program to request data or actions from another. service. The chat answers from a markdown knowledge base; a badge on every answer names the backend and model. Also booking, live chat, LeetCode, a career graph and the try-it mocks. Vendors: Anthropic, Langfuse, Neon, Railway, Vercel.

Architecture

What talks to what

No tier is tied to a host here, by design; the vendors are listed above.

What talks to whatBrowserSiteDataOutsideVisitor's browserBooking calendarWeb appPython serviceAgent graphKnowledge basePostgresClaudeLangfuseResendPublic data feeds1234567891011
  1. Visitor's browser → Web app: page load (request, sync)
  2. Visitor's browser → Python service: chat and data calls, made directly (request, sync)
  3. Visitor's browser → Booking calendar: the booking calendar, embedded (request, sync)
  4. Booking calendar → Python service: a booking webhook; the email is stored hashed (webhook, async)
  5. Python service → Agent graph: route each chat turn (request, sync)
  6. Agent graph → Knowledge base: whole knowledge base for Claude; sections for the local path (request, sync)
  7. Agent graph → Claude: one call per uncached turn (request, sync)
  8. Python service → Langfuse: a trace per turn when keys are set (request, sync)
  9. Python service → Resend: visit alerts and unanswered-question notes (request, async)
  10. Python service → Postgres: tracked links, visits, a few background wakes a day (request, sync)
  11. Python service → Public data feeds: daily repository refresh; LeetCode cached 6 hours (cron, async)

One chat turn

The badge reports the backend that actually answered, so a fallback to Claude is shown as Claude.

One chat turnRequestGraphModelsAfterRate limitCache checkRoute the turnRetrieve sectionsVerify the answerClaude callLocal modelWrite the traceCapture unanswered1234567891011
  1. Rate limit → Cache check: 30 requests per 5 minutes per address (request, sync)
  2. Cache check → Route the turn: a miss goes on; answers are kept 24 hours (request, sync)
  3. Cache check → Write the trace: a hit: no model call, still traced when keys are set (request, sync)
  4. Route the turn → Claude call: the default path (request, sync)
  5. Route the turn → Retrieve sections: the local path only (request, sync)
  6. Retrieve sections → Local model: admin only, off by default (request, sync)
  7. Local model → Claude call: fallback when the local call fails (event, sync)
  8. Local model → Verify the answer: answer to check (response, sync)
  9. Claude call → Verify the answer: answer to check (response, sync)
  10. Verify the answer → Write the trace: a trace when keys are set (event, sync)
  11. Verify the answer → Capture unanswered: a Claude answer the knowledge base missed, after the reply (event, async)

Wiremocks

A mock-up of a screen — Here, a static picture of a screen, drawn to show layout and flow, not the working app. — a static picture, not the real app

One chat turn

Retrieval rows are a real run on this question (the local path and the /skills demo); Claude reads the whole knowledge base instead.

Visitor asksWhat did he lead at Atlassian?

What retrieval picks (the local path, and the /skills demo)

  • graph.md215 chars
    15.95
  • atlassian.md1,666 chars
    7.02
  • story.md1,608 chars
    5.29

What Claude reads: the whole knowledge base

He led Org Delete, which removes an organisation's data across 12 services after a 30-day grace period. He also built the config-driven framework that onboards new Atlassian products onto the admin platform.

claude · Claude Haiku 5.5

Running cost

ResourceFigureStatusSource
Neon, idle background≤ $0.53 per monthDerived from an estimate
derived (see inputs)
Inputs: ≤ 5 Compute unit — The unit a serverless database bills its processing time in; one unit-hour is one unit running for one hour.-hours a month (projected, build log, line 753) × $0.106 per Compute unit — The unit a serverless database bills its processing time in; one unit-hour is one unit running for one hour.-hour (published, the Neon row of the owner's metered-resource table)
Read on 2026-10-17
Neon compute$0.106 per Compute unit — The unit a serverless database bills its processing time in; one unit-hour is one unit running for one hour.-hourPublished price
the Neon row of the owner's metered-resource table
Read on 2026-10-17
Neon storage$0.35 per Gigabyte — A billion bytes of data; storage is priced per gigabyte per month.-monthPublished price
the Neon row of the owner's metered-resource table
Read on 2026-10-17
Vercel edge requests, planOne million — Shorthand in prices and limits, as in $1 per 1M tokens. requests per month, shared by about 10 projectsPublished price
the Vercel Requests Vercel counts at its edge — Every request Vercel serves (a page, a script, an image) counts as one. row of the owner's metered-resource table
Read on 2026-10-17
Claude Haiku 5.5 input$0.10 per One million — Shorthand in prices and limits, as in $1 per 1M tokens. tokensPublished price
the Haiku 5.5 rate spec, line 43 (prompts up to 100K tokens)
Read on 2026-10-17
Claude Haiku 5.5 output$0.50 per One million — Shorthand in prices and limits, as in $1 per 1M tokens. tokensPublished price
the Haiku 5.5 rate spec, line 43
Read on 2026-10-17
Knowledge base per chat request30,629 tokens per requestMeasured
token count of the whole knowledge base as the chat sends it (the markdown files plus published answers from the store), from Anthropic's count_tokens for Haiku 5.5, read 2026-10-17; no answers were published at the time
Read on 2026-10-17
Knowledge-base input, one uncached request, at the base rate$0.0031 per requestDerived
derived (see inputs)
Inputs: 30,629 tokens (measured) × $0.10 per One million — Shorthand in prices and limits, as in $1 per 1M tokens. tokens (published); cache writes cost more (open question)
Read on 2026-10-17
Answer length cap512 output tokens per answerSet in the project's code
chat router, lines 224-231
Read on 2026-10-17
Chat rate limit30 requests per 5 minutes per addressSet in the project's code
chat router, line 373
Read on 2026-10-17
Retrieval demo$0 per requestSet in the project's code
the demo router, lines 1-2 ("No model call, no database")
Read on 2026-10-17
Railway—Unknown
the owner's Railway row is marked verify and names another project
Read on 2026-10-17
Resend—Unknown
the owner's verify row names no project
Read on 2026-10-17
Langfuse—Unknown
no owner row and no repo cost doc
Read on 2026-10-17

4 tracked-link visits in the 4 months to 2026-09-30 (build log, line 745); chat volume not recorded.

Design decisions

  • Two services: a web app and a Python Application Programming Interface — A defined way for one program to request data or actions from another.The Artificial intelligence — Software that performs tasks that normally need human judgment, such as language or image understanding. libraries are Python-native, so they live in their own service. That avoids coupling the chat to page rendering and its timeouts, and the Application Programming Interface — A defined way for one program to request data or actions from another. is reusable by other projects.
  • The whole knowledge base goes into Claude's prompt, with no vector storeThe prompt cache makes the large prefix cheap, so accuracy comes first: no retriever drops anything before Claude reads it.
  • Lexical retrieval only for the local pathIt is deterministic, and pinned sections cannot be dropped. A 9,699-token prompt took over 540 seconds cold on the local model.
  • The backend label never liesA failure falls back to Claude and the badge says who answered. The cache is keyed per backend, so a local answer is never replayed as Claude's.
  • Cost over cold startThe database may suspend when idle, and the first request after idle takes about 0.7 seconds longer. Accepted, because visits are few.

Open questions

  • Railway: plan and monthly cost are not recorded; the owner's row is marked verify and names another project
  • Resend: plan and monthly cost are not recorded; the owner's row names no project
  • Langfuse: plan and monthly cost are not recorded; whether the production keys are set is not recorded either
  • Chat: tokens per turn and monthly spend not recorded
  • Cache writes: every uncached turn writes the prompt cache, which Anthropic prices above the base input rate; the row above uses the base rate
  • Edge requests: this site's share of the shared plan is not measured
  • Cal.com: plan not recorded