Skip to content
Ryan Lindsey

Deployed shape

Architecture

Two Workers: one serves the site, the other serves the MCP server, chat and fit analysis. They call each other through service bindings, without a public network hop.

How this site is deployed

Two Cloudflare Workers. Visitors and agents reach the site Worker at ryanlindsey.me, which serves the pages, posts, case studies and the résumé, prerendered with some routes rendered on demand, plus llms.txt, the markdown variants and the feeds; it hosts the chat page and the form surfaces, and runs the queue consumer that sends the notification email. It calls the MCP Worker at mcp.ryanlindsey.me over a service binding for the MCP endpoint, for chat and for form posts; the MCP Worker calls back the same way to read published documents. The MCP Worker serves the MCP endpoint statelessly, rebuilding the server for each request, alongside the streamed chat inference and the discovery routes; it holds the one Durable Object in this system, which enforces the rate limits, and it is where inference runs: through AI Gateway to Anthropic, and through Workers AI into Vectorize for retrieval. Both Workers share D1 for the audit log, tokens, transcripts and eval runs, KV for caches and flags, R2 for generated PDFs and documents, Analytics Engine for request telemetry, and a queue that carries high-intent events to the email. Analytics Engine and the queue are independent sinks; neither feeds the other.

01Visitors and agents

HTTPS

02 · Site Worker

ryanlindsey.me

Astro, prerendered with some routes rendered on demand

  • pages, posts, case studies, the résumé
  • llms.txt, .md variants, RSS and JSON
  • chat page and the form surfaces
  • queue consumer and the email send

/mcp · chat · forms

document reads

03 · MCP Worker

mcp.ryanlindsey.me

The MCP server and every call that spends inference, plus the rate limiter's Durable Object

  • /mcp endpoint, stateless per request
  • POST /chat, the streamed inference
  • /robots.txt, /.well-known/mcp.json
  • rate limits in a Durable Object
  • AI Gateway → Anthropic
  • Workers AI → Vectorize, for retrieval

04 · Shared by both Workers · bindings

D1

audit · tokens · transcripts · evals

KV

caches and flags

R2

generated PDFs and documents

Analytics Engine

request telemetry

Queue

high-intent events → email

Last 30 days

Live metrics

225

Public MCP tool calls

D1

0

Chat sessions

D1

0

Chat turns

D1

6

Fit reports produced

D1

6 started, 0 failed, 0 abandoned, 0 in progress

101,067

Requests that reached the Worker

Analytics Engine

79,418

Requests from agents

Analytics Engine

0 ms

Median Worker time per request

Analytics Engine

$8.53

Inference spend

AI Gateway

Covers chat, fit analysis and eval judging. Search embeddings do not go through the gateway, so they are billed but not counted here.

246

Requests through the AI Gateway

AI Gateway

0

Served from the gateway cache

AI Gateway

AI Gateway

These three figures are AI Gateway's own accounting for the same window: the cost it computed for the calls it recorded. That is closer to the invoice than an estimate from token counts, but it is not the invoice. A blank figure means the read failed, not that nothing was spent, so a blank is never shown as zero. Token counts are not published. The dollar figure is what matters to a reader, and the rest describes other people's questions.

By tool · D1

  • get_contact75
  • get_resume35
  • list_writing34
  • list_case_studies33
  • resource:resume21
  • resource:writing17
  • search_writing6
  • request_private_access2
  • get_case_study1
  • get_post1

By agent · Analytics Engine

  • unknown43,386
  • other-bot19,965
  • http-client8,169
  • Claude-User5,012
  • first-party401
  • Googlebot271
  • OAI-SearchBot219
  • Amazonbot217
  • GPTBot200
  • meta-externalagent194
  • CCBot160
  • Applebot158
  • ClaudeBot157
  • Bytespider152
  • ChatGPT-User144

By route class · Analytics Engine

  • agent-signal83,007
  • other16,641
  • content1,419

Model & cost

· Constants in this build

Every model the site calls. Each name is read from the constant the calling code uses, so the list cannot name a model the site has stopped using. Chat answers and eval judging are set separately and currently use the same model.

Chat answers
anthropic/claude-sonnet-5
Fit analysis
anthropic/claude-opus-5
Eval judging
anthropic/claude-sonnet-5
Search embeddings
@cf/qwen/qwen3-embedding-0.6b

Evals

· Latest run per suite · D1

These are automated checks that run on a schedule against the live site and its MCP server, covering grounded answers, citation integrity, and whether the private surfaces stay invisible to an unscoped caller. A graded case is a real answer checked against expectations written down in advance, and the Graded column scores those cases alone. Couldn't run counts the cases where the model call never came back, which says nothing about answer quality, because there was no answer to judge. A suite that did not run at all says so on its row rather than showing its last good result.

SuiteRanGradedCouldn't run
chat2026-10-254/40
fit2026-10-252/30
leak2026-10-258/80
tier2026-10-311/10

Failures

· By surface and reason · D1

Failed calls over the last 30 days, counted by the part of the site they reached, by whether a visitor or a scheduled eval run made them, and by the reason recorded for each. The fit rows count calls made through the MCP server. Runs started from the fit form are counted once, in the fit reports figure above. A failure from before reasons were recorded counts as unrecorded.

SurfaceFailedReasons
chat · scheduledno failures
fit · liveno failures
fit · scheduled1 of 35 failed
  • internal 1
search · liveno failures
judge · scheduledno failures
public tools · live2 of 219 failed
  • not_found 2
public tools · scheduledno failures
private tools · liveno failures

Status & degradation

· What happens when something fails

When a part fails, the site says so and keeps serving what it can. These are the failures in the order a visitor would meet them.

The budget breaker trips
The daily inference budget breaker is tripped, so chat is paused until it resets. Everything published is still readable at /llms.txt, and the MCP endpoint still answers.
A read this page needs fails
The figure it feeds shows as unavailable, never as zero. A wrong zero is the one error a reader cannot spot.
Retrieval finds nothing
Chat says the published work does not cover the question instead of guessing. Everything published is also plain text at /llms.txt, and the MCP server answers the same questions for agents.

Rate limits

  • Reading a published document over MCP60 per minute
  • Semantic search over the published pages10 per minute
  • A chat message30 per 5 minutes
  • Chat, across all visitors, before the breaker trips500 per day

What's stored, and for how long

  • Chat questions and answers30 days
  • The MCP audit log1 year
  • Fit runs and their reports1 year

A daily job enforces these, and the AI policy states them.

Release history

  • 1.54.02026-10-11
  • 1.53.12026-10-02
  • 1.53.02026-10-02
  • 1.52.32026-10-02
  • 1.52.22026-10-01