Skip to content
Ryan Lindsey

Ops

How this site runs

This is a production system rather than a page about one, so here is what it is made of and what it has actually been doing. Every figure below says which system it was read from, and every one that could not be read says that instead of showing a zero. The numbers are read when you load the page and held for sixty seconds.

Deployed shape

Architecture

Two Cloudflare Workers. The site Worker renders the pages and serves the assets; the MCP Worker holds the protocol endpoint and everything that spends inference, because the bindings that call a model cannot live in the configuration the site is built from. They call each other over service bindings in both directions, which is legal and deliberate: a binding resolves when it is called, not when the Worker is defined.

How this site is deployedTwo 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.Visitors and agentsHTTPSSite Worker · ryanlindsey.meAstro, prerendered and on demandposts, case studies, the résumé/llms.txt, .md variants, RSS and JSON/chat page and the form surfacesthe queue consumer and the email send/mcp, chat and formsdocument readsMCP Worker · mcp.ryanlindsey.me/mcp endpoint, stateless per requestPOST /chat, the streamed inference/robots.txt, /.well-known/mcp.jsonrate limits in a Durable ObjectAI Gateway → AnthropicWorkers AI → Vectorize, for retrievalbindingsShared by both WorkersD1 · audit, tokens, transcripts, evalsKV · caches and flagsR2 · generated PDFs and documentsAnalytics Engine · request telemetryQueue · high-intent events → email
Drawn from the deployed configuration — the two wrangler.jsonc files and the two Worker entries in the repository — rather than from a sketch, so it can be checked against what is running.

Last 30 days

Live metrics

717

Public MCP tool calls

D1

214

Chat sessions

D1

215

Chat turns

D1

0

Fit analyses run

D1

2,394

Requests that reached the Worker

Analytics Engine

Static sub-resources and the home page are served without a Worker invocation, so they are not in this figure — and neither are /resume.md or /.well-known/mcp.json, which are prerendered files that wrangler.jsonc's run_worker_first does not list. Every other agent-signal route is counted: /llms.txt, /llms-full.txt, the markdown variants under /writing and /work, both feeds, /robots.txt and /resume.json — along with every on-demand route, which reaches the Worker by having no file behind it.

773

Requests from agents

Analytics Engine

3 ms

Median Worker time per request

Analytics Engine

Measured inside the Worker, where the clock advances only while a request is waiting on something else — a database, a cache, a model. It carries no network time, so a request served from memory is 0 ms here and this figure reads low by design.

By tool

  • get_contact579
  • list_case_studies36
  • list_writing32
  • get_resume28
  • request_private_access26
  • search_writing16

D1

By agent

  • unknown381
  • other-bot110
  • http-client42
  • Googlebot28
  • DuckAssistBot28
  • Claude-SearchBot26
  • Amazonbot24
  • MistralAI-User22
  • Google-Extended21
  • OAI-SearchBot18
  • ChatGPT-User15
  • CCBot14
  • ClaudeBot13
  • PerplexityBot12
  • cohere-ai7

Analytics Engine

By route class

  • other1,950
  • agent-signal406
  • content38

Analytics Engine

Constants in this build

Model & cost

Every model this build calls, named from the constant the calling code passes rather than typed out here, so this list cannot describe a model the site has stopped using. Chat answers and eval judging are separate constants that name the same model today; the fit path's documented fallback has no call site, so it is not a model the site runs and is not listed.

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

$6.07

Inference spend

AI Gateway

These three cover the calls that name the gateway — chat answers, fit analysis and eval judging. The corpus embedding calls pass no gateway option, so they are billed but not attributed here, and none of these figures is the account's whole inference bill.

888

Requests through the gateway

AI Gateway

0

Served from the gateway cache

AI Gateway

These three are the gateway's own accounting over the same window as everything above — its computed cost for the calls it was asked to record, which is a closer thing to an invoice than an estimate made here from token counts, and not the invoice itself. When they are blank, the read did not produce a figure: no token, a response this build would not read, or a window it could not tell from a misdirected query. A blank is not a claim that nothing was spent — the two look identical from here, which is why neither is rendered as a zero. The token counts behind the cost are deliberately not published: the dollar figure is the one that means anything to a reader, and the rest is detail about other people's questions.

Latest run per suite

Evals

The suites that run against the deployed endpoints: grounded answers, citation integrity, and the checks that the private surfaces stay invisible to an unscoped caller.

SuiteRanPassedFailed
chat2026-09-114/40
fit2026-09-111/32
leak2026-09-127/81
tier2026-09-111/10

D1

What happens when a part fails

Status & degradation

Everything here degrades rather than disappearing, and says so while it does. This is the ladder, in the order a reader would meet it.

The budget breaker trips
The daily inference budget breaker is tripped, so chat is paused until it resets. The corpus is still readable at /llms.txt, and the MCP endpoint still answers.
A read this page needs fails
The figure above it says so and the rest of the page still renders. No section here ever shows a zero standing in for a number nobody could read, because a zero is the one wrong answer a reader has no way to spot.
Retrieval finds nothing
The answer says the corpus does not cover it rather than filling the gap. The whole corpus is plain text at /llms.txt, and the MCP endpoint answers the same questions over a protocol.

Rate limits

  • Reading a published document over MCP60 per minute
  • Semantic search over the corpus10 per minute
  • A chat message30 per 5 minutes
  • Chat, across everyone, before the breaker500 per day

this build

What is kept, and for how long

  • Chat questions and answers30 days
  • The MCP audit log1 year
  • Stored analysis reports1 year

this build — a daily job enforces these, and the policy states them.

Generated from the release history

Changelog

Read out of the file release-please writes from merged pull request titles, so this list is the deploy history rather than a second one kept by hand. Dates only, never times.

  • 1.16.12026-09-12
  • 1.16.02026-09-12
  • 1.15.02026-09-12
  • 1.14.02026-09-12
  • 1.13.22026-09-11

this build