How this site works
A static site, a small API and an assistant that answers only from this site's own content.
The pages are plain HTML, built ahead of time. A Cloudflare Worker runs only for /api: it checks each request, counts questions, finds the passages that answer them and streams the model's reply through a guard. Every figure below was measured on the preview deployment before launch, and the decisions behind the design close the page.
The life of a question
From the first click in the question box to the last word of the answer.
A check on first focus
The first time you click into the question box, your browser loads Cloudflare Turnstile. It checks the browser in the background and hands back a one-time token. Until that click, nothing on this site loads from another domain.
A signed visitor session
The Worker redeems the token with Cloudflare, once, and answers with a signed, anonymous visitor token. Your browser keeps it in local storage for up to 30 days. There is no account and no cookie.
Checks, then the quota
The question arrives with the token. The Worker checks its origin, its shape, the signature and a burst limit. One database transaction then counts it: 10 questions per visitor, under daily caps per network and for the whole site. The counter you see is only a display.
Hybrid retrieval
The question becomes an embedding of 512 numbers. In memory, BM25 and cosine similarity each rank the passages of this site, and reciprocal rank fusion merges both rankings into the top 5. If the embedding call fails, BM25 answers alone.
The prompt
The fixed instructions and the profile go first, so the provider can serve them from its prompt cache. Then come the recent conversation, the retrieved passages, marked as reference material, and your question.
Sources first
The reply opens as a stream of Server-Sent Events. Its first event carries the sources and your questions left, before the model is even called, so the source links appear before the first word.
The model streams
One call to the OpenAI Responses API, streamed, with storage turned off. If it fails before the first word, it is retried once, a fallback model takes over after repeated failures, and the question does not count.
The output guard
Every piece of text passes a guard before it leaves the Worker. It holds back a few characters, so nothing forbidden gets out, even in part. It stops the answer if the canary or the instructions appear, plain or encoded, and it strips HTML and links to other sites.
Back to your browser
The text arrives as delta events, and a done event closes the stream with timing and token counts. A stream that ends without done counts as failed. Close the tab and the Worker cancels the model call, so nothing more is billed.
The fit check
A pasted job post becomes one row per requirement, each with its evidence. The model proposes. Code in the Worker decides what stands.
What the Worker enforces
Only offered evidence. A row may cite only the passages retrieval offered for that requirement. Anything else is dropped, including ids planted in the post.
No evidence, no level. A level above none with no valid evidence left becomes none.
Demonstrated needs support. Demonstrated needs a project passage that both rankings placed in their top 3. A skill that only appears in the CV list is related at most.
Years are counted. A requirement for more years of experience than his roles cover is none, whatever the model says.
Injections are capped. Sentences addressed to automated tools are removed before either model reads the post, and every level of that analysis is capped at related.
Plain text only. Each explanation is plain text of 200 characters at most: no markup, no links, no hidden characters.
Measured, not claimed
Figures from the pre-launch review of 8 October 2026, taken on the preview deployment with the real model. Each one names the report that states it.
Answers
The golden set: 60 questions with expected sources and checks, asked to the live assistant.
Recall@5
95.7%
gate 90%
Expected sources found in the top 5 passages, last run
Source: Pre-launch review, part 2
Time to first token, p95
1,525 ms
gate 2,500 ms
From the question to the first word, last run
Source: Pre-launch review, part 2
Hallucination bait rejected
100%
gate 100%
Questions built on a false premise, last run
Source: Pre-launch review, part 2
Attacks
The red team: 38 attacks in 13 families on the chat, and hostile job posts on the fit check.
Attacks answered cleanly
37 of 38
Extraction, role play, encoding, fake history and more, last live run
The miss showed a link to another site as plain text, never clickable. The guard now removes those too.
Source: Pre-launch review, part 2
Canary leaks
0
Every answer of six runs
Source: Pre-launch review, part 2
Levels invented on the fit check
0
Demonstrated without real evidence, every run
Source: Pre-launch review, part 2
The platform
Security headers, a scanner, crafted requests, a privacy crawl and a load test.
Mozilla HTTP Observatory
A+
Score 135, 12 of 12 tests passed
Source: Pre-launch review, part 1
Medium or high scanner alerts
0
OWASP ZAP baseline scan of the preview
Source: Pre-launch review, part 1
API abuse checks passed
184 of 184
Foreign origins, wrong methods, oversized and malformed bodies, forged tokens
Source: Pre-launch review, part 1
Third-party requests before you ask
0
Every page, in a fresh browser; also 0 cookies
Source: Pre-launch review, part 1
Response time under load, p95
24 to 59 ms
20 virtual users for 2 minutes, per route
Source: Pre-launch review, part 1
CPU time per chat answer, p50
about 18 ms
Retrieval, streaming and the guard, as Cloudflare measures it
Source: Pre-launch review, part 2
Cost
Priced from the token counts the model reports, at list prices.
One fit analysis
about $0.003
About 4,500 tokens in and 1,800 out
Source: Pre-launch review, part 2
Six full evaluation runs
about $0.35
Red team, golden set and fit check, every suite
Source: Pre-launch review, part 2
Verified answers (preview)
The next assistant, on the preview since 12 October 2026: each sentence cites what it relies on, checked first.
Recruiter questions answered right
88.2%
170 questions with expected facts; the current assistant passes 42.9%
Source: Assistant v3 evaluation
Claims the judge found false
0
Every claim of 250 answers, against what Tomás has published
Source: Assistant v3 evaluation
Rewordings routed to the right answer
97.2%
108 paraphrases never seen before; none routed to a wrong answer
Source: Assistant v3 evaluation
Unsupported sentences the second check catches
96%
56 hand-labelled sentences; it removed no supported one
Source: Assistant v3 evaluation
The stack
Few moving parts, each with one job.
- Pages
- Astro, static output
- Preact islands only where something reacts: the assistant, the fit check, the status page and search.
- API
- Cloudflare Worker with Hono
- Runs only for /api. Workers Rate Limiting absorbs bursts.
- Data
- Cloudflare D1
- Question quotas, a scrubbed question log and the hourly figures behind the status page.
- Model
- OpenAI Responses API
- Answers stream. Embeddings at 512 dimensions, stored as int8 inside the Worker bundle.
- Quality
- Vitest, Playwright, axe, Lighthouse CI
- Five browser profiles, accessibility checks in light, dark and reduced motion, and assistant evals in CI.
Decisions
Architecture decision records: the context, what was chosen and what it costs.
Static pages with islands, not a single-page app
- Context
A portfolio is read far more than it is used. The plan asked for a Lighthouse score of 95 or more on every page, a largest contentful paint under 1.5 s and small JavaScript budgets.
- Decision
Astro builds every page to static HTML. Preact runs only in islands that need it: the assistant, the fit check, the status page and the search palette, which loads on first use.
- Consequences
Every page is complete without JavaScript. CI fails a page whose initial JavaScript goes over its budget: 60 KB gzipped on the home page, 40 KB elsewhere. Islands render at build time first, so hydration moves nothing on screen.
The Worker runs only for /api
- Context
Pages and the API share one origin, so the browser needs no CORS. A Worker invocation costs CPU time, and almost every request is for a page or a file.
- Decision
Cloudflare's asset layer serves the build output directly. The Worker runs first only for /api/* (run_worker_first), with Hono routing inside it.
- Consequences
Pages never wake the Worker, and the API never sends CORS headers. Page headers live in a _headers file. The Worker sets its own on API responses, including a Content-Security-Policy that lets nothing load.
A retrieval index inside the Worker, not a vector database
- Context
The assistant answers only from this site's own content, a small set of Markdown entries. A hosted vector database or file search would add a service, a network hop and a bill.
- Decision
A build script splits the public entries into passages, embeds them at 512 dimensions, stores the vectors as int8 and commits the index as JSON. The Worker bundles it and searches it in memory.
- Consequences
Retrieval needs no network call besides the question's embedding, and every content change shows up in review as an index diff. A unit test fails when the index is stale. It would not scale to millions of passages, and it does not need to.
Hybrid retrieval: BM25 and embeddings, fused by rank
- Context
Names and acronyms such as DAL-A need exact matching. Paraphrases, and questions asked in Spanish or German, need meaning. Neither signal is enough alone.
- Decision
Rank the passages twice, with BM25 and with cosine similarity, drop weak matches from each ranking, and merge both with reciprocal rank fusion (k = 60). The top 5 go into the prompt.
- Consequences
Recall@5 reached 95.7% on the golden set. Fusion only sees ranks, so the relevance floors apply before it. When the embedding call fails, BM25 still answers, and suggested questions carry precomputed embeddings.
Turnstile, a signed visitor token and atomic quotas
- Context
The assistant is public and every answer costs money. There is no login and no cookie, and any counter kept in the browser can be edited.
- Decision
Cloudflare Turnstile checks the browser once, then the Worker issues a signed, anonymous visitor token. After a burst limit, the server counts each question in one D1 transaction against the visitor's 10 and against daily caps per network and for the whole site.
- Consequences
Concurrent requests cannot overshoot a limit, and editing the counter in the browser gains nothing. Clearing storage starts a new allowance, but the network and site caps still hold. A question is given back only when the model fails before its first word.
Server-Sent Events, with the sources first
- Context
A visitor judges an answer by how soon it starts and by whether it can be checked.
- Decision
Answers stream as Server-Sent Events from a plain POST. The first event carries the sources, the text follows in deltas, and a done or error event ends the stream.
- Consequences
Source links appear before the first word. The client treats a stream without done as failed and offers a retry. When the visitor leaves, the Worker aborts the model call, so no more tokens are billed.
An output guard with a canary
- Context
No prompt can promise that a model never repeats its instructions. In the red team, a paraphrase and an encoded reply both got past the prompt alone.
- Decision
The instructions carry a secret canary, and every delta passes a streaming guard before it reaches the visitor. The guard holds back a short window and stops the answer on the canary, on the instructions, verbatim or retold, or on a long encoded run. It also strips HTML and links to other sites.
- Consequences
An answer can stop mid-sentence, and that question still counts, so tripping the guard never buys a free question. The guard runs on every delta, so it scans only the new text to keep CPU time low.
The fit check: the model proposes, the server decides
- Context
A job post is untrusted text and may carry instructions for the model. What the model returns is untrusted too.
- Decision
Two model calls with strict JSON schemas extract the requirements, then propose a level and evidence for each. Deterministic code then keeps only evidence retrieval offered for that requirement, requires corroborated retrieval for demonstrated, applies a years rule, and caps every level at related when the post addresses automated tools.
- Consequences
A fully steered model can at worst produce related, backed by real, retrieved evidence. The price is some recall: an honest match the rules cannot corroborate stays at related.
Whole answers, not streams, for the fit check
- Context
The Workers Free plan allows 10 ms of CPU per request. Parsing about a thousand streamed events per analysis cost most of the fit check's CPU, and Cloudflare cut analyses off.
- Decision
Both fit check model calls ask for the whole answer. The visitor still gets the rows over Server-Sent Events, each one sent after validation. The chat keeps streaming.
- Consequences
The fit check's CPU time fell from 341 ms to 15 ms at p50. The first row now waits for the whole classification instead of arriving as soon as the model writes it.
A hashed CSP, and no third party until you ask
- Context
A portfolio has no reason to load trackers, and every third-party script widens the attack surface. Some visitors will open the developer tools.
- Decision
Every page carries a Content-Security-Policy with a SHA-256 hash for each inline script, and no unsafe-inline. Stylesheets and fonts are files on this domain. Turnstile, the only third party, loads on the first focus of the question box or the job post field.
- Consequences
A build check fails on an unhashed inline script or a style attribute. Mozilla Observatory grades the site A+. Adding analytics later means changing the CSP and the privacy notice together.
Verified answers: every sentence cites what it relies on
- Context
In October an audit of the assistant found it joining true facts into false ones: a client of the real estate platform became a client of his employer. Retrieving a few passages per question let the model fill the gaps, and the same question got different answers.
- Decision
The model reads all of Tomás's published statements and answers as structured output, each sentence naming the statements it relies on. Code checks every figure, date, name and relation against them, and a second model removes any sentence they do not support. Common questions get a prepared answer, served word for word. The cards under an answer come from the site's own data.
- Consequences
An answer can only say what Tomás has published, and the visitor can open the statement behind each sentence. A sentence that fails a check is retried once, then removed or replaced by an honest "not listed here" with his contact details. The second check adds about a second. It runs on the preview first, and production switches once the audit passes.
See it running
- System status
Live, aggregated numbers from the site and the assistant.
- Fit check
Paste a job post and read the evidence rows.