If you ran a URL through the Agent Runtime Readiness audit and the third check came back amber, you saw:
Host did not return Markdown content when Accept: text/markdown was requested. Enable Cloudflare Markdown for Agents or implement content negotiation at your origin.
If your site sits behind AWS CloudFront, neither suggestion is a one-toggle fix — there is no managed Markdown-for-Agents feature in CloudFront as of writing. The fix is a Lambda@Edge function attached to your distribution. Here's the working pattern.
CloudFront Functions vs Lambda@Edge — and why this needs Lambda@Edge
CloudFront has two compute layers, and the choice matters for this use case.
CloudFront Functions are tiny, fast, JavaScript-only, run at every edge location, and have a sub-millisecond budget. They can read and modify request and response headers. They cannot fetch a different body, do async work, or call external services. For Markdown for Agents you need to either return a different body or fetch a different origin object — neither is possible from CloudFront Functions.
Lambda@Edge is full Node.js (or Python), runs at the regional edge cache, can do async fetches, and can rewrite the request URL or replace the response body. For this feature, this is the right layer.
The trade-off is latency and cost. Lambda@Edge invocations run at regional edge caches (a smaller set than every-edge-location), so cold-start latency is higher than CloudFront Functions. But for Accept: text/markdown requests — which are coming from AI runtimes, not human visitors waiting for a page — the latency is acceptable.
Pattern A — Rewrite the request URL to a .md companion (viewer-request trigger)
If your origin (S3 bucket, custom origin, app server) already serves the markdown source files alongside the HTML, the cleanest implementation is a viewer-request Lambda that rewrites the request URL when Accept: text/markdown is present.
// markdown-negotiation/index.js
'use strict';
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
const accept = (headers['accept'] && headers['accept'][0] && headers['accept'][0].value) || '';
if (!/text\/markdown/i.test(accept)) return request;
// Rewrite /blog/foo/ → /blog/foo/index.md
// Rewrite /blog/foo → /blog/foo.md
let uri = request.uri;
if (uri.endsWith('/')) uri += 'index.md';
else if (!uri.endsWith('.md')) uri += '.md';
request.uri = uri;
return request;
};
Deploy this as a Lambda@Edge function (Node.js 20.x), then attach it to your CloudFront distribution's behavior on the Viewer Request event. The function rewrites the URL before CloudFront fetches from origin; your S3 bucket or custom origin serves the .md file directly.
You also need to make sure CloudFront caches the markdown response separately from the HTML response. In your distribution behavior:
- Set the Cache Policy to one that includes
Acceptin the cached headers (create a custom Cache Policy if needed — CloudFront's managedCachingOptimizedpolicy doesn't include Accept). - Set the response Vary header to
Acceptvia a Response Headers Policy or via a separate origin-response Lambda.
Without one or the other, CloudFront will serve cached HTML to a markdown request (or vice versa).
Pattern B — Convert HTML to markdown in origin-response (origin-response trigger)
If your origin is a CMS or app server that doesn't have markdown source available, convert the HTML response in flight using an origin-response Lambda.
// markdown-converter/index.js
'use strict';
const TurndownService = require('turndown');
exports.handler = async (event) => {
const cf = event.Records[0].cf;
const request = cf.request;
const response = cf.response;
const accept = (request.headers['accept'] && request.headers['accept'][0] && request.headers['accept'][0].value) || '';
if (!/text\/markdown/i.test(accept)) return response;
// Origin must return body inline; configure Lambda to include body
const body = response.body || '';
const mainMatch = body.match(/<main[^>]*>([\s\S]*?)<\/main>/i);
const target = mainMatch ? mainMatch[1] : body;
const td = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced' });
const md = td.turndown(target);
response.body = md;
response.bodyEncoding = 'text';
response.headers['content-type'] = [{ key: 'Content-Type', value: 'text/markdown; charset=utf-8' }];
response.headers['vary'] = [{ key: 'Vary', value: 'Accept' }];
return response;
};
For origin-response Lambda to receive the response body, the function must be configured with Include Body enabled. The body size limit is 1 MB for viewer-response, 1 MB for origin-response — most article pages are well under this, but be aware of the limit if your pages are large.
The conversion runs once per cache miss; subsequent requests for the same URL with Accept: text/markdown are served from CloudFront's cache without re-invoking Lambda.
Cache Policy and Vary header — the part that breaks deployments
The most common reason this implementation appears to work in curl but fails in the audit is that CloudFront is serving cached HTML to the audit's markdown request. CloudFront's default Cache Policy does not include the Accept request header in the cache key. Two requests to the same URL with different Accept values get the same cached response.
Fix:
- Create a custom Cache Policy that includes
AcceptinHeaders(Cache key and origin requests → Headers → Include the following headers → AddAccept). - Attach the new policy to the distribution behavior the Lambda is attached to.
- Add a Response Headers Policy that always includes
Vary: Accepton responses (or set it from the Lambda as in Pattern B above).
Until both are in place, your edge cache will serve the wrong shape to the wrong client unpredictably.
Verifying the fix
curl -s -H "Accept: text/markdown" -i https://yourdistribution.cloudfront.net/some-page/ | head -10
Look for content-type: text/markdown and vary: Accept in the response. Re-run the Agent Runtime Readiness audit — the third check should pass.
If the audit still warns, the most common causes are:
- Cache key didn't include Accept. Test by adding a query string cachebust (
?_t=12345) to the URL — if the markdown response shows up, your Lambda is working but the cache is collapsing. Fix the Cache Policy. - Lambda@Edge replication is not complete. When you publish a Lambda@Edge function or attach it to a behavior, replication to all regional edge caches takes a few minutes. Wait 5 minutes and re-test.
- The audit's proxy is being blocked. Some CloudFront WAF rules return a challenge to bot user agents. The audit's proxy uses a generic UA; if your WAF is aggressive, the audit may never reach Lambda. Check WAF logs.
What this costs
Lambda@Edge is billed per invocation and per GB-second. A typical viewer-request rewrite Lambda runs in single-digit milliseconds and costs essentially nothing per request — pennies per million invocations. The origin-response Lambda with HTML-to-markdown conversion is more expensive (the conversion takes 50-200 ms depending on page size), but still in the dollars-per-month range for a moderately trafficked site, and only fires on cache misses.
Related reading
- The Original Markdown For Agents Warning Post — what the audit is checking and the Cloudflare-toggle path
- Agent Runtime Readiness — the audit tool itself
- The Conversation Has Moved Past The Model — why this matters now
- Fastly Compute@Edge Pattern — same fix on Fastly
- Origin Server Configs (Nginx / Apache / Caddy) — if you're not on a CDN at all
Fact-check notes and sources
- Lambda@Edge developer guide: docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html
- CloudFront Functions vs Lambda@Edge feature comparison: docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-functions.html
- Cache Policy custom configuration: docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/controlling-the-cache-key.html
- Vary header semantics: RFC 9110 §12.5.5
- Cloudflare Markdown for Agents reference (the feature this post replicates on CloudFront): developers.cloudflare.com/fundamentals/reference/markdown-for-agents/
If you're managing CloudFront as part of a larger stack and want a lighter operating model that avoids the AWS console-drag, The $20 Dollar Agency covers the build-your-own-web stance from the operations side.
This post is informational, not legal, security, or SEO-consulting advice. Mentions of AWS, CloudFront, Lambda@Edge, and other third parties are nominative fair use; no affiliation is implied.