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.
- Visitor's browser → Web app: page load (request, sync)
- Visitor's browser → Python service: chat and data calls, made directly (request, sync)
- Visitor's browser → Booking calendar: the booking calendar, embedded (request, sync)
- Booking calendar → Python service: a booking webhook; the email is stored hashed (webhook, async)
- Python service → Agent graph: route each chat turn (request, sync)
- Agent graph → Knowledge base: whole knowledge base for Claude; sections for the local path (request, sync)
- Agent graph → Claude: one call per uncached turn (request, sync)
- Python service → Langfuse: a trace per turn when keys are set (request, sync)
- Python service → Resend: visit alerts and unanswered-question notes (request, async)
- Python service → Postgres: tracked links, visits, a few background wakes a day (request, sync)
- 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.
- Rate limit → Cache check: 30 requests per 5 minutes per address (request, sync)
- Cache check → Route the turn: a miss goes on; answers are kept 24 hours (request, sync)
- Cache check → Write the trace: a hit: no model call, still traced when keys are set (request, sync)
- Route the turn → Claude call: the default path (request, sync)
- Route the turn → Retrieve sections: the local path only (request, sync)
- Retrieve sections → Local model: admin only, off by default (request, sync)
- Local model → Claude call: fallback when the local call fails (event, sync)
- Local model → Verify the answer: answer to check (response, sync)
- Claude call → Verify the answer: answer to check (response, sync)
- Verify the answer → Write the trace: a trace when keys are set (event, sync)
- 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.
What retrieval picks (the local path, and the /skills demo)
- graph.md215 chars15.95
- atlassian.md1,666 chars7.02
- story.md1,608 chars5.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.5Running cost
| Resource | Figure | Status | Source |
|---|---|---|---|
| Neon, idle background | ≤ $0.53 per month | Derived 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.-hour | Published 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.-month | Published price | the Neon row of the owner's metered-resource table Read on 2026-10-17 |
| Vercel edge requests, plan | One million — Shorthand in prices and limits, as in $1 per 1M tokens. requests per month, shared by about 10 projects | Published 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. tokens | Published 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. tokens | Published price | the Haiku 5.5 rate spec, line 43 Read on 2026-10-17 |
| Knowledge base per chat request | 30,629 tokens per request | Measured | 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 request | Derived | 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 cap | 512 output tokens per answer | Set in the project's code | chat router, lines 224-231 Read on 2026-10-17 |
| Chat rate limit | 30 requests per 5 minutes per address | Set in the project's code | chat router, line 373 Read on 2026-10-17 |
| Retrieval demo | $0 per request | Set 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