← Zalman Friedman

How This Site Works

The site is a portfolio piece. So is this page.

Problem
A job-search portfolio needs to work for a recruiter skimming for 30 seconds and a hiring manager digging for 20 minutes — and I wanted it to demonstrate AI-forward product judgment, not just claim it.
My role
All of it: product decisions, content, design, and the build (working AI-natively, as I do).
Outcome
A plain-HTML site with an AI assistant grounded in one document, costing under $5/month with a hard spend ceiling.

The design decision: aggressively boring

This site is static HTML with one stylesheet. No framework, no build pipeline, no trackers. That's not a budget constraint — it's the same judgment I apply to products: complexity is a tax you should only pay for a return. A portfolio's job is to deliver information fast; nothing delivers information faster than text on a page.

The AI decision: why there's no RAG here

The assistant on the homepage answers questions from a single career document. The fashionable architecture would be RAG — embeddings, a vector database, chunking, retrieval. I didn't build that, on purpose: RAG exists for corpora too large to fit in a model's context window, and my career document is about 25,000 tokens. It fits whole. So the entire document ships with every request as a cached system prompt.

What that buys: no retrieval misses (the model always sees everything), no extra infrastructure to break, and deterministic behavior — the same question always has the same grounding. Prompt caching makes it cheap: after the first request, the document costs about a tenth of the normal input rate. Choosing the boring architecture over the impressive-sounding one is the actual demonstration of judgment here.

The cost model

The assistant runs on Claude Haiku. Per message: roughly 15K cached input tokens plus a 400-token capped reply comes to about a third of a cent. At realistic job-search traffic that's a couple of dollars a month. But "realistic" isn't a cost control, so the actual controls are layered:

Questions are capped at 400 characters and replies at 400 tokens. Each visitor gets 10 questions per session and 25 per day (tracked per IP). A global kill switch stops the assistant after 500 messages in a day — worst possible day, about $2 — and falls back to pointing at the resume. Above all of that, a hard monthly spend limit at the API account level means the absolute worst case is a number I chose, not a surprise.

The security posture: minimize blast radius

Someone will try to jailbreak this bot. Probably today. The defense isn't a clever prompt — prompts are not security boundaries. The defense is that there's nothing behind the boundary: the bot's document is the public, recruiter-safe version of my career file, containing nothing I wouldn't say in an interview. A fully successful jailbreak yields an off-topic answer at my expense of a third of a cent. The system prompt still instructs the model to stay on topic, refuse persona changes, and decline salary questions — but the real control is that the worst case was designed to be boring.

This is the same principle I applied scoping the MCP server at Propela: don't try to make the agent unbreakable; decide what it's allowed to touch so that breaking it doesn't matter.

The stack, for completeness

Cloudflare Pages (static hosting, free) · one Pages Function for the chat endpoint · Cloudflare KV for rate-limit counters · Cloudflare Turnstile for bot filtering · the Anthropic API (Claude Haiku) with prompt caching. Total infrastructure cost: the domain, plus single-digit dollars a month for inference — capped.