<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://timothyjordan.com/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://timothyjordan.com/blog/" rel="alternate" type="text/html" /><updated>2026-07-01T22:42:57+00:00</updated><id>https://timothyjordan.com/blog/feed.xml</id><title type="html">Timothy Jordan</title><subtitle>Hello! I&apos;m Timothy Jordan and I believe in improving life through engineering and art.</subtitle><entry><title type="html">llms.txt saves an agent real tokens, but it won’t read it unless you ask</title><link href="https://timothyjordan.com/blog/2026/07/01/llms-txt-linking.html" rel="alternate" type="text/html" title="llms.txt saves an agent real tokens, but it won’t read it unless you ask" /><published>2026-07-01T00:00:00+00:00</published><updated>2026-07-01T00:00:00+00:00</updated><id>https://timothyjordan.com/blog/2026/07/01/llms-txt-linking</id><content type="html" xml:base="https://timothyjordan.com/blog/2026/07/01/llms-txt-linking.html"><![CDATA[<p><em><a href="https://a14y.dev/research/llms-txt-linking/">Cross-posted from a14y.dev</a></em></p>

<h2 id="summary">Summary</h2>

<p><strong>Ship the discovery layer, then tell the agent to use it.</strong></p>

<p>Publishing an <code class="language-plaintext highlighter-rouge">llms.txt</code> is increasingly common, though far from universal: in our <a href="https://a14y.dev/research/state-of-agent-readability/">survey of the CrUX top-100k</a>, only about 1 in 4 sites (27% of the 50,074 we reached) ship one. The harder part is getting an AI agent to actually read the file. In an earlier a14y benchmark, a clean discovery layer (an <code class="language-plaintext highlighter-rouge">llms.txt</code> index plus markdown mirrors) cut an agent’s token use roughly in half at the same answer quality. In a new benchmark, the agent captured that saving only when we told it to look. Not one of the in-page signals we tried (a <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> tag, a visible “For agents” footer link) got it to use <code class="language-plaintext highlighter-rouge">llms.txt</code> on its own. A single line in the prompt did.</p>

<table>
  <thead>
    <tr>
      <th>Metric</th>
      <th>Result</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Page-side links</td>
      <td>0 / 20 reps used llms.txt</td>
    </tr>
    <tr>
      <td>One line in the prompt</td>
      <td>5 / 5 reps used it</td>
    </tr>
    <tr>
      <td>Tokens, with the nudge</td>
      <td>−33% on the same page</td>
    </tr>
  </tbody>
</table>

<h2 id="the-payoff-is-real">The payoff is real</h2>

<p>Let’s start with the good news. In our earlier discovery-layer study, we ran the same evaluation task twice against the same site: once with the agent-discovery files served, once with them missing. Same content, same prompt, same model. The discovery layer cut the cost about in half.</p>

<table>
  <thead>
    <tr>
      <th>Condition</th>
      <th>Tokens (mean)</th>
      <th>Tool calls</th>
      <th>Answer quality (judge)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Discovery layer present</td>
      <td>~123K</td>
      <td>~10</td>
      <td>tied</td>
    </tr>
    <tr>
      <td>Discovery layer absent</td>
      <td>~240K</td>
      <td>~20</td>
      <td>tied</td>
    </tr>
  </tbody>
</table>

<p>Roughly 2x fewer tokens, about half the tool calls, no measurable drop in quality. When an agent can pull a tight markdown index instead of crawling and re-parsing a pile of HTML, it does less work to reach the same answer.</p>

<h2 id="but-the-agent-wont-take-the-payoff-on-its-own">But the agent won’t take the payoff on its own</h2>

<p>Five arms, identical content and identical discovery files (all returning 200). The only thing that changed was how the page points at <code class="language-plaintext highlighter-rouge">/llms.txt</code>. Claude 2.1.141, isolated Docker, five reps each, adoption read from the web server’s request log.</p>

<table>
  <thead>
    <tr>
      <th>Arm</th>
      <th>Signal</th>
      <th>Fetched /llms.txt</th>
      <th>Tokens (avg ± sd)</th>
      <th>Tools</th>
      <th>Page GETs</th>
      <th>Judge</th>
      <th>Pass</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>no-link (control)</td>
      <td>none</td>
      <td>0 / 5</td>
      <td>266,591 ± 32,066</td>
      <td>14.6</td>
      <td>59</td>
      <td>75.2 ± 6.8</td>
      <td>80%</td>
    </tr>
    <tr>
      <td>head link</td>
      <td><code class="language-plaintext highlighter-rouge">&lt;link rel="llms-txt"&gt;</code></td>
      <td>0 / 5</td>
      <td>257,743 ± 53,953</td>
      <td>13.2</td>
      <td>47</td>
      <td>72.4 ± 8.3</td>
      <td>80%</td>
    </tr>
    <tr>
      <td>head link</td>
      <td><code class="language-plaintext highlighter-rouge">&lt;link rel="alternate" type="text/markdown"&gt;</code></td>
      <td>0 / 5</td>
      <td>254,641 ± 47,968</td>
      <td>14.4</td>
      <td>65</td>
      <td>80.4 ± 5.5</td>
      <td>100%</td>
    </tr>
    <tr>
      <td>visible footer link</td>
      <td>“For agents” block</td>
      <td>0 / 5</td>
      <td>251,741 ± 38,857</td>
      <td>12.8</td>
      <td>48</td>
      <td>76.0 ± 3.3</td>
      <td>100%</td>
    </tr>
    <tr>
      <td>nudge</td>
      <td>prompt: “check /llms.txt first”</td>
      <td>5 / 5 (+4/5 sitemap.md)</td>
      <td>177,735 ± 32,131</td>
      <td>17.0</td>
      <td>91</td>
      <td>88.8 ± 1.6</td>
      <td>100%</td>
    </tr>
  </tbody>
</table>

<p>We tried three genuinely different ways to point the page at <code class="language-plaintext highlighter-rouge">/llms.txt</code>: a machine-readable <code class="language-plaintext highlighter-rouge">&lt;link rel="llms-txt"&gt;</code> in the head, a standards-style <code class="language-plaintext highlighter-rouge">&lt;link rel="alternate" type="text/markdown"&gt;</code>, and a plain visible “For agents” footer link a human would click. The agent ignored all three. The four page arms cluster tightly, 252K to 267K tokens and 0 of 5 adoption, with the spread between link styles sitting well inside the noise (token sd of 32K to 54K, judge sd of 3 to 8). The agent received the bytes (it fetched the homepage, head tags and all) and acted on none of them.</p>

<p>It’s not that we picked the wrong link format. Every link format failed. The lever is the instruction, not the markup. We changed one line of the prompt and left the page exactly as it was, with no link at all. Adoption went to five out of five (and four of five also pulled <code class="language-plaintext highlighter-rouge">sitemap.md</code>), and tokens dropped from 266,591 to 177,735. That is 88,856 fewer tokens, a 33% reduction on the same page, attributable purely to the agent using a layer that was already there in every arm. Sitting next to the earlier ~2x, the two figures bracket the opportunity: the prior study measured having the layer at all, this one measures actually using it, isolated to a single line of prompt.</p>

<p>A few details from the run that sharpen the picture:</p>

<ul>
  <li><strong>The cost is ingestion, not generation.</strong> Output tokens were tiny across every arm, roughly 5K to 9K per rep. Almost the entire bill is input plus cache, the cost of reading the site in. That is exactly the cost the discovery layer cuts, which is why “use the index” shows up as a token drop and not a quality drop.</li>
  <li><strong>More fetches, far less content.</strong> The nudge arm actually made more requests than the control (91 page GETs versus 59), yet used fewer tokens. It pulled the lean <code class="language-plaintext highlighter-rouge">.md</code> mirrors (for example <code class="language-plaintext highlighter-rouge">/scorecards/0.2.0/checks/llms-txt.exists.md</code>) instead of full HTML pages. More round trips, a fraction of the bytes each.</li>
  <li><strong>Quality went up, not down.</strong> The nudge arm posted the highest judge score (88.8) and the tightest spread (± 1.6) of any arm, at 100% pass. Using the structured index made the answers cheaper and more consistent.</li>
  <li><strong>Clean measurement.</strong> Zero requests hit the live a14y.dev in any arm. Adoption was read from the static server’s log, filtered to the agent container’s IP, so there is no contamination from background traffic. (a14y.dev’s own copy documents the <code class="language-plaintext highlighter-rouge">llms-txt.exists</code> check, so this was a friendly case for content-based discovery; adoption was still 0 of 5 without the nudge.)</li>
</ul>

<h2 id="this-is-not-just-a-claude-quirk">This is not just a Claude quirk</h2>

<p>It would be easy to write this off as one model’s behavior on one site. It is not. When we went looking for whether any major agent or crawler consumes <code class="language-plaintext highlighter-rouge">llms.txt</code> in the wild, the evidence pointed the same direction.</p>

<p>The cleanest distinction to hold onto is <strong>publish versus consume</strong>. Almost everyone publishes. Cursor serves an <code class="language-plaintext highlighter-rouge">llms.txt</code> for its own docs. Anthropic publishes one. Every site built on <a href="https://www.mintlify.com/docs/ai/llmstxt">Mintlify</a> gets an <code class="language-plaintext highlighter-rouge">llms.txt</code> (and an <code class="language-plaintext highlighter-rouge">llms-full.txt</code>) automatically, zero maintenance, which is how a whole category of docs sites gained one at once. That is a real and healthy trend. But publishing a file is not the same as an agent reading one, and on the consume side the record is close to empty.</p>

<ul>
  <li><a href="https://ahrefs.com/blog/llmstxt-study/"><strong>Ahrefs</strong></a> analyzed 137,210 domains. Of the roughly 38,000 with a valid <code class="language-plaintext highlighter-rouge">llms.txt</code>, 97% received zero requests for that file in the month studied. Of the requests that did land, most were ordinary bots, not AI agents. No AI bot went looking for files that did not exist.</li>
  <li><strong>Google’s John Mueller</strong> has said, more than once, that no AI system currently uses <code class="language-plaintext highlighter-rouge">llms.txt</code>, that you can see in your server logs the bots do not even check for it, and that it is <a href="https://www.searchenginejournal.com/google-says-llms-txt-comparable-to-keywords-meta-tag/544804/">“comparable to the keywords meta tag.”</a> When <code class="language-plaintext highlighter-rouge">llms.txt</code> files showed up on Google’s own developer properties, he said plainly it was a <a href="https://www.seroundtable.com/google-does-not-endorse-llms-txt-40789.html">CMS artifact, not an endorsement</a>.</li>
  <li><strong>Google’s official <a href="https://developers.google.com/search/docs/fundamentals/ai-optimization-guide">generative-AI search guidance</a></strong> now states you do not need to create <code class="language-plaintext highlighter-rouge">llms.txt</code> or any special machine-readable files to appear in AI search, and that Search ignores them: keeping one is fine, it just “won’t harm (nor help)” rankings.</li>
  <li><strong>Independent server-log audits</strong> (<a href="https://www.longato.ch/llmstxt-2026-june/">Adobe AEM</a>, <a href="https://otterly.ai/blog/the-llms-txt-experiment/">OtterlyAI</a>) put verified LLM fetches of <code class="language-plaintext highlighter-rouge">llms.txt</code> at roughly 0.1% to 1% of traffic to those files. Most of the rest is scanners and auditors.</li>
</ul>

<p>And the tools people assume are quietly reading it? For GitHub Copilot, Windsurf, Cline, Aider, Continue, Devin, ChatGPT’s browsing and Atlas modes, and Gemini’s agent, we found no public statement either way (as of this research, mid-2026). Silence is not adoption. We also went in expecting to find that Claude Code would fetch <code class="language-plaintext highlighter-rouge">llms.txt</code> when linked from the page. The hope did not survive verification. Claude Code can show up in server logs fetching <code class="language-plaintext highlighter-rouge">llms.txt</code>, but only when a user or a prompt points it there, not on its own. We don’t have any evidence that any major agent fetches <code class="language-plaintext highlighter-rouge">llms.txt</code> by default.</p>

<h2 id="what-to-actually-do-with-this">What to actually do with this</h2>

<p>The wrong conclusion here would be “don’t bother.” That is not what the data says: the value of the discovery layer is real and significant, and it lands whenever you control the agent or its instructions. Three situations where that is true today.</p>

<h3 id="your-own-agents">Your own agents</h3>

<p>If you build a docs assistant, a support bot, an MCP server, or an internal coding agent that reads your site, you write the prompt. Tell it to start from <code class="language-plaintext highlighter-rouge">/llms.txt</code>. This data says that a single instruction is worth roughly a third of the tokens on the spot, and up to half against a no-discovery baseline.</p>

<h3 id="agentic-tooling-you-can-configure">Agentic tooling you can configure</h3>

<p>A growing set of harnesses let the user or the project point the agent at a markdown index. The payoff shows up the moment the pointer exists. Notably, Chrome’s experimental <a href="https://developer.chrome.com/docs/lighthouse/agentic-browsing/llms-txt">“agentic browsing” audit in Lighthouse</a> already checks for <code class="language-plaintext highlighter-rouge">llms.txt</code>, a hint that the consume side may start catching up to the publish side.</p>

<h3 id="future-proofing-at-near-zero-cost">Future-proofing at near-zero cost</h3>

<p>Publishing the layer is cheap and mostly static. If and when agents start consuming it by default, you are already done. If they never do, you have lost very little, and your own tooling benefited the whole time.</p>

<p>What the data definitely does not support is treating <code class="language-plaintext highlighter-rouge">llms.txt</code> as an SEO move for third-party AI search. Today, the big crawlers mostly ignore it. Publish it because it makes the agents you point at your content dramatically cheaper and more accurate, not because ChatGPT or Gemini will discover it on their own.</p>

<p>Also, <code class="language-plaintext highlighter-rouge">llms.txt</code> is not a standard. It is a good idea that caught on, but a better convention may well replace it. What the data backs is narrower and more durable: a clean and lean discovery layer saves real tokens, and the way you capture that saving today is to tell the model to use it. So bet on the two things that hold no matter how the conventions shake out, the token savings and the instruction, rather than on <code class="language-plaintext highlighter-rouge">llms.txt</code> being the permanent answer.</p>

<h2 id="why-keep-this-in-the-scorecard">Why keep this in the scorecard</h2>

<p>At a14y we score sites on agent-readability, and <code class="language-plaintext highlighter-rouge">llms.txt</code> is part of that score. This research does not weaken that, it sharpens it. A high score means your site is ready to be read cheaply by an agent. Whether a given agent takes you up on it depends on how that agent was built or prompted, which is exactly the variable we just isolated. We would rather tell you both sides than sell you one.</p>

<p>If you want to see the gap for yourself, run a14y against your docs, then point your own agent at the <code class="language-plaintext highlighter-rouge">llms.txt</code> we find (or flag as missing) and watch the token count. That is the experiment that convinced us, and it takes about ten minutes. We would genuinely like to see your numbers, especially if you can get any agent to adopt <code class="language-plaintext highlighter-rouge">llms.txt</code> from a page-side link alone.</p>

<h2 id="caveats">Caveats</h2>

<p>Where to read these numbers with care.</p>

<ul>
  <li><strong>Small n, one setting.</strong> Five reps per arm (adoption is a low-frequency, near-binary event), a single retrieval prompt, a single model (Claude 2.1.141), and a single site. Treat the adoption pattern as directional, not the last word.</li>
  <li><strong>One site’s content.</strong> Every arm serves the same a14y.dev content; only the linking changes. And because a14y.dev documents its own discovery checks, it is a friendly case for content-based discovery. The relative ordering should generalize; the absolute numbers are tied to this content.</li>
  <li><strong>A snapshot of a moving target.</strong> Agent behavior and vendor support change fast. The “no public statement” list and the “no agent fetches by default” finding are true as of mid-2026 and worth re-checking over time.</li>
</ul>

<h2 id="method-notes">Method notes</h2>

<p>The page-side and nudge figures are from the a14y benchmark <code class="language-plaintext highlighter-rouge">llms-txt-linking-2026-06-27</code> (Claude 2.1.141, isolated Docker, n=5 per arm), with adoption read from the static server’s request log filtered to the agent container’s IP. The ~2x figure is from the earlier discovery-layer <a href="https://a14y.dev/research/scorecard-evals/">case study</a>. The agent reached our test build via in-container HTTP, so it received the full page including the head tags and chose not to act on them. External claims are linked inline. Full data and the reproducible harness live in the a14y benchmark repo.</p>

<details>
  <summary>Reproduce this study</summary>

  <p>Score any single site with the same tool the probe drove:</p>

  <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npx a14y https://example.com
</code></pre></div>  </div>

  <p>Then point your own agent at the <code class="language-plaintext highlighter-rouge">llms.txt</code> it finds and compare token counts with and without a “check /llms.txt first” line in the prompt. Benchmark provenance: <code class="language-plaintext highlighter-rouge">llms-txt-linking-2026-06-27</code>, Claude 2.1.141, isolated Docker, n=5 per arm.</p>

</details>]]></content><author><name></name></author><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Agents" /><category term="Developer Relations" /><summary type="html"><![CDATA[A controlled benchmark: a clean llms.txt discovery layer cuts an AI agent's tokens, but no in-page link (a head tag or a visible footer link) got the agent to use it on its own. One line in the prompt did. The lever is the instruction, not the markup.]]></summary></entry><entry><title type="html">Which agent-readiness features actually pay off</title><link href="https://timothyjordan.com/blog/2026/06/25/per-feature-ablation.html" rel="alternate" type="text/html" title="Which agent-readiness features actually pay off" /><published>2026-06-25T00:00:00+00:00</published><updated>2026-06-25T00:00:00+00:00</updated><id>https://timothyjordan.com/blog/2026/06/25/per-feature-ablation</id><content type="html" xml:base="https://timothyjordan.com/blog/2026/06/25/per-feature-ablation.html"><![CDATA[<p><em><a href="https://a14y.dev/research/per-feature-ablation/">Cross-posted from a14y.dev</a></em></p>

<h2 id="summary">Summary</h2>

<p><strong>Ship a markdown mirror and a real meta description first. Skip the agent-skills directory for now.</strong></p>

<p>We took a14y.dev, toggled each of its 11 agent-readiness features on and off, and measured what each one is worth to an AI agent doing a real retrieval task. For Claude, the full discovery layer cuts token use about 47%, and a markdown mirror alone does most of that. But the features do not simply add up: some substitute for each other, one (the agent-skills directory) actively makes things worse, and a judge re-grade found the answers stayed exactly as accurate. The features make the agent faster and more efficient, not smarter. We ran the same probe through Codex and Cursor too, but only Claude’s runs were cleanly instrumented enough to quantify, which is a finding in itself (more on that below).</p>

<table>
  <thead>
    <tr>
      <th>Metric</th>
      <th>Result</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Full layer (Claude)</td>
      <td>−47% tokens</td>
    </tr>
    <tr>
      <td>Best single feature</td>
      <td>−48.6% (md-mirrors)</td>
    </tr>
    <tr>
      <td>Answer quality</td>
      <td>flat, ~75/100 across all variants</td>
    </tr>
  </tbody>
</table>

<h2 id="the-question">The question</h2>

<p>The a14y scorecard rewards a long list of agent-readiness features. A fair question for any site owner with finite time: which of them actually pay off, and do they stack or just overlap? So we ablated them. Starting from a stripped site, we added each feature alone to see what it buys (call it <strong>sparse</strong>). Then, starting from a fully featured site, we removed each feature one at a time to see what it costs (<strong>dense</strong>). The gap between those two numbers is where the interesting behavior lives.</p>

<p>One caveat belongs up front, not in a footnote: this is a black box, and it is moving fast. None of these signals is a ratified web standard. <code class="language-plaintext highlighter-rouge">llms.txt</code>, for instance, is a good idea, not a spec, and it does not look like most agents go hunting for it on their own. But if an agent does encounter it, because you linked it, referenced it in robots.txt, or told the agent to look, it can help a lot. So read these results as a snapshot of how today’s agents behave with today’s signals, not as laws of nature. They will shift as the agents do.</p>

<h2 id="methodology">Methodology</h2>

<p>A focused retrieval probe run against 24 builds of the same site, each exposing a different subset of a14y’s features, across three coding agents.</p>

<table>
  <thead>
    <tr>
      <th>Field</th>
      <th>Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Probe</td>
      <td>5 fact-retrieval questions about a14y.dev (a scorecard version, specific check IDs, a threshold). The site’s discovery structure decides how much the agent has to crawl to answer.</td>
    </tr>
    <tr>
      <td>Features</td>
      <td>11 toggles: <code class="language-plaintext highlighter-rouge">llms-txt</code>, <code class="language-plaintext highlighter-rouge">robots-txt</code>, <code class="language-plaintext highlighter-rouge">sitemap-xml</code>, <code class="language-plaintext highlighter-rouge">sitemap-md</code>, <code class="language-plaintext highlighter-rouge">agents-md</code>, <code class="language-plaintext highlighter-rouge">agent-skills</code>, <code class="language-plaintext highlighter-rouge">md-mirrors</code>, <code class="language-plaintext highlighter-rouge">canonical-link</code>, <code class="language-plaintext highlighter-rouge">meta-description</code>, <code class="language-plaintext highlighter-rouge">og-tags</code>, <code class="language-plaintext highlighter-rouge">json-ld</code></td>
    </tr>
    <tr>
      <td>Variants</td>
      <td><code class="language-plaintext highlighter-rouge">baseline</code> (all off), <code class="language-plaintext highlighter-rouge">only-X</code> (each feature alone), <code class="language-plaintext highlighter-rouge">all-except-X</code> (each removed from full), and <code class="language-plaintext highlighter-rouge">all</code> = 24 builds</td>
    </tr>
    <tr>
      <td>Sparse vs dense</td>
      <td>Sparse = <code class="language-plaintext highlighter-rouge">only-X</code> vs baseline (what a feature buys from zero). Dense = <code class="language-plaintext highlighter-rouge">all-except-X</code> vs all (what removing it costs from a full site).</td>
    </tr>
    <tr>
      <td>Runs</td>
      <td>24 variants × 3 repeats = <strong>72 runs</strong></td>
    </tr>
    <tr>
      <td>Agents</td>
      <td>Claude Code, Codex CLI, Cursor, and Gemini. Only Claude is reported (see <a href="#why-this-is-claude-only">Why this is Claude only</a>).</td>
    </tr>
    <tr>
      <td>Metrics</td>
      <td>Tokens per run (efficiency) and a judge re-grade of answer quality against canonical answers (effectiveness)</td>
    </tr>
    <tr>
      <td>Date</td>
      <td>June 4, 2026</td>
    </tr>
  </tbody>
</table>

<p>The matrix below is Claude. We also ran Codex and Cursor, but only Claude’s runs could be cleanly tied to the build we served, so every number on this page is Claude’s. The “Why this is Claude only” section explains what went wrong with the other two.</p>

<h2 id="what-each-feature-is-worth-claude">What each feature is worth (Claude)</h2>

<p>Token change for each feature, alone and removed-from-full. Negative is fewer tokens. The full layer saves 47%, but markdown mirrors alone save 48.6%, so the discovery features hit diminishing returns fast.</p>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>Alone</th>
      <th>Removed</th>
      <th>Reading</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">md-mirrors</code></td>
      <td>-48.6%</td>
      <td>+10.3%</td>
      <td>Biggest win alone; partly substitutable in a full site</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">agents-md</code></td>
      <td>-45.5%</td>
      <td>-1.9%</td>
      <td>Huge alone, redundant once llms.txt + mirrors exist</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">meta-description</code></td>
      <td>-43.0%</td>
      <td>+43.3%</td>
      <td>Irreplaceable; nothing else substitutes for it</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">canonical-link</code></td>
      <td>-40.4%</td>
      <td>+13.0%</td>
      <td>Moderate marginal value</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">og-tags</code></td>
      <td>-40.0%</td>
      <td>+13.2%</td>
      <td>Moderate marginal value</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">agent-skills</code></td>
      <td>-35.1%</td>
      <td>-7.8%</td>
      <td>Helps alone, distracts when stacked</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">llms-txt</code></td>
      <td>-31.6%</td>
      <td>+25.4%</td>
      <td>Meaningful marginal value</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">sitemap-md</code></td>
      <td>-28.6%</td>
      <td>+12.2%</td>
      <td>Moderate marginal value</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">robots-txt</code></td>
      <td>-25.7%</td>
      <td>+23.7%</td>
      <td>Meaningful marginal value</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">json-ld</code></td>
      <td>-25.3%</td>
      <td>+6.5%</td>
      <td>Small marginal value</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">sitemap-xml</code></td>
      <td>-16.3%</td>
      <td>+3.8%</td>
      <td>Smallest effect</td>
    </tr>
  </tbody>
</table>

<p>“Alone” is the feature added to a stripped site (sparse). “Removed” is the feature taken out of a full site (dense). A negative number for the Alone column means fewer tokens required with the feature. A positive number in the Removed column means more tokens required without the feature. A negative number in the Removed column means more tokens required with the feature.</p>

<h2 id="four-findings">Four findings</h2>

<h3 id="markdown-mirrors-are-the-biggest-single-win">Markdown mirrors are the biggest single win</h3>

<p>Alone, a markdown mirror cuts Claude’s tokens 48.6%, more than any other feature and more than the full layer combined. But remove it from a full site and the cost is only 10.3%, because llms.txt, sitemap.md, and a meta description can partly cover for it. Starting from zero, though, a mirror is the highest-impact thing you can ship.</p>

<h3 id="a-meta-description-is-uniquely-irreplaceable">A meta description is uniquely irreplaceable</h3>

<p>It cuts 43.0% alone and costs 43.3% to remove. Nothing else in the matrix is that symmetric. Every other feature substitutes for something; the meta description does a job none of the others can. The likely reason: it short-circuits the “what is this page even about” step an agent does early in its navigation. Surfacing that one line cheaply seems to matter more than any amount of structured discovery.</p>

<h3 id="agentsmd-is-redundant-once-you-have-the-basics">AGENTS.md is redundant once you have the basics</h3>

<p>Alone it is huge (−45.5%), but removing it from a full site costs nothing (−1.9%, within noise). The combination of llms.txt and markdown mirrors already carries the information it would. So AGENTS.md is a fine choice if it is the easiest file for you to maintain, but it is not pulling independent weight when the rest of the layer is present.</p>

<h3 id="the-agent-skills-directory-is-actively-distracting">The agent-skills directory is actively distracting</h3>

<p>It helps a little alone (−35.1%), but in a full site, removing it makes Claude faster (−7.8%). The <code class="language-plaintext highlighter-rouge">.well-known/agent-skills/</code> index appears to pull Claude into a wrong-direction exploration when other signals are already present. It is the only clear “stop doing this” in the study, small in absolute tokens but consistent.</p>

<h2 id="faster-and-more-efficient-not-smarter">Faster and more efficient, not smarter</h2>

<p>Efficiency is only half the question. We re-graded every answer with a judge against the canonical answers. Claude’s answer quality is essentially flat at about 75 of 100 across all 24 variants, while token use swings 2×. The discovery layer makes Claude reach the answer with less work; it does not change whether the answer is right. That is the clean version of the win: the same answers, reached with far less effort.</p>

<p>The re-grade also corrected the record. Our first pass used a strict substring rubric, and one question’s canonical answer (“5%”) matched inside a wrong answer (“15%”), so every agent “passed” a question they all actually got wrong. The judge caught it. Pass-rate numbers from the first draft were inflated by that bug; the quality figures here come from the re-grade.</p>

<h2 id="why-this-is-claude-only">Why this is Claude only</h2>

<p>We initially did this audit with four agents: Claude, Codex, Cursor, and Gemini. However, today’s agent CLIs are still hard to benchmark in a controlled way, and so only Claude could be measured cleanly.</p>

<p>Codex calls a hosted <code class="language-plaintext highlighter-rouge">web_search</code> tool that cannot be turned off, so it reaches the open web (the live a14y.dev included), not just the controlled build. Its token counts measure that off-target exploration, not the variant in front of it. Cursor, in headless mode, does not surface its fetches at all, so we have no record of what it actually read; its answers looked reasonable, but we cannot attribute its cost to any one feature. Gemini’s CLI could not fetch our target and was dropped.</p>

<p>That left Claude as the only agent whose runs we can tie to the exact build it was given, which is why every number on this page is Claude’s. The finding underneath stands on its own: agents differ a great deal in how they read a site, and the tooling to measure them rigorously is still catching up. We will fold other agents back in as their CLIs expose what they fetch. The caveats below list the specific instrumentation gaps.</p>

<h2 id="what-to-ship">What to ship</h2>

<h3 id="for-site-owners">For site owners</h3>

<p>Assuming Claude-class agents are your main readers: ship a markdown mirror first, it is the largest win and the hardest to substitute. Always include a meta description; it is cheap and uniquely irreplaceable. Add llms.txt and sitemap.md as a pair (they back each other up). Treat AGENTS.md as optional once you have mirrors and llms.txt. And skip <code class="language-plaintext highlighter-rouge">.well-known/agent-skills/</code> unless you have a specific reason to publish skills.</p>

<h3 id="for-agent-and-model-builders">For agent and model builders</h3>

<p>A discovery file can be read as a jump list or as a reading list, and those have very different cost profiles for the same content (we saw both behaviors across the agents we ran). The outsized pull of the meta description suggests agents spend real effort early on “what is this page about”; surfacing that cheaply may matter more than richer discovery. And the agent-skills directory needs clearer semantics: more than one agent failed to benefit from it, and Claude was slowed by it.</p>

<h3 id="for-everyone-while-this-is-still-a-black-box">For everyone, while this is still a black box</h3>

<p>These are not standards, and the agents are changing under us. Linking your agent files so they get found matters as much as having them. The safe bets are the ones that held across agents and map to plain readability: a clean mirror and a real description. We will re-run this as the agents evolve.</p>

<h2 id="caveats">Caveats</h2>

<p>Where to read these numbers with care.</p>

<ul>
  <li><strong>One task.</strong> A 5-question retrieval probe. Long-form summarization, code generation, or multi-hop reasoning could rank the features very differently. The defensible generalization is “for focused retrieval.”</li>
  <li><strong>One site.</strong> Every variant serves the same a14y.dev content; only the discovery scaffolding changes. Whether the deltas hold on other sites is untested.</li>
  <li><strong>Only Claude is reported.</strong> We ran Codex and Cursor too, but Codex’s hosted <code class="language-plaintext highlighter-rouge">web_search</code> cannot be disabled and reaches the open web, and Cursor’s headless mode does not surface its fetches, so neither could be cleanly tied to the served build. Gemini was dropped (its CLI could not fetch). See “Why this is Claude only.”</li>
  <li><strong>Rubric bug.</strong> The original strict-substring rubric scored a universally-wrong question as passing (“5%” inside “15%”). Quality figures here use the judge re-grade; the first-draft pass rates were inflated.</li>
  <li><strong>Mirror quality is a lower bound.</strong> The mirrors used v0.2.0-style output. v0.3.0-draft tightens mirror checks, so a fully compliant mirror could save more than 48.6%.</li>
</ul>

<details>
  <summary>Reproduce this study</summary>

  <p>Score any single site with the same tool the probe drove:</p>

  <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npx a14y https://example.com
</code></pre></div>  </div>

  <p>The ablation builds the same site 24 ways by toggling each feature, then runs an identical retrieval probe against each build and aggregates tokens and judge-graded quality. Source data: the 2026-06-04 per-feature ablation.</p>

</details>]]></content><author><name></name></author><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Agents" /><category term="Developer Relations" /><summary type="html"><![CDATA[We toggled each of a14y.dev's 11 agent-readiness features on and off and measured what each one is worth to an AI agent. A markdown mirror and a real meta description do most of the work, the features don't simply stack, and one of them actively makes things worse.]]></summary></entry><entry><title type="html">The State of Agent Readability on the Web</title><link href="https://timothyjordan.com/blog/2026/06/25/state-of-agent-readability.html" rel="alternate" type="text/html" title="The State of Agent Readability on the Web" /><published>2026-06-25T00:00:00+00:00</published><updated>2026-06-25T00:00:00+00:00</updated><id>https://timothyjordan.com/blog/2026/06/25/state-of-agent-readability</id><content type="html" xml:base="https://timothyjordan.com/blog/2026/06/25/state-of-agent-readability.html"><![CDATA[<p><em><a href="https://a14y.dev/research/state-of-agent-readability/">Cross-posted from a14y.dev</a></em></p>

<h2 id="summary">Summary</h2>

<p><strong>On most of the popular web, an AI agent burns roughly twice the tokens it should.</strong></p>

<p>We scored the 50,074 most-visited websites for how well an agent can discover, parse, and comprehend them. The plumbing search engines asked for is everywhere: 88% expose a robots.txt. The layer agents need is not. Only 27% ship an <code class="language-plaintext highlighter-rouge">llms.txt</code> and 25% an <code class="language-plaintext highlighter-rouge">AGENTS.md</code>. The median site scores 52/100, and not one of the 50,074 scored “excellent.” Raising a site’s a14y score roughly halves the tokens an agent spends to use it. The web is wide open for improvement and <a href="https://a14y.dev/leaderboard/">very few</a> have moved.</p>

<table>
  <thead>
    <tr>
      <th>Metric</th>
      <th>Result</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Sites scored</td>
      <td>50,074</td>
    </tr>
    <tr>
      <td>Median score</td>
      <td>52 / 100</td>
    </tr>
    <tr>
      <td>Scored “excellent”</td>
      <td>none (best is 83)</td>
    </tr>
    <tr>
      <td>llms.txt</td>
      <td>27%</td>
    </tr>
    <tr>
      <td>AGENTS.md</td>
      <td>25%</td>
    </tr>
  </tbody>
</table>

<h2 id="the-question">The question</h2>

<p>AI agents are becoming a primary way people reach the web. They read sites, follow links, and synthesize answers on someone’s behalf. That raises a concrete question for every site owner: <strong>can an agent use my site efficiently?</strong> The a14y scorecard answers it for one site. We ran it across the web at scale to ask the bigger version: is the <em>web</em> ready for agents?</p>

<p>“Ready” here is mechanical, not subjective. Does the site expose what an agent needs to find and parse it: a usable <code class="language-plaintext highlighter-rouge">llms.txt</code>, an <code class="language-plaintext highlighter-rouge">AGENTS.md</code>, a sitemap, clean robots rules, semantic HTML, all measured by the v0.2.0 scorecard’s checks. It does not judge the quality of the content itself.</p>

<h2 id="methodology">Methodology</h2>

<p>A single point-in-time scan of the CrUX most-visited list, scored in page mode against the published scorecard. Run on cloud infrastructure, off any residential IP.</p>

<table>
  <thead>
    <tr>
      <th>Field</th>
      <th>Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Source</td>
      <td>CrUX top-100k most-visited origins (<a href="https://github.com/zakird/crux-top-lists/blob/main/data/global/current.csv.gz">zakird/crux-top-lists</a>, global)</td>
    </tr>
    <tr>
      <td>Sample</td>
      <td><strong>50,074</strong> scored: 100,000 rows → 64,746 after dedup to one per registrable domain → 50,074 reachable &amp; not adult/gambling/spam</td>
    </tr>
    <tr>
      <td>Mode</td>
      <td>Page mode: each site’s homepage, not a full-site crawl</td>
    </tr>
    <tr>
      <td>Scorecard</td>
      <td>Published <code class="language-plaintext highlighter-rouge">v0.2.0</code> (38 checks) and the in-flight <code class="language-plaintext highlighter-rouge">v0.3.0-draft</code> (45 checks), scored 0–100. Headline scores use v0.2.0; the analysis below draws on both, tagged.</td>
    </tr>
    <tr>
      <td>Tool</td>
      <td><code class="language-plaintext highlighter-rouge">npx a14y</code> (the same audit anyone can run)</td>
    </tr>
    <tr>
      <td>Infra</td>
      <td>Sharded across 64 Cloud Run tasks; run data archived in object storage</td>
    </tr>
    <tr>
      <td>Source label</td>
      <td><code class="language-plaintext highlighter-rouge">crux batch-2026-06-15-100k</code></td>
    </tr>
    <tr>
      <td>Date</td>
      <td>June 18, 2026</td>
    </tr>
  </tbody>
</table>

<p>Page mode is deliberate. Scoring one homepage per site keeps a survey this size tractable and comparable. It proxies a site’s agent readiness; it does not capture readiness that lives deeper in the site.</p>

<h2 id="results-the-web-scores-low">Results: the web scores low</h2>

<p>Distribution of agent-readability scores across all 50,074 sites. Mean 50.5, median 52.</p>

<table>
  <thead>
    <tr>
      <th>Band</th>
      <th>Sites</th>
      <th>Share</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Excellent (85–100)</td>
      <td>0</td>
      <td>0%</td>
    </tr>
    <tr>
      <td>Good (70–84)</td>
      <td>1,678</td>
      <td>3.4%</td>
    </tr>
    <tr>
      <td>Fair (50–69)</td>
      <td>26,530</td>
      <td>53%</td>
    </tr>
    <tr>
      <td>Poor (0–49)</td>
      <td>21,866</td>
      <td>43.7%</td>
    </tr>
  </tbody>
</table>

<p>The popular web clusters in the middle and below. <strong>43.7%</strong> of sites score under 50, and only <strong>3.4%</strong> clear 70. <strong>Not one site in the top 50,074 scored “excellent” (85 or above)</strong>; the single best managed 83. Whatever the web was optimized for, it wasn’t agents.</p>

<p><a href="https://a14y.dev/research/web/">Explore all 50,074 sites on the web leaderboard →</a></p>

<h2 id="results-the-agent-era-layer-is-missing">Results: the agent-era layer is missing</h2>

<p>What the most-visited sites actually expose to agents, against the published v0.2.0 scorecard and the in-flight v0.3.0-draft.</p>

<p>The signals search engines taught the web to ship are nearly everywhere: 87% expose a robots.txt and 63% a sitemap. But only 78% actually let the major AI crawlers in, so close to a fifth of the most-visited sites block them outright.</p>

<p>The signals agents need are not just rare, they are often a mirage. 29% ship an <code class="language-plaintext highlighter-rouge">llms.txt</code>, and most are real (97% are non-empty). The other agent files mostly answer with a 200 but not a usable document: 30% appear to have an <code class="language-plaintext highlighter-rouge">AGENTS.md</code> while only 0.2% carry the expected sections, and 24% answer at <code class="language-plaintext highlighter-rouge">/sitemap.md</code> while only 0.1% are a real structured sitemap.</p>

<p>The markdown mirror tells the same story, and the draft scorecard makes it sharper. 27% advertise one, but under v0.3.0-draft only 1.8% of those mirrors are actually markdown rather than HTML. And the page itself often needs a browser to read: v0.3.0-draft finds 74% serve real homepage content in the initial HTML, which leaves about a quarter as JavaScript shells that agents without a JS engine (Claude, Perplexity, OpenAI’s SearchBot) see as blank.</p>

<table>
  <thead>
    <tr>
      <th>Signal</th>
      <th>Sites</th>
      <th>What’s behind the number</th>
      <th>From</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>robots.txt allows AI bots</td>
      <td>78%</td>
      <td>22% block GPTBot, ClaudeBot, CCBot, or Google-Extended</td>
      <td><code class="language-plaintext highlighter-rouge">v0.2.0</code></td>
    </tr>
    <tr>
      <td>llms.txt</td>
      <td>29%</td>
      <td>97% of them are non-empty, a real file</td>
      <td><code class="language-plaintext highlighter-rouge">v0.2.0</code></td>
    </tr>
    <tr>
      <td>AGENTS.md</td>
      <td>30%</td>
      <td>only 0.2% carry the expected sections</td>
      <td><code class="language-plaintext highlighter-rouge">v0.2.0</code></td>
    </tr>
    <tr>
      <td>sitemap.md</td>
      <td>24%</td>
      <td>only 0.1% are a real structured sitemap</td>
      <td><code class="language-plaintext highlighter-rouge">v0.2.0</code></td>
    </tr>
    <tr>
      <td>Markdown mirror advertised</td>
      <td>27%</td>
      <td>4% serve it on request via content negotiation</td>
      <td><code class="language-plaintext highlighter-rouge">v0.2.0</code></td>
    </tr>
    <tr>
      <td>Mirror is valid markdown</td>
      <td>1.8%</td>
      <td>of advertised mirrors are actually markdown, not HTML</td>
      <td><code class="language-plaintext highlighter-rouge">v0.3.0-draft</code></td>
    </tr>
    <tr>
      <td>Homepage server-renders content</td>
      <td>74%</td>
      <td>26% are JavaScript shells, invisible to agents that don’t run JS</td>
      <td><code class="language-plaintext highlighter-rouge">v0.3.0-draft</code></td>
    </tr>
    <tr>
      <td>No consent interstitial</td>
      <td>93%</td>
      <td>7% gate content behind a wall an agent can’t click</td>
      <td><code class="language-plaintext highlighter-rouge">v0.3.0-draft</code></td>
    </tr>
    <tr>
      <td>JSON-LD structured data</td>
      <td>37%</td>
      <td>6% include a dateModified</td>
      <td><code class="language-plaintext highlighter-rouge">v0.2.0</code></td>
    </tr>
    <tr>
      <td>Glossary link</td>
      <td>0.5%</td>
      <td>effectively nobody</td>
      <td><code class="language-plaintext highlighter-rouge">v0.2.0</code></td>
    </tr>
  </tbody>
</table>

<details>
  <summary>Full reference: every check, by theme</summary>

  <p>Adoption for all 38 v0.2.0 checks, plus the 5 checks v0.3.0-draft adds. “Of applicable” excludes sites where a check does not apply (the markdown sub-checks, for example, only apply once a mirror exists). Three crawl-dependent checks (<code class="language-plaintext highlighter-rouge">discovery.in-page-link</code>, <code class="language-plaintext highlighter-rouge">discovery.indexed</code>, <code class="language-plaintext highlighter-rouge">discovery.no-duplicate-content</code>) need a multi-page crawl and were not measured by this single-page survey.</p>

  <table>
    <thead>
      <tr>
        <th>Discoverability</th>
        <th>Of all</th>
        <th>Of applicable</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">sitemap-md.has-structure</code></td>
        <td>0%</td>
        <td>0%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">llms-txt.md-extensions</code></td>
        <td>0%</td>
        <td>0%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">agents-md.has-min-sections</code></td>
        <td>0%</td>
        <td>0%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">llms-txt.content-type</code></td>
        <td>5%</td>
        <td>19%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">sitemap-md.exists</code></td>
        <td>24%</td>
        <td>n/a</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">sitemap-xml.has-lastmod</code></td>
        <td>24%</td>
        <td>50%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">llms-txt.non-empty</code></td>
        <td>28%</td>
        <td>97%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">llms-txt.exists</code></td>
        <td>29%</td>
        <td>n/a</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">agents-md.exists</code></td>
        <td>30%</td>
        <td>n/a</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">sitemap-xml.valid</code></td>
        <td>47%</td>
        <td>76%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">sitemap-xml.exists</code></td>
        <td>63%</td>
        <td>n/a</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">robots-txt.allows-ai-bots</code></td>
        <td>78%</td>
        <td>n/a</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">robots-txt.exists</code></td>
        <td>87%</td>
        <td>n/a</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">robots-txt.allows-llms-txt</code></td>
        <td>91%</td>
        <td>n/a</td>
      </tr>
    </tbody>
  </table>

  <table>
    <thead>
      <tr>
        <th>Markdown mirror</th>
        <th>Of all</th>
        <th>Of applicable</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">markdown.frontmatter</code></td>
        <td>0%</td>
        <td>0%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">markdown.sitemap-section</code></td>
        <td>0%</td>
        <td>0%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">markdown.alternate-link</code></td>
        <td>0%</td>
        <td>0%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">markdown.canonical-header</code></td>
        <td>0%</td>
        <td>0%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">markdown.content-negotiation</code></td>
        <td>4%</td>
        <td>4%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">markdown.mirror-suffix</code></td>
        <td>27%</td>
        <td>27%</td>
      </tr>
    </tbody>
  </table>

  <table>
    <thead>
      <tr>
        <th>Structured data</th>
        <th>Of all</th>
        <th>Of applicable</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">html.json-ld.date-modified</code></td>
        <td>6%</td>
        <td>17%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">html.json-ld.breadcrumb</code></td>
        <td>10%</td>
        <td>27%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">html.json-ld</code></td>
        <td>37%</td>
        <td>37%</td>
      </tr>
    </tbody>
  </table>

  <table>
    <thead>
      <tr>
        <th>Content structure</th>
        <th>Of all</th>
        <th>Of applicable</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">html.glossary-link</code></td>
        <td>0%</td>
        <td>0%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">html.text-ratio</code></td>
        <td>45%</td>
        <td>45%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">html.headings</code></td>
        <td>61%</td>
        <td>61%</td>
      </tr>
    </tbody>
  </table>

  <table>
    <thead>
      <tr>
        <th>HTML metadata</th>
        <th>Of all</th>
        <th>Of applicable</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">html.og-description</code></td>
        <td>52%</td>
        <td>52%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">html.canonical-link</code></td>
        <td>55%</td>
        <td>55%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">html.og-title</code></td>
        <td>55%</td>
        <td>55%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">html.meta-description</code></td>
        <td>65%</td>
        <td>65%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">html.lang-attribute</code></td>
        <td>82%</td>
        <td>82%</td>
      </tr>
    </tbody>
  </table>

  <table>
    <thead>
      <tr>
        <th>HTTP</th>
        <th>Of all</th>
        <th>Of applicable</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">http.content-type-html</code></td>
        <td>83%</td>
        <td>83%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">http.redirect-chain</code></td>
        <td>95%</td>
        <td>n/a</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">http.status-200</code></td>
        <td>97%</td>
        <td>n/a</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">http.no-noindex-noai</code></td>
        <td>100%</td>
        <td>n/a</td>
      </tr>
    </tbody>
  </table>

  <table>
    <thead>
      <tr>
        <th>Added in v0.3.0-draft</th>
        <th>Of all</th>
        <th>Of applicable</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">markdown.valid-markdown</code></td>
        <td>0%</td>
        <td>2%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">markdown.size-reduction</code></td>
        <td>5%</td>
        <td>17%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">markdown.navigation-stripped</code></td>
        <td>14%</td>
        <td>52%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">html.ssr-content</code></td>
        <td>74%</td>
        <td>74%</td>
      </tr>
      <tr>
        <td><code class="language-plaintext highlighter-rouge">http.no-interstitial</code></td>
        <td>93%</td>
        <td>93%</td>
      </tr>
    </tbody>
  </table>

</details>

<h2 id="and-the-fix-works">And the fix works</h2>

<p>The gap matters because closing it pays off. In a controlled A/B (<a href="https://a14y.dev/research/scorecard-evals/">full case study</a>), serving the same site with the agent-readiness layer, versus without it, sharply cut what an AI agent spent to use it, with no loss in answer quality.</p>

<table>
  <thead>
    <tr>
      <th>Metric</th>
      <th>Result</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>a14y score</td>
      <td>37 → 89</td>
    </tr>
    <tr>
      <td>Agent tokens</td>
      <td>−49%</td>
    </tr>
    <tr>
      <td>Tool calls</td>
      <td>−52%</td>
    </tr>
    <tr>
      <td>Wall-clock</td>
      <td>−30%</td>
    </tr>
    <tr>
      <td>Answer quality</td>
      <td>tied (84 vs 83)</td>
    </tr>
  </tbody>
</table>

<p>Raising one site’s a14y score from 37 to 89 cut the agent’s token use about 49% and its tool calls about 52%, while an independent judge rated the answers statistically indistinguishable. Put the two findings together. <strong>The agent-readiness layer roughly halves what an agent spends to use a site, and 73% of the most-visited web hasn’t shipped it.</strong></p>

<h2 id="what-it-means">What it means</h2>

<h3 id="for-site-owners">For site owners</h3>

<p>Agent readiness is still a near-empty field. Roughly three in four popular sites haven’t shipped the layer, and none has it fully dialed in. That makes it a cheap, early-mover advantage: run <code class="language-plaintext highlighter-rouge">npx a14y</code> on your site, ship the top fixes, and roughly halve what every agent spends to use you.</p>

<h3 id="for-agent--model-builders">For agent &amp; model builders</h3>

<p>The signals you’d want to lean on can’t be assumed yet. Only about 1 in 4 popular sites expose an <code class="language-plaintext highlighter-rouge">llms.txt</code> or <code class="language-plaintext highlighter-rouge">AGENTS.md</code>, so most of the time agents still fall back to the expensive path: fetching and parsing raw HTML. This dataset quantifies exactly where the gaps are.</p>

<h3 id="for-the-a14y-project">For the a14y project</h3>

<p>This is the baseline. We’ll re-run the survey on each scorecard release and track whether the web’s agent readiness moves: a standing measure of how the agent-readable web is (or isn’t) being built.</p>

<h2 id="caveats">Caveats</h2>

<p>What this survey does not show, and where to read the numbers with care.</p>

<ul>
  <li><strong>Page mode.</strong> Each site is scored on its homepage only, a proxy for the site rather than a full-crawl verdict. Sites whose agent readiness lives in deeper sections are understated.</li>
  <li><strong>Point-in-time.</strong> A single snapshot (June 18, 2026). Sites change; this is a frame, not a film.</li>
  <li><strong>Popularity bias.</strong> CrUX skews toward large, heavily-trafficked sites; the long tail of the web likely looks different.</li>
  <li><strong>Reachable sites only.</strong> Origins that didn’t answer (DNS/TLS failures, bot or geo blocks) are dropped, so the sample is the reachable popular web.</li>
  <li><strong>Scorecard-specific.</strong> Scores are against v0.2.0’s 38 checks; a different scorecard shifts the absolute numbers (the leaderboard also carries a 0.3.0-draft scoring for comparison).</li>
  <li><strong>Filtered.</strong> Adult, gambling, and spam domains are removed by a heuristic denylist, which is imperfect.</li>
</ul>

<details>
  <summary>Reproduce this study</summary>

  <p>Score any single site with the same tool the survey uses:</p>

  <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npx a14y https://example.com --scorecard 0.2.0
</code></pre></div>  </div>

  <p>The survey itself is the same audit fanned out across the CrUX list. Batch provenance: <code class="language-plaintext highlighter-rouge">crux batch-2026-06-15-100k</code>, 50,074 sites, generated June 18, 2026.</p>

</details>]]></content><author><name></name></author><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Agents" /><category term="Developer Relations" /><summary type="html"><![CDATA[We scored the 50,074 most-visited websites for how well an AI agent can discover, parse, and comprehend them. The median scores 52 of 100, not one scored excellent, and roughly three in four haven't shipped the agent-readability layer that halves what an agent spends to use a site.]]></summary></entry><entry><title type="html">The Ghost of PageRank: AI Spam Will Backfire</title><link href="https://timothyjordan.com/blog/2026/05/26/ai-spam-will-backfire.html" rel="alternate" type="text/html" title="The Ghost of PageRank: AI Spam Will Backfire" /><published>2026-05-26T00:00:00+00:00</published><updated>2026-05-26T00:00:00+00:00</updated><id>https://timothyjordan.com/blog/2026/05/26/ai-spam-will-backfire</id><content type="html" xml:base="https://timothyjordan.com/blog/2026/05/26/ai-spam-will-backfire.html"><![CDATA[<p>In the early days of search, the people who built and tuned websites were called webmasters, and a good number of them spent their time trying to trick the crawler. This started before Google. On engines like AltaVista, Lycos, and Excite, which leaned heavily on on-page text, the moves were crude: stuff a meta tag with repeated keywords, hide white text on a white background, climb the rankings. Reverse-engineering the algorithm to beat it was the game from the very beginning.</p>

<p>Google arrived in 1998 with a better idea. PageRank treated an inbound link as a vote of confidence, on the assumption that a genuinely useful page would accumulate more high-quality citations over time than a thin one. But the core mechanics were <a href="http://infolab.stanford.edu/pub/papers/google.pdf">published in an academic paper</a>, so they were never a secret. If links were votes, you just needed more votes. Webmasters figured that out immediately and built the machinery to manufacture them: automated link farms, guestbook spam, Google bombing. None of it waited for permission, and none of it waited for tools. By the early 2000s it was a full shadow economy.</p>

<p>Google’s first real answer, came in November 2003 when the <a href="https://www.searchenginejournal.com/google-algorithm-history/florida-update/">“Florida” update</a> wiped out a swath of sites built on spam tactics, right before the holiday shopping season. The message was clear: game the algorithm and you could lose your rankings overnight. The tools came afterward, as a kind of peace offering. <a href="https://thexeo.com/2024/03/16/the-history-of-google-search-console-and-common-misconceptions/">Google Sitemaps launched in 2005</a> (renamed Webmaster Tools in 2006, eventually Search Console) and finally gave webmasters a legitimate channel to see crawl errors, submit URLs, and understand how the bot actually saw their site. Google paired it with <a href="https://developers.google.com/search/docs/essentials/spam-policies">guidelines that named the bad behaviors plainly</a>, link schemes, keyword stuffing, cloaking, doorway pages, and over time made the consequences explicit: these tactics wouldn’t merely fail to help, they would actively <em>lower</em> your rank, and a <a href="https://support.google.com/webmasters/answer/9044175">Manual Actions report</a> would tell you for certain whether you’d been hit. The whole shadow economy of link farms slowly turned from an edge into a liability.</p>

<p>Now fast-forward to today, because the same arc is repeating against a new target, and how it ended last time tells you what to actually do now.</p>

<p>People have figured out that models are trained on, and increasingly retrieve from, what’s available on the web. So picture a model asked for an infrastructure recommendation. It has absorbed a lot of “top 10 best tools for X” content. The gameable insight writes itself: produce a pile of those lists, make sure your company always lands at or near the top, and seed them everywhere a crawler might look. It’s PageRank’s link farm, reincarnated. Same move, new target.</p>

<p>This is the “get rich quick” side of AEO (agent/answer engine optimization), or GEO (generative engine optimization). And the straight answer to “what are the model companies doing about it” is: it’s early, and it’s uneven. But it’s worth noting where Google has already landed, because they have been here before. In <a href="https://developers.google.com/search/docs/fundamentals/ai-optimization-guide">Google’s guide to optimizing for generative AI features</a>, they tell site owners to <em>ignore</em> a set of AI-specific tactics, and one of the named tactics is pursuing inauthentic brand mentions. That is, in plain terms, the seeding-fake-top-10-lists move. Google is also explicit that, from their perspective, optimizing for generative AI search is <a href="https://www.searchenginejournal.com/googles-new-ai-search-guide-calls-aeo-and-geo-still-seo/575026/">still just SEO</a>, and that commodity content (the generic “7 Tips for X” piece anyone could reproduce) doesn’t earn visibility.</p>

<p>There’s a complication this time that early SEO didn’t have, and it’s the one that should worry you most: it’s now trivially easy to <em>generate</em> the content. In the link-farm era you at least had to author pages. Today you get slop: AI-generated text, unreviewed, carrying no real signal, nothing new or unique. Hundreds of top 10 lists, your company conveniently at the top, published at an endpoint that nothing but a crawler will ever visit. The hope is that all the robots ingest it and none of the humans notice.</p>

<p>And right now, this appears to work.</p>

<p>Let’s be precise about <em>why</em> it works. It works only because the algorithms haven’t yet caught up to the opportunity to penalize it. That’s the entire reason. In SEO’s early years, manipulative tactics often worked for a while before Google caught on; today the opposite holds: sites caught in link schemes face swift algorithmic devaluation, and recovery can take months or years. When the AEO algorithms catch up, and they will, this content doesn’t just stop being evaluated favorably. It becomes the thing that gets you <em>penalized</em>.</p>

<p>So why does this matter, and what should you actually do?</p>

<p>I’m not going to moralize about whether generating slop to nudge recommendations is good or bad. Right now it’s a Wild West strategy, and that just is what it is. What I’ll say instead is the part that’s actually defensible: it is not a long-term strategy. It has a shelf life, and the shelf life is “until the algorithm notices.”</p>

<p>The long-term play is the same one it always was. <strong>Invest in the core principles and value of the business.</strong> Make your product genuinely, unmistakably great, for humans <em>and</em> for agents. Be the best, so that when someone asks a model for the best recommendation and the model is accurate, it arrives at you. Not because you gamed the retrieval layer, but because you <em>are</em> the answer.</p>

<p>This is not a new conclusion. It’s exactly where the SEO industry landed after a decade of penalties. There’s a real, useful technical checklist (make sure the search engine can crawl, read, and index your content properly), and that checklist is worth following. But it was never a substitute for having something worth indexing.</p>

<p>The AEO version is the same shape. Follow the technical checklist: make your site agent-readable, structured, clean, accessible. If it’s a mess for humans to navigate, it’ll be a mess for agents too, and there’s a fast-growing set of practices for getting this right (serve markdown, support <code class="language-plaintext highlighter-rouge">.md</code> endpoints, publish <code class="language-plaintext highlighter-rouge">llms.txt</code> and <code class="language-plaintext highlighter-rouge">sitemap.md</code>, expose search and MCP tools, the same <a href="https://timothyjordan.com/blog/2026/03/23/agent-readability-spec.html">agent-readability work I’ve written about before</a> that you can audit and implement with the <a href="https://a14y.dev">a14y scorecard and toolset</a>. Then do the part that actually compounds: produce great content, build a great website, get referenced for real. Those moves are good for your customers, good for your business, <em>and</em> good for your discovery, all at once. Everything else is just borrowing visibility from a future penalty.</p>

<p>If you’re working through how your org should show up for agents, or wrestling with the strategy and governance questions underneath it, I’d love to learn more and discuss.</p>]]></content><author><name></name></author><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Agents" /><category term="Developer Relations" /><category term="AI Transformation" /><summary type="html"><![CDATA[In the early days of web search, webmasters tried to cheat search algorithms. We're seeing the same thing playing out by those who are trying to game model training with slop content.]]></summary></entry><entry><title type="html">Raising an a14y score from 37 to 89 halved an AI agent’s token use</title><link href="https://timothyjordan.com/blog/2026/05/16/raising-a14y-score-halved-token-use.html" rel="alternate" type="text/html" title="Raising an a14y score from 37 to 89 halved an AI agent’s token use" /><published>2026-05-16T00:00:00+00:00</published><updated>2026-05-16T00:00:00+00:00</updated><id>https://timothyjordan.com/blog/2026/05/16/raising-a14y-score-halved-token-use</id><content type="html" xml:base="https://timothyjordan.com/blog/2026/05/16/raising-a14y-score-halved-token-use.html"><![CDATA[<p><em><a href="https://a14y.dev/research/scorecard-evals/">Cross-posted from a14y.dev</a></em></p>

<h2 id="summary">Summary</h2>

<p>Same docs, same answer quality, statistically indistinguishable judge scores. The only thing we changed was running an a14y audit and shipping the top fixes, bringing the site’s a14y score from 37 to 89. That roughly halved Claude’s token use and tool calls on the same evaluation task.</p>

<table>
  <thead>
    <tr>
      <th>Metric</th>
      <th>Result</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>a14y score</td>
      <td>37 → 89</td>
    </tr>
    <tr>
      <td>Tokens</td>
      <td>−49%</td>
    </tr>
    <tr>
      <td>Tool calls</td>
      <td>−52%</td>
    </tr>
    <tr>
      <td>Wall-clock</td>
      <td>−30%</td>
    </tr>
    <tr>
      <td>Judge score</td>
      <td>tied (84 ± 3 vs 83 ± 5)</td>
    </tr>
  </tbody>
</table>

<h2 id="the-question">The question</h2>

<p>Does raising an a14y score actually translate into AI agent efficiency? We took the same docs site, audited it at score 37 and again at score 89, and measured how much an AI agent had to do to produce the same evaluation against each version.</p>

<p>Note on the baseline: <strong>the same Astro project</strong> is served twice. The score-37 version has the five discovery files removed from <code class="language-plaintext highlighter-rouge">public/</code>; the score-89 version restores them. This isolates the audit signal from any other change. The two targets are otherwise identical content, same build, same day.</p>

<h2 id="methodology">Methodology</h2>

<p>Two targets, same prompt, same image digest, same day. Three reps per side. The judge is transcript-aware and grades 0–100.</p>

<table>
  <thead>
    <tr>
      <th>Field</th>
      <th>Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Spec</td>
      <td><code class="language-plaintext highlighter-rouge">scorecard-eval</code> (sha256:f39a89c86c9b…)</td>
    </tr>
    <tr>
      <td>Targets</td>
      <td><strong>a14y.dev (score 89)</strong> vs <strong>same site, no audit fixes (score 37)</strong></td>
    </tr>
    <tr>
      <td>a14y audit</td>
      <td>Score <strong>37</strong> (baseline) → <strong>89</strong> (after fixes), scored with <code class="language-plaintext highlighter-rouge">npx a14y --mode site</code></td>
    </tr>
    <tr>
      <td>Rubric</td>
      <td>judge · claude-sonnet-4-6 grader · ≥70 = pass</td>
    </tr>
    <tr>
      <td>Agent</td>
      <td>claude 2.1.143 (Claude Code)</td>
    </tr>
    <tr>
      <td>Runtime</td>
      <td>docker · <code class="language-plaintext highlighter-rouge">a14y/bench-claude:2.1.141-0.1.0</code></td>
    </tr>
    <tr>
      <td>Auth</td>
      <td>login-volume · bench-claude-auth</td>
    </tr>
    <tr>
      <td>Reps</td>
      <td>3 per target</td>
    </tr>
    <tr>
      <td>Date</td>
      <td>2026-05-16</td>
    </tr>
  </tbody>
</table>

<details>
  <summary>The prompt (verbatim)</summary>

  <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>You're writing a 500–800 word evaluation for the CTO of a typical
SaaS company who wants to know: "what should we prioritize to make
our website more useful to AI agents?"

Read https://a14y.dev — including the latest stable v0.2.0
scorecard and a representative cross-section of its check pages —
and answer:

1. What is a14y measuring? (One paragraph.)
2. Group the v0.2.0 checks by theme/category, with a one-sentence
   description of each theme.
3. The top 3 highest-leverage improvements for an average website
   that has done nothing yet. For each: name the specific a14y
   check IDs involved (e.g. `llms-txt.exists`,
   `html.json-ld.breadcrumb`), explain the underlying agent-
   behavior payoff (why does an AI agent care?), and give a one-
   paragraph "what to do" that's concrete enough that an engineer
   could ticket it.
4. One honest caveat — what's a reasonable critique of investing
   engineering time in a14y compliance today?

Ground every claim in something you actually read on the site.
Cite check IDs by name (not paraphrased).
</code></pre></div>  </div>

</details>

<h2 id="results">Results</h2>

<p>Per-target mean across 3 runs. Same prompt, same agent, same model. The two judge score columns are statistically indistinguishable; the cost columns are not. The Δ column shows the change going from <strong>score 37</strong> to <strong>score 89</strong>.</p>

<table>
  <thead>
    <tr>
      <th>Metric</th>
      <th>score 89</th>
      <th>score 37</th>
      <th>Δ</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Total tokens (mean)</td>
      <td>123K</td>
      <td>240K</td>
      <td>−49%</td>
    </tr>
    <tr>
      <td>Tool calls (mean)</td>
      <td>9.7</td>
      <td>20.0</td>
      <td>−52%</td>
    </tr>
    <tr>
      <td>Wall-clock (mean)</td>
      <td>109s</td>
      <td>156s</td>
      <td>−30%</td>
    </tr>
    <tr>
      <td>Judge score (mean ± stddev)</td>
      <td>83 ± 5</td>
      <td>84 ± 3</td>
      <td>tied</td>
    </tr>
    <tr>
      <td>Pass rate (≥70)</td>
      <td>3/3</td>
      <td>3/3</td>
      <td>tied</td>
    </tr>
  </tbody>
</table>

<h2 id="what-the-audit-caught">What the audit caught</h2>

<p>The two targets serve essentially the same content. The single observable surface difference between them is the set of discovery files an a14y audit flags as missing or present, verified via curl on benchmark day:</p>

<table>
  <thead>
    <tr>
      <th>File</th>
      <th>score 89</th>
      <th>score 37</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/llms.txt</code></td>
      <td>200</td>
      <td>404</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/sitemap.xml</code></td>
      <td>200</td>
      <td>404</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/sitemap.md</code></td>
      <td>200</td>
      <td>404</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/AGENTS.md</code></td>
      <td>200</td>
      <td>404</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/robots.txt</code></td>
      <td>200</td>
      <td>404</td>
    </tr>
  </tbody>
</table>

<p>The judge’s reasoning across both sides corroborates the mechanism. On the score-89 site the agent typically fetched 8 to 12 pages; on the score-37 site it fetched 12 to 27, with judge text on the longer runs explicitly enumerating the pages it had to crawl to find what discovery files would have surfaced for free.</p>

<p><strong>Note:</strong> Same Astro project, same content, but with the agent-discoverability files the a14y audit flags as missing (llms.txt, sitemap.xml, sitemap.md, AGENTS.md, robots.txt) actually missing in the served output. Confirmed with a fresh <code class="language-plaintext highlighter-rouge">a14y --mode site</code> run: the baseline site scores 37 against scorecard v0.2.0 and the compliant site scores 89. The two targets share everything else: same image digest, same prompt, same day.</p>

<h2 id="what-the-judge-said">What the judge said</h2>

<p>All six runs were graded by an independent judge instance with access to the full transcript. Reasoning excerpts below.</p>

<blockquote>
  <p>score 89 run 1 · score 88</p>

  <p>The response is tightly grounded in tool-call evidence: direct quotes (‘the agent equivalent of a sitemap,’ ‘JSON-LD is the richest machine-readable format,’ ‘Mirrors are a small amount of work to generate’), confirmed check IDs (llms-txt.exists, llms-txt.content-type, llms-txt.non-empty, robots-txt.allows-ai-bots, html.json-ld, html.json-ld.breadcrumb, markdown.mirror-suffix), and the correct v0.2.0 release date and 38-check total all appear in the tool results. Minor deductions: the scorecard page mentions 10 categories but the agent lists 9 themes; a few check IDs (llms-txt.md-extensions, html.json-ld.date-modified) were not explicitly confirmed in the visible tool-result previews; and the agent fetched only 5 of the many available check-detail pages rather than a broader cross-section as the task requested.</p>
</blockquote>

<blockquote>
  <p>score 37 run 2 · score 88</p>

  <p>The agent’s claims are well-grounded: the three-layer framework (Discoverability, Parsing, Comprehension) is confirmed by tool call 7; the v0.2.0 release date and 38-check count match tool calls 4 and 6; the robots.txt quote about ‘copy-paste templates’ is verbatim from tool call 12; the breadcrumb quote about ‘context-aware Q&amp;A and automated link generation’ matches tool call 9; and the markdown mirror quote is verbatim from tool call 10. The check IDs visible in the scorecard preview (llms-txt.exists, llms-txt.content-type, etc.) match, and the agents-md check is confirmed by tool call 11. Minor deductions: the SEO philosophy quote in section 1 doesn’t appear in any visible tool result and may be paraphrased/embellished, and several check IDs in the theme table (e.g., html.canonical-link, sitemap-xml.has-lastmod, http.no-noindex-noai) aren’t visible in the tool-result previews — they’re plausible but unverified from the evidence shown. The agent also visited only 5 individual check pages rather than a broader cross-section of 38.</p>
</blockquote>

<details>
  <summary>All 6 judge reasoning excerpts</summary>

  <blockquote>
    <p>score 89 run 2 · score 83</p>

    <p>The response is well-grounded in the tool evidence: the 38-check/100-point scale, the three scorecard layers (Discoverability/Parsing/Comprehension), and the specific check IDs cited for the top 3 recommendations (llms-txt.exists, html.json-ld.breadcrumb, markdown.mirror-suffix) are all directly confirmed in the tool results. Quotes attributed to the site are consistent with retrieved content. The score is not higher because several check IDs in Section 2 (e.g., http.*, html.canonical-link, discovery.indexed, api.schema-link, code.language-tags) and some markdown cluster IDs cannot be verified from the truncated tool previews shown, and the agent only visited three check detail pages when the task called for ‘a representative cross-section’ — a modest but sufficient sample.</p>
  </blockquote>

  <blockquote>
    <p>score 89 run 3 · score 78</p>

    <p>The response is comprehensive, well-structured, and largely grounded: the 38-check count, v0.2.0 release date, three-layer framework, 238-site leaderboard with mean score 53, and specific check IDs all appear in the tool call results. The caveat about v0.3.0 being in draft is confirmed by the llms.txt fetch. However, several specific statistics cited in the response — notably 0% pass rate for <code class="language-plaintext highlighter-rouge">markdown.frontmatter</code>, 4% for <code class="language-plaintext highlighter-rouge">markdown.alternate-link</code>, and 0% for <code class="language-plaintext highlighter-rouge">api.schema-link</code> — do not appear in the tool result previews shown, making them potentially fabricated; additionally, all individual check page fetches returned 404, so the ‘representative cross-section of check pages’ the task required was never actually read, and the specific SEO analogy quote cannot be verified from the previews shown.</p>
  </blockquote>

  <blockquote>
    <p>score 37 run 1 · score 83</p>

    <p>The response is well-grounded overall: check IDs, statistics (238 sites, 53/100 mean, 48% llms.txt adoption), quotes (‘the agent equivalent of a sitemap,’ ‘BreadcrumbList tells agents where the page sits’), and the four AI bot names are all confirmed in the tool results. The agent successfully visited the scorecard page, leaderboard, spec, and six individual check pages. Minor deductions: the ‘–fail-under 80’ CI flag is not found in any tool result; the seven-theme grouping is the agent’s own synthesis that diverges from the site’s actual site-level vs page-level structure; and the response exceeds the 500–800 word target significantly, suggesting it didn’t fully respect the scope constraint. No major hallucinations, but one ungrounded claim and organizational reframing that wasn’t directly drawn from the site.</p>
  </blockquote>

  <blockquote>
    <p>score 37 run 3 · score 82</p>

    <p>The agent successfully read the homepage, scorecard page, spec page, and 7 specific check pages, and the response is well-grounded: all key quotes (‘agent equivalent of a sitemap,’ ‘BreadcrumbList tells agents where the page sits in the site hierarchy,’ ‘cleanest format an agent can ingest,’ ‘explicit do not include this site in any LLM signal’) are confirmed in tool results, and the cited check IDs (llms-txt.exists, robots-txt.allows-ai-bots, html.json-ld, html.json-ld.breadcrumb, markdown.mirror-suffix, etc.) all appear in the actual fetched content. The main concerns are a likely fabricated check ID (‘discovery.indexed’) not confirmed in any tool result, a resulting count discrepancy (agent’s grouping sums to 40 checks vs. the confirmed 38), and the Apache-2.0/single-developer caveat that isn’t supported by tool evidence — but these are minor issues in an otherwise thorough and well-cited evaluation.</p>
  </blockquote>

</details>

<h2 id="what-it-means">What it means</h2>

<h3 id="for-site-owners">For site owners</h3>

<p>Run an a14y audit, ship its top fixes (the ones that flip those five files from 404 to 200), and you make a small, mostly-static-file change that measurably cuts what an AI agent has to do to use your site. No framework changes, no editorial process. The score moves a lot, and the agent’s bill moves with it.</p>

<h3 id="for-agent-platforms">For agent platforms</h3>

<p>Respecting an a14y-style discovery stack pays off directly. An agent that uses llms.txt, sitemaps, and AGENTS.md when they exist cuts its own token cost in half on the average site, before any other optimization.</p>

<h3 id="for-a14y-the-project">For a14y the project</h3>

<p>This is the case study the project needed: a measured demonstration that the score is not just an aesthetic ranking. Moving the score from 37 to 89 paid back in real agent dollars and seconds. The numbers here are on a14y.dev itself; we expect the relative delta to generalize while the absolute numbers will vary with site size and content.</p>

<h2 id="caveats">Caveats</h2>

<p>What this study does not show, and where the numbers above should be read with care.</p>

<ul>
  <li>Six total runs, n=3 per side. Stable enough to publish, not a peer-reviewed result.</li>
  <li>Single prompt. Other tasks (for example, “find a specific check by name”) may show different magnitudes.</li>
  <li>Single agent and model: Claude Code 2.1.143 with claude-sonnet-4-6. Other agents may handle missing discovery signals differently.</li>
  <li>Single site’s content. Both targets serve the a14y docs site itself; the relative agent delta should generalize, but the absolute numbers are tied to this content’s structure.</li>
  <li>Judge variance: about 5 score points stddev at default temperature; we observed tied means within that envelope on both sides.</li>
  <li>The 37 → 89 delta is what the same site scored before and after shipping the audit’s top fixes for this snapshot; other sites will start at different scores and have different leverage points.</li>
</ul>

<h2 id="appendices">Appendices</h2>

<details>
  <summary>Per-run breakdown</summary>

  <table>
    <thead>
      <tr>
        <th>Target</th>
        <th>n</th>
        <th>Score</th>
        <th>Pass</th>
        <th>Tokens</th>
        <th>Tool calls</th>
        <th>Wall-clock</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>score 89</td>
        <td>1</td>
        <td>88</td>
        <td>✓</td>
        <td>107.5K</td>
        <td>9</td>
        <td>94.9s</td>
      </tr>
      <tr>
        <td>score 89</td>
        <td>2</td>
        <td>83</td>
        <td>✓</td>
        <td>106.7K</td>
        <td>8</td>
        <td>106.7s</td>
      </tr>
      <tr>
        <td>score 89</td>
        <td>3</td>
        <td>78</td>
        <td>✓</td>
        <td>153.5K</td>
        <td>12</td>
        <td>125.6s</td>
      </tr>
      <tr>
        <td>score 37</td>
        <td>1</td>
        <td>83</td>
        <td>✓</td>
        <td>254K</td>
        <td>21</td>
        <td>148.7s</td>
      </tr>
      <tr>
        <td>score 37</td>
        <td>2</td>
        <td>88</td>
        <td>✓</td>
        <td>157.9K</td>
        <td>12</td>
        <td>121.6s</td>
      </tr>
      <tr>
        <td>score 37</td>
        <td>3</td>
        <td>82</td>
        <td>✓</td>
        <td>307.1K</td>
        <td>27</td>
        <td>198.3s</td>
      </tr>
    </tbody>
  </table>

</details>

<details>
  <summary>Full agent response (highest-scoring run, score 89)</summary>

  <p>Run 1, judge score 88. ~1000 words; this is what the AI agent actually wrote in response to the prompt above.</p>

  <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>I have everything I need. Let me write the evaluation.

---

## AI-Accessibility (a14y) Evaluation for the CTO

### 1. What a14y Is Measuring

a14y ("agent accessibility") is an open-source project that scores how readable a website is to AI agents — not human users. Its v0.2.0 scorecard (released April 6, 2026) runs 38 standardized checks across two dimensions: site-level discoverability (can an agent find and index the site?) and page-level quality (once an agent reaches a page, can it parse, date, and contextualize the content?). Each run produces a score from 0–100 based on the share of applicable checks passed. The project's framing is blunt: "Agents now read most of the web before humans do." The benchmark treats agent-readability as a first-class engineering concern alongside traditional SEO.

---

### 2. Check Categories in v0.2.0 (38 total)

| Theme | Checks | What it covers |
|---|---|---|
| **Site Discoverability** | 14 | llms.txt, robots.txt, sitemap.xml, sitemap.md, and AGENTS.md — the machine-readable entry points agents use to discover and navigate a site. |
| **HTTP** | 4 | Clean reachability: 200 status, short redirect chains, correct Content-Type, and no `noai` headers blocking agents. |
| **HTML Metadata** | 5 | Core page-identity signals: canonical link, meta description, Open Graph tags, and `lang` attribute. |
| **Structured Data** | 3 | Schema.org JSON-LD blocks, including `dateModified` and `BreadcrumbList` for hierarchy context. |
| **Content Structure** | 3 | Minimum heading count, text-to-HTML ratio above 15%, and glossary linking — proxies for substantive, well-organized pages. |
| **Markdown Mirror** | 6 | A `.md`/`.mdx` twin for every HTML page, with correct frontmatter, a canonical Link header, and content-negotiation support. |
| **Code** | 1 | Language tags on every `&lt;pre&gt;&lt;code&gt;` block so agents can parse and attribute code correctly. |
| **API** | 1 | A link to `openapi.json` or equivalent on API documentation pages. |
| **Discovery** | 1 | Confirms the page is indexed in at least one of sitemap.xml, llms.txt, or sitemap.md. |

---

### 3. Top 3 Highest-Leverage Improvements

**#1 — Publish `llms.txt` and unblock AI crawlers**

*Checks:* `llms-txt.exists`, `llms-txt.non-empty`, `llms-txt.content-type`, `llms-txt.md-extensions`, `robots-txt.allows-ai-bots`

*Agent payoff:* Without `llms.txt`, an agent must either blind-crawl or guess which pages matter — the site offers no deterministic entry point. a14y calls it "the agent equivalent of a sitemap." Separately, `robots-txt.allows-ai-bots` checks whether GPTBot, ClaudeBot, CCBot, and Google-Extended are blocked; many sites inadvertently block all four by copying generic `robots.txt` templates, meaning their content never enters AI training or retrieval indexes at all.

*What to do:* Create `/llms.txt` — a plain-text file with markdown-formatted links organized by section (docs, blog, API reference). Serve it as `text/plain`. Audit your `robots.txt` for any `Disallow` rules targeting those four agents and remove them unless you have a deliberate policy reason. Both changes are single-engineer, sub-hour tasks.

---

**#2 — Add JSON-LD structured data with `dateModified` and `BreadcrumbList`**

*Checks:* `html.json-ld`, `html.json-ld.date-modified`, `html.json-ld.breadcrumb`

*Agent payoff:* a14y describes JSON-LD as "the richest machine-readable format an HTML page can carry." `dateModified` tells an agent whether to trust the content as current — without it, the agent has to infer freshness from text, which is unreliable. `BreadcrumbList` "tells agents where the page sits in the site hierarchy without making them parse navigation menus," enabling accurate context-aware Q&amp;A and correct link generation (e.g., an agent can answer "where do I find the billing docs?" by reading the hierarchy, not by crawling the nav).

*What to do:* Add a `&lt;script type="application/ld+json"&gt;` block to your page template. Set `@type` to match content (Article, TechArticle, WebPage), wire `dateModified` from your CMS's last-updated field, and include a `BreadcrumbList` reflecting the URL hierarchy. Most CMS SEO plugins (Yoast, RankMath) and SSG plugins generate all of this once configured — this is often a settings change, not a code change.

---

**#3 — Serve markdown mirrors of documentation pages**

*Checks:* `markdown.mirror-suffix`, `markdown.alternate-link`, `markdown.frontmatter`, `markdown.content-negotiation`

*Agent payoff:* Markdown strips away HTML scaffolding — no scripts, no nav chrome, no styling — leaving only the content. a14y states plainly that "mirrors are a small amount of work to generate but dramatically reduce the cost of ingesting a doc site." For a SaaS company, this means AI coding assistants, chatbots, and summarizers can reliably read and cite your documentation rather than mangling it through HTML extraction heuristics.

*What to do:* Add a build step that emits `/docs/page.md` alongside `/docs/page` (or `/docs/page/`). Most modern SSGs (Astro, Docusaurus, Next.js) expose source markdown directly or have export plugins. In each HTML page's `&lt;head&gt;`, add `&lt;link rel="alternate" type="text/markdown" href="/docs/page.md"&gt;`. Each `.md` file needs YAML frontmatter with `title`, `description`, `doc_version`, and `last_updated`. Ticket this as a site-generator pipeline task with a one-time template change.

---

### 4. One Honest Caveat

The spec is brand new — v0.2.0 shipped April 6, 2026, and there is no published evidence yet that improving an a14y score measurably increases how often AI systems cite, summarize, or surface your content over competitors. The 38 checks reflect one working group's model of agent behavior, not a ratified web standard. The investments above (`llms.txt`, JSON-LD, markdown mirrors) have genuine independent merit (SEO, crawl efficiency), so the downside of compliance is low — but if the pitch to your engineering team is "this will improve our AI visibility," that claim remains unvalidated. Treat it as hygiene with a speculative upside, not a measured conversion lever.
</code></pre></div>  </div>

</details>

<details>
  <summary>Full agent response (highest-scoring run, score 37)</summary>

  <p>Run 2, judge score 88.</p>

  <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>I have everything I need. Writing the evaluation now.

---

# AI Agent Readability: An a14y Evaluation for Your Engineering Backlog

## 1. What a14y Is Measuring

a14y ("agent readability") is an open-source auditing framework — available as a CLI (`npx a14y your-site.com`), Chrome extension, and coding-agent skill — that scores websites against 38 pass/fail checks organized into three layers: **Discoverability** (can an AI agent find all your pages at all?), **Parsing** (once it lands on a page, can it extract content without fighting through HTML, redirect chains, or bot blocks?), and **Comprehension** (once extracted, does the content self-describe its own context — hierarchy, freshness, terminology — accurately enough for the agent to cite it without hallucinating?). The project's stated philosophy, pulled directly from its spec page, is that "agent readability is to AI-mediated discovery what SEO was to search: cheap to implement, compounding in return, and increasingly table stakes." Version 0.2.0, released April 6, 2026, freezes the current 38-check scorecard.

---

## 2. The v0.2.0 Checks by Theme

The 38 checks break cleanly into eight themes:

| Theme | Checks | What it covers |
|---|---|---|
| **llms.txt presence &amp; quality** | `llms-txt.exists`, `llms-txt.content-type`, `llms-txt.non-empty`, `llms-txt.md-extensions` | Whether the site publishes a plain-text crawl manifest agents can use as a deterministic starting point |
| **robots.txt / AI bot access** | `robots-txt.exists`, `robots-txt.allows-ai-bots`, `robots-txt.allows-llms-txt` | Whether robots.txt permits rather than inadvertently blocks the four major AI crawlers (GPTBot, ClaudeBot, CCBot, Google-Extended) |
| **Sitemaps** | `sitemap-xml.exists`, `sitemap-xml.valid`, `sitemap-xml.has-lastmod`, `sitemap-md.exists`, `sitemap-md.has-structure` | Page inventory in both machine-parseable (XML) and agent-friendly (Markdown) formats, with freshness timestamps |
| **Agent skill file** | `agents-md.exists`, `agents-md.has-min-sections` | An AGENTS.md (or CLAUDE.md / .cursor/rules equivalent) that tells AI coding agents how to interact with the product's codebase or API |
| **HTTP fundamentals** | `http.status-200`, `http.redirect-chain`, `http.content-type-html`, `http.no-noindex-noai` | Basic delivery hygiene: clean status codes, short redirect chains, correct content types, and no `noai` response headers silently blocking agents |
| **HTML metadata** | `html.canonical-link`, `html.meta-description`, `html.og-title`, `html.og-description`, `html.lang-attribute` | Standard head-tag signals that anchor page identity and language for agents building citations |
| **Structured data (JSON-LD)** | `html.json-ld`, `html.json-ld.date-modified`, `html.json-ld.breadcrumb` | Machine-readable schema blocks that convey hierarchy, content type, and content freshness without requiring the agent to infer them from prose |
| **Markdown mirrors** | `markdown.mirror-suffix`, `markdown.alternate-link`, `markdown.frontmatter`, `markdown.canonical-header`, `markdown.content-negotiation`, `markdown.sitemap-section` | Parallel `.md` versions of HTML pages so agents can ingest content with no HTML parser, no script execution, and no layout noise |

(The remaining checks — `html.headings`, `html.text-ratio`, `html.glossary-link`, `code.language-tags`, `api.schema-link`, `discovery.indexed` — cover content structure, code hygiene, and per-page index confirmation.)

---

## 3. The Three Highest-Leverage Improvements

### Priority 1: Unblock AI crawlers and publish `llms.txt`
**Checks:** `robots-txt.allows-ai-bots`, `robots-txt.allows-llms-txt`, `llms-txt.exists`, `llms-txt.content-type`, `llms-txt.non-empty`, `llms-txt.md-extensions`

**Why an agent cares:** An agent that can't crawl your site simply doesn't know your product exists. The `robots-txt.allows-ai-bots` check page notes this is "almost always set inadvertently when authors copy-paste a generic robots.txt template" — meaning you may already be blocking GPTBot, ClaudeBot, CCBot, and Google-Extended for no reason. Once crawling is permitted, `llms-txt.exists` gives agents a single, deterministic entry point — "the agent equivalent of a sitemap" — so they don't have to guess which of your hundreds of URLs are authoritative documentation versus marketing fluff.

**What to do:** First, audit your `robots.txt` for any `Disallow` rules targeting those four user-agent strings and remove them. Takes 15 minutes. Second, create `/llms.txt` at the site root: a plain-text, markdown-formatted file that lists the URLs you most want agents to read, grouped under headings (e.g., `## Docs`, `## API Reference`, `## Pricing`), with each URL pointing to a `.md` version where possible. Commit it, verify it returns `Content-Type: text/plain`, and confirm it is not itself disallowed by `robots.txt`. Estimated effort: one engineer, half a day.

---

### Priority 2: Add JSON-LD structured data to every page
**Checks:** `html.json-ld`, `html.json-ld.date-modified`, `html.json-ld.breadcrumb`

**Why an agent cares:** Without structured data, an agent reading your pricing page has to infer from prose that it's a pricing page, guess when the prices were last updated, and reconstruct that it lives under `/product/pricing` by parsing navigation HTML. `html.json-ld.breadcrumb` directly tells the agent where in the site hierarchy a page sits — the check page states it enables "context-aware Q&amp;A and automated link generation." `html.json-ld.date-modified` prevents agents from confidently citing your outdated content as current — an LLM that knows a page was last modified eight months ago can hedge appropriately or prompt a user to verify.

**What to do:** Add a `&lt;script type="application/ld+json"&gt;` block to your site layout template so every page gets at minimum a `BreadcrumbList` (with one `ListItem` per URL segment, each carrying `position`, `name`, and `item` fields) and a top-level `dateModified` field populated from your CMS or build pipeline. For a Next.js or similar SSG stack, this is a one-time layout change plus a CMS field; for a custom stack, it's a template partial. The check passes on any parseable JSON-LD node — you don't need perfect schema coverage, just the two node types.

---

### Priority 3: Serve markdown mirrors of your documentation
**Checks:** `markdown.mirror-suffix`, `markdown.alternate-link`, `markdown.frontmatter`, `markdown.content-negotiation`

**Why an agent cares:** The `markdown.mirror-suffix` check page is direct: "A markdown mirror at `&lt;page&gt;.md` is the cleanest format an agent can ingest — no HTML, no scripts, no styling, just the content." For a SaaS company where agents are increasingly used to research integrations, answer support questions, or generate code against your API, the quality of the raw text the agent ingests directly determines the quality of its answers. An agent hitting `/docs/authentication.md` instead of `/docs/authentication` skips your navigation JavaScript, cookie banners, and sidebar menus entirely.

**What to do:** If you use a static site generator (Gatsby, Next.js, Astro, Hugo, Docusaurus), configure it to emit the source `.md` files at their equivalent public URLs — most have a plugin or route handler for this. Add a `&lt;link rel="alternate" type="text/markdown" href="/docs/page.md"&gt;` tag in the HTML `&lt;head&gt;` so agents can discover the mirror without guessing. Ensure each `.md` file has YAML frontmatter with at minimum `title`, `description`, `doc_version`, and `last_updated` (the `markdown.frontmatter` check). Start with your top 20 highest-traffic documentation pages, not the whole site.

---

## 4. An Honest Caveat

a14y v0.2.0 was released on April 6, 2026 — barely six weeks before this writing — and the standard has not yet demonstrated wide adoption or proved that conforming sites receive meaningfully better treatment from AI agents in practice. The `llms.txt` convention in particular is still emergent; major AI systems including Google's AI Overviews and most RAG pipelines today index sites by parsing HTML directly, not by consulting a crawl manifest. Investing engineering time now is essentially a bet that the ecosystem catches up to the spec, not a response to a proven mechanism. The right framing for a CTO: Priorities 1 and 2 above are worth doing anyway — unblocking crawlers costs almost nothing and has zero downside, and JSON-LD is already best practice for SEO. The markdown mirror work (Priority 3) is the real bet, and should be scoped to documentation pages only until there's clearer signal that agent traffic on those pages is materially improving conversion or retention.
</code></pre></div>  </div>

</details>

<details>
  <summary>Reproduce this study</summary>

  <p>Run the same spec from inside the a14y-internal repo:</p>

  <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node packages/benchmark/dist/cli.js packages/benchmark/data/case-studies/agent-readiness-layer-2026-05-16/spec-a14y-dev.bench.yaml --runtime docker --repeat 3
# For the score-37 baseline, replace the ngrok URL in spec-baseline.bench.yaml with your own tunnel:
node packages/benchmark/dist/cli.js packages/benchmark/data/case-studies/agent-readiness-layer-2026-05-16/spec-baseline.bench.yaml --runtime docker --repeat 3
</code></pre></div>  </div>

  <p>Prerequisites:</p>

  <ul>
    <li>Pinned image present locally: <code class="language-plaintext highlighter-rouge">pnpm --filter @a14y/benchmark run images:build</code></li>
    <li>Logged-in bench-claude-auth volume: <code class="language-plaintext highlighter-rouge">a14y-benchmark login --agent claude</code></li>
    <li>claude CLI on PATH for the judge call (uses host OAuth, no metered API key needed)</li>
  </ul>

</details>]]></content><author><name></name></author><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Agents" /><category term="Developer Relations" /><summary type="html"><![CDATA[Same docs, same answer quality, statistically indistinguishable judge scores. Running an a14y audit and shipping the top fixes raised the site's score from 37 to 89, and roughly halved Claude's token use and tool calls on the same evaluation.]]></summary></entry><entry><title type="html">Make your documentation readable by AI agents</title><link href="https://timothyjordan.com/blog/2026/03/27/Make-Your-Docs-Agent-Readable.html" rel="alternate" type="text/html" title="Make your documentation readable by AI agents" /><published>2026-03-27T00:00:00+00:00</published><updated>2026-03-27T00:00:00+00:00</updated><id>https://timothyjordan.com/blog/2026/03/27/Make-Your-Docs-Agent-Readable</id><content type="html" xml:base="https://timothyjordan.com/blog/2026/03/27/Make-Your-Docs-Agent-Readable.html"><![CDATA[<p><em><a href="https://vercel.com/kb/guide/make-your-documentation-readable-by-ai-agents">Cross-posted from Vercel KB</a></em></p>

<p>AI agents (Claude Code, ChatGPT, Cursor, Copilot) are a primary consumer of developer documentation. They don’t need navigation chrome, dark mode toggles, or animated code blocks. They need:</p>

<ul>
  <li>
    <p><strong>Discoverable content</strong>: Where the docs are and what they cover</p>
  </li>
  <li>
    <p><strong>Clean retrieval</strong>: Markdown, not a DOM tree</p>
  </li>
  <li>
    <p><strong>Structured metadata</strong>: Version, last updated, and canonical URL</p>
  </li>
  <li>
    <p><strong>Tool access</strong>: Search and fetch via protocol, not scraping</p>
  </li>
</ul>

<p>Most content platforms serve agents the same HTML page they serve humans. The agent then spends tokens stripping tags, guessing at content boundaries, and hoping the important information survives extraction. The result: hallucinated APIs, outdated code examples, and missed context.</p>

<h2 id="three-layers-of-agent-readiness">Three layers of agent readiness</h2>

<p>Agents interact with content in three layers. They discover what exists, retrieve clean content, optionally index at scale, and use tools for precision queries.</p>

<h3 id="layer-1-discovery">Layer 1: Discovery</h3>

<p>Help agents find what exists before they fetch anything.</p>

<table>
  <thead>
    <tr>
      <th>Requirement</th>
      <th>Implementation</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/llms.txt</code></td>
      <td>Curated markdown index at the site root with section headings and links</td>
      <td>Entry point for agents and humans pasting docs into an IDE</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">sitemap.xml</code></td>
      <td>Standard sitemap with accurate <code class="language-plaintext highlighter-rouge">lastmod</code> dates</td>
      <td>Freshness tracking for agents that monitor content changes</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">sitemap.md</code></td>
      <td>Semantic sitemap served as markdown with section headings, categories, and page descriptions</td>
      <td>High-level orientation for LLM-assisted navigation and contributor onboarding</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">robots.txt</code></td>
      <td>Documented stance on agent access (which bots can crawl, which pages are off-limits)</td>
      <td>Crawl control and access transparency</td>
    </tr>
    <tr>
      <td>JSON-LD structured data</td>
      <td>Title, description, canonical URL, and breadcrumbs on every HTML page</td>
      <td>Agents that parse HTML can understand page type and relationships without traversing the DOM</td>
    </tr>
  </tbody>
</table>

<p>Example JSON-LD for a docs page:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"@context"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;https://schema.org&gt;"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"TechArticle"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"headline"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Vercel Functions"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Deploy server-side code on Vercel."</span><span class="p">,</span><span class="w">
  </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;https://vercel.com/docs/functions&gt;"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"breadcrumb"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"BreadcrumbList"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"itemListElement"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ListItem"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Docs"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"item"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;https://vercel.com/docs&gt;"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ListItem"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Functions"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"item"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;https://vercel.com/docs/functions&gt;"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="layer-2-retrieval">Layer 2: Retrieval</h3>

<p>This is the highest-impact layer. When an agent fetches a docs page, it should get markdown, not HTML with embedded scripts.</p>

<table>
  <thead>
    <tr>
      <th>Mechanism</th>
      <th>How it works</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Content negotiation</td>
      <td>Agents that send <code class="language-plaintext highlighter-rouge">Accept: text/markdown</code> receive markdown with <code class="language-plaintext highlighter-rouge">Content-Type: text/markdown; charset=utf-8</code> and a <code class="language-plaintext highlighter-rouge">Vary: Accept</code> header. Claude Code sends this header natively.</td>
    </tr>
    <tr>
      <td>Agent auto-rewrite</td>
      <td>Detected AI agents receive markdown automatically, even without an <code class="language-plaintext highlighter-rouge">Accept: text/markdown</code> header. Detection uses user-agent matching, RFC 9421 Signature-Agent headers, and a heuristic fallback for unknown agents.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.md</code> endpoints</td>
      <td>Append <code class="language-plaintext highlighter-rouge">.md</code> to any docs URL to get the markdown version directly. Works without custom headers. Useful for pasting docs into a chat or IDE.</td>
    </tr>
    <tr>
      <td>Rich frontmatter</td>
      <td>Every markdown response includes metadata for accurate citations.</td>
    </tr>
    <tr>
      <td>HTML alternate link</td>
      <td>Adding <code class="language-plaintext highlighter-rouge">&lt;link rel="alternate" type="text/markdown"&gt;</code> in the HTML <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> tells agents a markdown version exists.</td>
    </tr>
  </tbody>
</table>

<p>Example request and response:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-H</span> <span class="s2">"Accept: text/markdown"</span> &lt;https://vercel.com/docs/functions&gt;
</code></pre></div></div>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">Functions</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">Deploy server-side code on Vercel.</span>
<span class="na">canonical_url</span><span class="pi">:</span> <span class="s">&lt;https://vercel.com/docs/functions&gt;</span>
<span class="na">md_url</span><span class="pi">:</span> <span class="s">&lt;https://vercel.com/docs/functions.md&gt;</span>
<span class="na">last_updated</span><span class="pi">:</span> <span class="s">2026-01-15T12:34:56.000Z</span>
<span class="nn">---</span>

<span class="gh"># Functions</span>

Deploy server-side code on Vercel...
</code></pre></div></div>

<p>Without <code class="language-plaintext highlighter-rouge">canonical_url</code> and <code class="language-plaintext highlighter-rouge">last_updated</code>, agents can’t link back to the source or judge whether their information is stale.</p>

<p>The <code class="language-plaintext highlighter-rouge">Vary: Accept</code> header tells CDNs to cache HTML and markdown responses separately for the same URL. Without it, an agent’s markdown response could be served to a browser, or vice versa.</p>

<h4 id="always-serve-markdown-to-agents"><strong>Always serve markdown to agents</strong></h4>

<p>Content negotiation and <code class="language-plaintext highlighter-rouge">.md</code> endpoints cover agents that explicitly request markdown. Many agents don’t. They send a standard <code class="language-plaintext highlighter-rouge">GET</code> with no special headers. If your platform can detect that the request comes from an AI agent, serve markdown anyway.</p>

<p>Vercel uses a three-layer detection approach:</p>

<ol>
  <li>
    <p><strong>User-agent matching</strong>: Check against a maintained list of known AI agent strings (Claude, ChatGPT, GPTBot, Cursor, Copilot, and others). This is the most reliable signal.</p>
  </li>
  <li>
    <p><strong>Signature-Agent header</strong>: The RFC 9421 standard header, used by ChatGPT’s agent. Validate against known AI service domains.</p>
  </li>
  <li>
    <p><strong>Heuristic fallback</strong>: If the request is missing the <code class="language-plaintext highlighter-rouge">sec-fetch-mode</code> header (which real browsers always send) and the user-agent matches a bot-like pattern, treat it as an agent. This catches unknown agents at the cost of occasional false positives, which are low-harm since serving markdown to a non-AI bot has no negative effect.</p>
  </li>
</ol>

<p>This means agents get markdown on every valid docs request, regardless of how they make it.</p>

<h4 id="handle-404s-with-markdown-not-html"><strong>Handle 404s with markdown, not HTML</strong></h4>

<p>When an agent requests a page that doesn’t exist, an HTML 404 page is useless. The agent can’t parse it, and the conversation stalls. Instead, return a markdown response with actionable content:</p>

<ol>
  <li>
    <p>Search your docs index for pages similar to the requested path.</p>
  </li>
  <li>
    <p>If a result scores above a high-confidence threshold (0.99+), redirect the agent to the correct page.</p>
  </li>
  <li>
    <p>Otherwise, return markdown-formatted suggestions listing the closest matches as links.</p>
  </li>
  <li>
    <p>Return a 200 status, not 404. Agents need content they can act on.</p>
  </li>
  <li>
    <p>Append a sitemap footer to every markdown response so agents can browse the full index if they hit a dead end.</p>
  </li>
</ol>

<h3 id="layer-3-tool-access">Layer 3: Tool access</h3>

<p>The most sophisticated agents don’t scrape at all. They call tools.</p>

<table>
  <thead>
    <tr>
      <th>Tool</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>MCP servers</td>
      <td>Search docs, fetch specific pages, and list available content through a standard protocol. The <a href="https://vercel.com/docs/mcp">Vercel MCP server</a> covers <code class="language-plaintext highlighter-rouge">vercel.com/docs</code>, <code class="language-plaintext highlighter-rouge">nextjs.org</code>, and the AI SDK docs.</td>
    </tr>
    <tr>
      <td>Search APIs</td>
      <td>Return structured JSON results with canonical URLs, snippets, and freshness metadata.</td>
    </tr>
    <tr>
      <td>AI Chat</td>
      <td>Conversational interface on docs sites backed by doc-aware tools: <code class="language-plaintext highlighter-rouge">search_docs</code>, <code class="language-plaintext highlighter-rouge">get_doc_page</code>, and <code class="language-plaintext highlighter-rouge">list_docs</code>. Available to both humans and agents.</td>
    </tr>
  </tbody>
</table>

<h2 id="checklist">Checklist</h2>

<blockquote>
  <p>For the full scoring rubric (0–100) and detailed verification steps, see the <a href="https://vercel.com/kb/guide/agent-readability-spec">Agent-Readability Spec</a>.</p>
</blockquote>

<p><strong>Discovery</strong></p>

<ul>
  <li>
    <p>Serve <code class="language-plaintext highlighter-rouge">/llms.txt</code> with a curated H1 + H2 index of your content</p>
  </li>
  <li>
    <p>Publish <code class="language-plaintext highlighter-rouge">sitemap.xml</code> with accurate <code class="language-plaintext highlighter-rouge">lastmod</code> dates</p>
  </li>
  <li>
    <p>Serve <code class="language-plaintext highlighter-rouge">/sitemap.md</code> with a semantic, markdown-formatted sitemap describing docs sections and pages</p>
  </li>
  <li>
    <p>Document agent access policy in <code class="language-plaintext highlighter-rouge">robots.txt</code></p>
  </li>
  <li>
    <p>Add JSON-LD structured data (title, description, canonical URL, and breadcrumbs) to every page</p>
  </li>
</ul>

<p><strong>Retrieval</strong></p>

<ul>
  <li>
    <p>Return markdown for <code class="language-plaintext highlighter-rouge">Accept: text/markdown</code> with a <code class="language-plaintext highlighter-rouge">Vary: Accept</code> header</p>
  </li>
  <li>
    <p>Generate <code class="language-plaintext highlighter-rouge">.md</code> endpoints for all content pages</p>
  </li>
  <li>
    <p>Include frontmatter metadata (<code class="language-plaintext highlighter-rouge">title</code>, <code class="language-plaintext highlighter-rouge">canonical_url</code>, <code class="language-plaintext highlighter-rouge">last_updated</code>) in every markdown response</p>
  </li>
  <li>
    <p>Add <code class="language-plaintext highlighter-rouge">&lt;link rel="alternate" type="text/markdown"&gt;</code> to HTML pages</p>
  </li>
  <li>
    <p>Detect AI agents (user-agent matching, Signature-Agent header, heuristic fallback) and serve markdown automatically</p>
  </li>
  <li>
    <p>Verify by appending <code class="language-plaintext highlighter-rouge">.md</code> to any page URL and confirming you get clean markdown with frontmatter</p>
  </li>
</ul>

<p><strong>Tool access</strong></p>

<ul>
  <li>
    <p>Expose search via MCP server or search API</p>
  </li>
  <li>
    <p>Add a <code class="language-plaintext highlighter-rouge">SKILLS.md</code> or <code class="language-plaintext highlighter-rouge">AGENTS.md</code> file with install, config, and usage instructions for coding agents</p>
  </li>
</ul>

<p>To get started, run through the <a href="#checklist">checklist</a> above, then score your site against the <a href="https://vercel.com/kb/guide/agent-readability-spec">Agent-Readability Spec</a>.</p>

<h2 id="further-reading">Further reading</h2>

<ul>
  <li>
    <p><a href="https://vercel.com/kb/guide/agent-readability-spec">Agent-Readability Spec</a>: Full checklist and 0–100 scoring rubric</p>
  </li>
  <li>
    <p><a href="https://vercel.com/docs/mcp">Vercel MCP server</a>: Query Vercel, Next.js, and AI SDK docs via MCP</p>
  </li>
  <li>
    <p><a href="https://llmstxt.org/">llms.txt specification</a>: The llms.txt standard</p>
  </li>
</ul>]]></content><author><name></name></author><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Agents" /><category term="Developer Relations" /><summary type="html"><![CDATA[How to structure developer documentation so that AI agents like Claude Code, ChatGPT, Cursor, Copilot can discover, retrieve, and cite it accurately. Three layers: discovery, retrieval, and tools.]]></summary></entry><entry><title type="html">Agent Readability: A Specification for AI-Optimized Websites</title><link href="https://timothyjordan.com/blog/2026/03/23/agent-readability-spec.html" rel="alternate" type="text/html" title="Agent Readability: A Specification for AI-Optimized Websites" /><published>2026-03-23T00:00:00+00:00</published><updated>2026-03-23T00:00:00+00:00</updated><id>https://timothyjordan.com/blog/2026/03/23/agent-readability-spec</id><content type="html" xml:base="https://timothyjordan.com/blog/2026/03/23/agent-readability-spec.html"><![CDATA[<p><em><a href="https://vercel.com/kb/guide/agent-readability-spec">Cross-posted from Vercel KB</a></em></p>

<p>AI agents — tools like ChatGPT, Claude, Copilot, and Cursor — are becoming a primary way people discover and consume web content.</p>

<p>Agent readability is a set of best practices that make your website accessible to AI agents and assistants. It covers three areas:</p>

<ul>
  <li>
    <p><strong>Discovery</strong> — Can agents find your pages? (<code class="language-plaintext highlighter-rouge">llms.txt</code>, sitemaps, <code class="language-plaintext highlighter-rouge">robots.txt</code>)</p>
  </li>
  <li>
    <p><strong>Structure</strong> — Can agents parse your pages? (meta tags, headings, structured data, markdown mirrors)</p>
  </li>
  <li>
    <p><strong>Context</strong> — Can agents understand your content? (skill files, content negotiation, code documentation)</p>
  </li>
</ul>

<p>This guide describes what to implement, why it matters, and how to verify each requirement.</p>

<hr />

<h2 id="quick-start-checklist">Quick-Start Checklist</h2>

<h3 id="site-level-files">Site-Level Files</h3>

<ul>
  <li>
    <p>Serve an <code class="language-plaintext highlighter-rouge">llms.txt</code> file at your site root</p>
  </li>
  <li>
    <p>Allow AI bots in <code class="language-plaintext highlighter-rouge">robots.txt</code></p>
  </li>
  <li>
    <p>Publish a <code class="language-plaintext highlighter-rouge">sitemap.xml</code> with <code class="language-plaintext highlighter-rouge">&lt;lastmod&gt;</code> dates</p>
  </li>
  <li>
    <p>Publish a <code class="language-plaintext highlighter-rouge">sitemap.md</code> with headings and links</p>
  </li>
  <li>
    <p>Create an <code class="language-plaintext highlighter-rouge">AGENTS.md</code> with install, config, and usage sections</p>
  </li>
  <li>
    <p>Ensure all pages are <a href="#page-discoverability">discoverable</a> from at least one source</p>
  </li>
</ul>

<h3 id="page-level-html">Page-Level HTML</h3>

<ul>
  <li>
    <p>Return <a href="#http-response-basics">HTTP 200</a> with 0–1 redirects</p>
  </li>
  <li>
    <p>Set correct <code class="language-plaintext highlighter-rouge">Content-Type</code> headers</p>
  </li>
  <li>
    <p>Do not set restrictive <code class="language-plaintext highlighter-rouge">x-robots-tag</code> values</p>
  </li>
  <li>
    <p>Include a <code class="language-plaintext highlighter-rouge">&lt;link rel="canonical"&gt;</code> tag</p>
  </li>
  <li>
    <p>Add <code class="language-plaintext highlighter-rouge">meta description</code> (50+ characters), <code class="language-plaintext highlighter-rouge">og:title</code>, <code class="language-plaintext highlighter-rouge">og:description</code>, and <code class="language-plaintext highlighter-rouge">html lang</code></p>
  </li>
  <li>
    <p>Add <a href="#schema.org-/-json-ld">Schema.org / JSON-LD</a> structured data</p>
  </li>
  <li>
    <p>Use <a href="#section-headings">3+ section headings</a> (h1–h3) per page</p>
  </li>
  <li>
    <p>Maintain a <a href="#signal-to-noise-ratio">text-to-HTML ratio</a> above 15%</p>
  </li>
  <li>
    <p>Include a <a href="#glossary-link">glossary or terminology link</a></p>
  </li>
</ul>

<h3 id="server-configuration">Server Configuration</h3>

<ul>
  <li>
    <p>Provide <a href="#markdown-mirrors-and-content-negotiation">markdown mirrors</a> for HTML pages</p>
  </li>
  <li>
    <p>Add <code class="language-plaintext highlighter-rouge">&lt;link rel="alternate" type="text/markdown"&gt;</code> to HTML pages</p>
  </li>
  <li>
    <p>Return <code class="language-plaintext highlighter-rouge">Link</code> header with <code class="language-plaintext highlighter-rouge">rel="canonical"</code> from markdown endpoints</p>
  </li>
  <li>
    <p>Support <code class="language-plaintext highlighter-rouge">Accept: text/markdown</code> content negotiation</p>
  </li>
  <li>
    <p>Include a <code class="language-plaintext highlighter-rouge">## Sitemap</code> section in markdown pages</p>
  </li>
</ul>

<h3 id="content-quality">Content Quality</h3>

<ul>
  <li>
    <p><a href="#code-blocks">Fence all code blocks</a> with language identifiers</p>
  </li>
  <li>
    <p>Link to <a href="#open-api-schema">OpenAPI/Swagger schemas</a> from API reference pages</p>
  </li>
</ul>

<hr />

<h2 id="scoring">Scoring</h2>

<p>Your agent readability score measures how well your site meets these requirements:</p>

<p><code class="language-plaintext highlighter-rouge">score = round((passed checks / total checks) x 100)</code></p>

<p>Only checks with a <strong>pass</strong> status count toward the numerator. Checks that <strong>fail</strong>, <strong>warn</strong>, or <strong>error</strong> do not.</p>

<p>The total is the sum of all site-wide checks plus all per-page checks across every discovered page. Because per-page checks run on every page, sites with many pages have a larger denominator — a single failing check matters less on a large site, but a systemic issue (such as missing canonical links on every page) compounds significantly.</p>

<table>
  <thead>
    <tr>
      <th>Score</th>
      <th>Rating</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>90–100</td>
      <td>Excellent</td>
      <td>Highly optimized for AI agents. All critical checks pass.</td>
    </tr>
    <tr>
      <td>70–89</td>
      <td>Good</td>
      <td>Meets most requirements. Address warnings to improve.</td>
    </tr>
    <tr>
      <td>50–69</td>
      <td>Fair</td>
      <td>Has gaps. Review failed checks and implement fixes.</td>
    </tr>
    <tr>
      <td>0–49</td>
      <td>Needs Improvement</td>
      <td>Significant work needed across multiple areas.</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="site-level-requirements">Site-Level Requirements</h2>

<p>These requirements apply once per site, at the root level.</p>

<h3 id="llmstxt">llms.txt</h3>

<p><strong>What:</strong> Serve an <code class="language-plaintext highlighter-rouge">llms.txt</code> file that lists your documentation pages. This is the primary entry point for AI agents discovering your content.</p>

<p><strong>Why:</strong> AI agents look for <code class="language-plaintext highlighter-rouge">llms.txt</code> as a machine-readable index of your site’s content, similar to how search engines use <code class="language-plaintext highlighter-rouge">sitemap.xml</code>. Without it, agents must crawl your site to find pages, which is slower and less reliable.</p>

<p><strong>Requirements:</strong></p>

<ul>
  <li>
    <p>Serve the file at one of: <code class="language-plaintext highlighter-rouge">/llms.txt</code>, <code class="language-plaintext highlighter-rouge">/.well-known/llms.txt</code>, or <code class="language-plaintext highlighter-rouge">/docs/llms.txt</code></p>
  </li>
  <li>
    <p>Alternatively, serve <code class="language-plaintext highlighter-rouge">llms-full.txt</code> at the same paths</p>
  </li>
  <li>
    <p>Use <code class="language-plaintext highlighter-rouge">text/plain</code> as the <code class="language-plaintext highlighter-rouge">Content-Type</code></p>
  </li>
  <li>
    <p>The file must not be empty</p>
  </li>
  <li>
    <p>Listed URLs should use <code class="language-plaintext highlighter-rouge">.md</code> or <code class="language-plaintext highlighter-rouge">.mdx</code> extensions, not <code class="language-plaintext highlighter-rouge">.html</code></p>
  </li>
</ul>

<p><strong>Example:</strong></p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh"># Example Product Documentation</span>

<span class="gu">## Getting Started</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Installation</span><span class="p">](</span><span class="sx">/docs/installation.md</span><span class="p">)</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Quick Start</span><span class="p">](</span><span class="sx">/docs/quick-start.md</span><span class="p">)</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Configuration</span><span class="p">](</span><span class="sx">/docs/configuration.md</span><span class="p">)</span>

<span class="gu">## API Reference</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Authentication</span><span class="p">](</span><span class="sx">/docs/api/auth.md</span><span class="p">)</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Endpoints</span><span class="p">](</span><span class="sx">/docs/api/endpoints.md</span><span class="p">)</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Error Handling</span><span class="p">](</span><span class="sx">/docs/api/errors.md</span><span class="p">)</span>

<span class="gu">## Guides</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Deployment</span><span class="p">](</span><span class="sx">/docs/guides/deployment.md</span><span class="p">)</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Monitoring</span><span class="p">](</span><span class="sx">/docs/guides/monitoring.md</span><span class="p">)</span>
</code></pre></div></div>

<p><strong>How to verify:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-I</span> &lt;https://example.com/llms.txt&gt;
<span class="c"># Should return 200 with Content-Type: text/plain</span>
</code></pre></div></div>

<h3 id="robotstxt">robots.txt</h3>

<p><strong>What:</strong> Ensure your <code class="language-plaintext highlighter-rouge">robots.txt</code> does not block known AI bots.</p>

<p><strong>Why:</strong> AI agents respect <code class="language-plaintext highlighter-rouge">robots.txt</code> directives. If you block them, your content will not be indexed or cited by AI assistants.</p>

<p><strong>Requirements:</strong></p>

<ul>
  <li>
    <p>Do not block <code class="language-plaintext highlighter-rouge">GPTBot</code>, <code class="language-plaintext highlighter-rouge">ClaudeBot</code>, <code class="language-plaintext highlighter-rouge">CCBot</code>, or <code class="language-plaintext highlighter-rouge">Google-Extended</code></p>
  </li>
  <li>
    <p>Do not disallow <code class="language-plaintext highlighter-rouge">/llms.txt</code></p>
  </li>
  <li>
    <p>Having no <code class="language-plaintext highlighter-rouge">robots.txt</code> at all triggers a warning — it is better to explicitly allow access</p>
  </li>
</ul>

<p><strong>Example:</strong></p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>User-agent: *
Allow: /

User-agent: GPTBot
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: CCBot
Allow: /

User-agent: Google-Extended
Allow: /

Sitemap: &lt;https://example.com/sitemap.xml&gt;
</code></pre></div></div>

<p><strong>How to verify:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl &lt;https://example.com/robots.txt&gt;
<span class="c"># Inspect output for Disallow rules targeting AI bots or /llms.txt</span>
</code></pre></div></div>

<h3 id="sitemap-xml-and-markdown">Sitemap (XML and Markdown)</h3>

<p><strong>What:</strong> Publish both a <code class="language-plaintext highlighter-rouge">sitemap.xml</code> and a <code class="language-plaintext highlighter-rouge">sitemap.md</code> to help agents understand your site structure.</p>

<p><strong>Why:</strong> XML sitemaps are the standard for search engine crawlers. Markdown sitemaps give AI agents a structured, readable overview of your documentation hierarchy. Publishing both maximizes discoverability.</p>

<p><strong>Requirements for</strong> <code class="language-plaintext highlighter-rouge">sitemap.xml</code><strong>:</strong></p>

<ul>
  <li>
    <p>Serve a valid XML sitemap with <code class="language-plaintext highlighter-rouge">&lt;urlset&gt;</code> or <code class="language-plaintext highlighter-rouge">&lt;sitemapindex&gt;</code> containing <code class="language-plaintext highlighter-rouge">&lt;loc&gt;</code> entries</p>
  </li>
  <li>
    <p>Include <code class="language-plaintext highlighter-rouge">&lt;lastmod&gt;</code> dates so agents know which pages have changed</p>
  </li>
</ul>

<p><strong>Requirements for</strong> <code class="language-plaintext highlighter-rouge">sitemap.md</code><strong>:</strong></p>

<ul>
  <li>
    <p>Serve at one of: <code class="language-plaintext highlighter-rouge">/sitemap.md</code>, <code class="language-plaintext highlighter-rouge">/docs/sitemap.md</code>, or <code class="language-plaintext highlighter-rouge">/.well-known/sitemap.md</code></p>
  </li>
  <li>
    <p>Include headings and links that reflect your site’s structure</p>
  </li>
</ul>

<p><strong>Example</strong> <code class="language-plaintext highlighter-rouge">sitemap.md</code><strong>:</strong></p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh"># Sitemap</span>

<span class="gu">## Getting Started</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Installation</span><span class="p">](</span><span class="sx">/docs/installation.md</span><span class="p">)</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Quick Start</span><span class="p">](</span><span class="sx">/docs/quick-start.md</span><span class="p">)</span>

<span class="gu">## API Reference</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Authentication</span><span class="p">](</span><span class="sx">/docs/api/auth.md</span><span class="p">)</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Endpoints</span><span class="p">](</span><span class="sx">/docs/api/endpoints.md</span><span class="p">)</span>

<span class="gu">## Guides</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Deployment</span><span class="p">](</span><span class="sx">/docs/guides/deployment.md</span><span class="p">)</span>
<span class="p">-</span> <span class="p">[</span><span class="nv">Monitoring</span><span class="p">](</span><span class="sx">/docs/guides/monitoring.md</span><span class="p">)</span>
</code></pre></div></div>

<p><strong>How to verify:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl &lt;https://example.com/sitemap.xml&gt; | <span class="nb">head</span> <span class="nt">-20</span>
<span class="c"># Should contain &lt;urlset&gt; with &lt;loc&gt; and &lt;lastmod&gt; entries</span>

curl &lt;https://example.com/sitemap.md&gt;
<span class="c"># Should return structured markdown with headings and links</span>
</code></pre></div></div>

<h3 id="skill-file-agentsmd">Skill File (<a href="http://agents.md/">AGENTS.md</a>)</h3>

<p><strong>What:</strong> Create an <code class="language-plaintext highlighter-rouge">AGENTS.md</code> file that gives coding agents direct context about your product — how to install it, configure it, and use it.</p>

<p><strong>Why:</strong> Coding agents like Copilot, Claude Code, and Cursor use skill files to understand how to work with your product. A well-written skill file means agents can generate correct code for your users without guessing.</p>

<p><strong>Requirements:</strong></p>

<ul>
  <li>
    <p>Serve the file at one of: <code class="language-plaintext highlighter-rouge">/AGENTS.md</code>, <code class="language-plaintext highlighter-rouge">/agents.md</code>, <code class="language-plaintext highlighter-rouge">/.well-known/agents.md</code>, <code class="language-plaintext highlighter-rouge">/docs/AGENTS.md</code>, <code class="language-plaintext highlighter-rouge">/llms-full.txt</code>, <code class="language-plaintext highlighter-rouge">/CLAUDE.md</code>, <code class="language-plaintext highlighter-rouge">/.cursor/rules</code>, or <code class="language-plaintext highlighter-rouge">/.cursorrules</code></p>
  </li>
  <li>
    <p>Include at least 2 of the following sections: installation instructions, configuration details, usage examples or code blocks</p>
  </li>
</ul>

<p><strong>Example</strong> <code class="language-plaintext highlighter-rouge">AGENTS.md</code><strong>:</strong></p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh"># My Product</span>

<span class="gu">## Installation</span>

<span class="p">```</span><span class="nl">bash
</span>npm <span class="nb">install </span>my-product
<span class="p">```</span>
</code></pre></div></div>

<h2 id="configuration">Configuration</h2>

<p>Create a <code class="language-plaintext highlighter-rouge">my-product.config.ts</code> file in your project root:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">my-product</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nf">defineConfig</span><span class="p">({</span>
  <span class="na">apiKey</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">MY_PRODUCT_API_KEY</span><span class="p">,</span>
  <span class="na">region</span><span class="p">:</span> <span class="dl">'</span><span class="s1">us-east-1</span><span class="dl">'</span><span class="p">,</span>
<span class="p">});</span>
</code></pre></div></div>

<h2 id="usage">Usage</h2>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">createClient</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">my-product</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="nf">createClient</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">client</span><span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="dl">'</span><span class="s1">SELECT * FROM users</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">rows</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>How to verify:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl &lt;https://example.com/AGENTS.md&gt;
<span class="c"># Should return markdown with install, config, and/or usage sections</span>
</code></pre></div></div>

<h3 id="page-discoverability">Page Discoverability</h3>

<p><strong>What:</strong> Ensure every page on your site is reachable from at least one discovery source.</p>

<p><strong>Why:</strong> Pages that are not linked from <code class="language-plaintext highlighter-rouge">sitemap.xml</code>, <code class="language-plaintext highlighter-rouge">llms.txt</code>, <code class="language-plaintext highlighter-rouge">sitemap.md</code>, or other pages cannot be found by agents. Orphaned pages are invisible to AI.</p>

<p><strong>Requirements:</strong></p>

<ul>
  <li>Every page should appear in at least one of: <code class="language-plaintext highlighter-rouge">sitemap.xml</code>, <code class="language-plaintext highlighter-rouge">llms.txt</code>, <code class="language-plaintext highlighter-rouge">sitemap.md</code>, or be reachable via links from other discoverable pages</li>
</ul>

<p><strong>How to verify:</strong> Cross-reference your page count against the URLs listed in your sitemaps and <code class="language-plaintext highlighter-rouge">llms.txt</code>. Any page not present in any source is undiscoverable to agents.</p>

<hr />

<h2 id="page-level-requirements">Page-Level Requirements</h2>

<p>These requirements apply to every page on your site.</p>

<h3 id="http-response-basics">HTTP Response Basics</h3>

<p><strong>What:</strong> Ensure pages return clean HTTP responses that agents can process without issues.</p>

<p><strong>Why:</strong> Agents follow redirects and inspect headers to decide whether to index a page. Broken responses, long redirect chains, or restrictive headers cause agents to skip your content.</p>

<p><strong>Requirements:</strong></p>

<ul>
  <li>
    <p>Return HTTP 200 for all live pages</p>
  </li>
  <li>
    <p>Limit redirect chains to 0–1 hops (2+ redirects cause failures)</p>
  </li>
  <li>
    <p>Set the correct <code class="language-plaintext highlighter-rouge">Content-Type</code> header:</p>

    <ul>
      <li>
        <p>HTML pages: <code class="language-plaintext highlighter-rouge">text/html;charset=UTF-8</code></p>
      </li>
      <li>
        <p>Markdown pages: <code class="language-plaintext highlighter-rouge">text/plain;charset=UTF-8</code></p>
      </li>
    </ul>
  </li>
  <li>
    <p>Do not include <code class="language-plaintext highlighter-rouge">noindex</code>, <code class="language-plaintext highlighter-rouge">noai</code>, or <code class="language-plaintext highlighter-rouge">noimageai</code> in the <code class="language-plaintext highlighter-rouge">x-robots-tag</code> response header</p>
  </li>
</ul>

<p><strong>How to verify:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-I</span> <span class="nt">-L</span> &lt;https://example.com/docs/getting-started&gt;
<span class="c"># Check: HTTP status is 200, no more than 1 redirect, correct Content-Type</span>
<span class="c"># Check: x-robots-tag does not contain noindex, noai, or noimageai</span>
</code></pre></div></div>

<h3 id="html-meta-and-structure">HTML Meta and Structure</h3>

<p><strong>What:</strong> Include proper metadata, structured data, and heading hierarchy so agents can understand each page’s content and context.</p>

<p><strong>Why:</strong> Meta tags tell agents what a page is about before they read the full content. <a href="http://schema.org/">Schema.org</a> structured data provides machine-readable context like authorship, dates, and breadcrumbs. Headings create a scannable structure that agents use to extract sections relevant to a user’s query. A high text-to-HTML ratio ensures the page contains real content rather than framework boilerplate.</p>

<p><strong>Requirements:</strong></p>

<h4 id="canonical-link">Canonical link</h4>

<p>Include <code class="language-plaintext highlighter-rouge">&lt;link rel="canonical" href="..."&gt;</code> on every page to tell agents which URL is authoritative.</p>

<h4 id="meta-tags">Meta tags</h4>

<p>Include all of the following:</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">&lt;meta name="description" content="..."&gt;</code> (at least 50 characters)</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">&lt;meta property="og:title" content="..."&gt;</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">&lt;meta property="og:description" content="..."&gt;</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">lang</code> attribute on the <code class="language-plaintext highlighter-rouge">&lt;html&gt;</code> element</p>
  </li>
</ul>

<h4 id="schemaorg--json-ld"><a href="http://schema.org/">Schema.org</a> / JSON-LD</h4>

<p>Include a <code class="language-plaintext highlighter-rouge">&lt;script type="application/ld+json"&gt;</code> block with at minimum: title, description, canonical URL, <code class="language-plaintext highlighter-rouge">dateModified</code>, and <code class="language-plaintext highlighter-rouge">BreadcrumbList</code>.</p>

<p><strong>Example JSON-LD:</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"application/ld+json"</span><span class="nt">&gt;</span>
<span class="p">{</span>
  <span class="dl">"</span><span class="s2">@context</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">&lt;https://schema.org&gt;</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">@type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">TechArticle</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">headline</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Getting Started with My Product</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">description</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Learn how to install, configure, and use My Product in your application.</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">url</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">&lt;https://example.com/docs/getting-started&gt;</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">dateModified</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">2025-01-15T10:00:00Z</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">breadcrumb</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">@type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">BreadcrumbList</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">itemListElement</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span>
      <span class="p">{</span>
        <span class="dl">"</span><span class="s2">@type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ListItem</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">position</span><span class="dl">"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Docs</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">item</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">&lt;https://example.com/docs&gt;</span><span class="dl">"</span>
      <span class="p">},</span> <span class="p">{</span>
        <span class="dl">"</span><span class="s2">@type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ListItem</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">position</span><span class="dl">"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Getting Started</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">item</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">&lt;https://example.com/docs/getting-started&gt;</span><span class="dl">"</span>
      <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">}</span>
<span class="p">}</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>

<h4 id="section-headings">Section headings</h4>

<p>Use 3 or more headings (h1–h3) per page to create a clear structure. Well-structured pages produce better embeddings and allow agents to extract specific sections.</p>

<h4 id="signal-to-noise-ratio">Signal-to-noise ratio</h4>

<p>Maintain a text-to-HTML ratio above 15%. Pages dominated by JavaScript bundles, framework boilerplate, or empty wrappers are harder for agents to parse.</p>

<h4 id="glossary-link">Glossary link</h4>

<p>Include a link to a glossary or terminology page. This helps agents resolve ambiguous terms in your content.</p>

<p><strong>How to verify:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-s</span> &lt;https://example.com/docs/getting-started&gt; | <span class="nb">grep</span> <span class="nt">-E</span> <span class="s1">'canonical|og:title|og:description|ld\\\\+json|&lt;meta name="description"'</span>
<span class="c"># Each should return a match</span>
</code></pre></div></div>

<h3 id="markdown-mirrors-and-content-negotiation">Markdown Mirrors and Content Negotiation</h3>

<p><strong>What:</strong> Provide markdown versions of your HTML pages and support content negotiation so agents can request the format they prefer.</p>

<p><strong>Why:</strong> AI agents work natively with markdown. Raw HTML requires parsing and stripping away navigation, headers, footers, and scripts. A markdown mirror gives agents clean, structured content they can process directly — resulting in more accurate citations and better answers.</p>

<p><strong>Requirements:</strong></p>

<p><strong>Markdown mirrors</strong> — For every HTML page, provide a corresponding <code class="language-plaintext highlighter-rouge">.md</code> or <code class="language-plaintext highlighter-rouge">.mdx</code> version. Include frontmatter metadata:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">Getting Started</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">Learn how to install and configure My Product</span>
<span class="na">doc_version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2.1"</span>
<span class="na">last_updated</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2025-01-15"</span>
<span class="nn">---</span>
</code></pre></div></div>

<p><strong>Alternate link in HTML</strong> — Add a <code class="language-plaintext highlighter-rouge">&lt;link&gt;</code> tag pointing to the markdown version:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"alternate"</span> <span class="na">type=</span><span class="s">"text/markdown"</span> <span class="na">href=</span><span class="s">"/docs/getting-started.md"</span><span class="nt">&gt;</span>
</code></pre></div></div>

<p><strong>Canonical link in markdown responses</strong> — When serving markdown files, include a <code class="language-plaintext highlighter-rouge">Link</code> HTTP header:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Link: &lt;https://example.com/docs/getting-started&gt;; rel="canonical"
</code></pre></div></div>

<p><strong>Content negotiation</strong> — Return markdown when the client sends an <code class="language-plaintext highlighter-rouge">Accept: text/markdown</code> header:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-H</span> <span class="s2">"Accept: text/markdown"</span> &lt;https://example.com/docs/getting-started&gt;
<span class="c"># Should return markdown content with Content-Type: text/markdown</span>
</code></pre></div></div>

<p><strong>Sitemap section</strong> — Include a <code class="language-plaintext highlighter-rouge">## Sitemap</code> heading in each markdown page with a link to <code class="language-plaintext highlighter-rouge">/sitemap.md</code>:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gu">## Sitemap</span>

See the full <span class="p">[</span><span class="nv">sitemap</span><span class="p">](</span><span class="sx">/sitemap.md</span><span class="p">)</span> for all pages.
</code></pre></div></div>

<p><strong>How to verify:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Check alternate link</span>
curl <span class="nt">-s</span> &lt;https://example.com/docs/getting-started&gt; | <span class="nb">grep</span> <span class="s1">'rel="alternate".*text/markdown'</span>

<span class="c"># Check content negotiation</span>
curl <span class="nt">-H</span> <span class="s2">"Accept: text/markdown"</span> <span class="nt">-I</span> &lt;https://example.com/docs/getting-started&gt;
<span class="c"># Content-Type should be text/markdown</span>

<span class="c"># Check markdown mirror directly</span>
curl &lt;https://example.com/docs/getting-started.md&gt;
<span class="c"># Should return markdown with frontmatter</span>
</code></pre></div></div>

<h3 id="code-and-api-documentation">Code and API Documentation</h3>

<p><strong>What:</strong> Fence all code blocks with language identifiers and link to machine-readable API schemas.</p>

<p><strong>Why:</strong> Language-tagged code blocks let agents generate syntactically correct examples. API schema links (OpenAPI, Swagger) give agents the full contract of your API, enabling them to write integration code without guessing endpoints or parameters.</p>

<p><strong>Requirements:</strong></p>

<h4 id="code-blocks">Code blocks</h4>

<p>Every <code class="language-plaintext highlighter-rouge">&lt;pre&gt;&lt;code&gt;</code> block should have a <code class="language-plaintext highlighter-rouge">language-*</code> or <code class="language-plaintext highlighter-rouge">lang-*</code> class:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Good --&gt;</span>
<span class="nt">&lt;pre&gt;&lt;code</span> <span class="na">class=</span><span class="s">"language-typescript"</span><span class="nt">&gt;</span>
const client = createClient();
<span class="nt">&lt;/code&gt;&lt;/pre&gt;</span>

<span class="c">&lt;!-- Bad: no language specified --&gt;</span>
<span class="nt">&lt;pre&gt;&lt;code&gt;</span>
const client = createClient();
<span class="nt">&lt;/code&gt;&lt;/pre&gt;</span>
</code></pre></div></div>

<p>In markdown, always specify the language after the opening fence:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>```typescript
const client = createClient();
```
</code></pre></div></div>

<h4 id="open-api-schema">Open API schema</h4>

<p><strong>API schema links</strong>
— On pages with API documentation (URLs containing <code class="language-plaintext highlighter-rouge">/api/</code>, <code class="language-plaintext highlighter-rouge">/reference/</code>, <code class="language-plaintext highlighter-rouge">/endpoints/</code>, <code class="language-plaintext highlighter-rouge">/swagger/</code>, or <code class="language-plaintext highlighter-rouge">/openapi/</code>), include links to your machine-readable schema files (<code class="language-plaintext highlighter-rouge">openapi.json</code>, <code class="language-plaintext highlighter-rouge">swagger.json</code>, <code class="language-plaintext highlighter-rouge">swagger.yaml</code>, or <code class="language-plaintext highlighter-rouge">schema.json</code>).</p>

<p><strong>How to verify:</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Check code blocks have language classes</span>
curl <span class="nt">-s</span> &lt;https://example.com/docs/api/endpoints&gt; | <span class="nb">grep</span> <span class="nt">-E</span> <span class="s1">'&lt;code class="(language|lang)-'</span>

<span class="c"># Check API schema links</span>
curl <span class="nt">-s</span> &lt;https://example.com/docs/api/reference&gt; | <span class="nb">grep</span> <span class="nt">-E</span> <span class="s1">'openapi\\\\.json|swagger\\\\.(json|yaml)|schema\\\\.json'</span>
</code></pre></div></div>

<hr />

<h2 id="optional-ai-text-style-analysis">Optional: AI Text Style Analysis</h2>

<p>For technical documentation sites, an AI-powered analysis can evaluate your content against documentation best practices:</p>

<ul>
  <li>
    <p><strong>Voice and tone</strong> — Is the writing clear, direct, and consistent?</p>
  </li>
  <li>
    <p><strong>Formatting</strong> — Are lists, tables, and code examples used effectively?</p>
  </li>
  <li>
    <p><strong>Examples</strong> — Does the documentation include runnable code samples?</p>
  </li>
  <li>
    <p><strong>Units and precision</strong> — Are measurements, limits, and thresholds clearly stated?</p>
  </li>
  <li>
    <p><strong>Pricing clarity</strong> — If applicable, is pricing information unambiguous?</p>
  </li>
</ul>

<p>This analysis is opt-in and requires an AI model to evaluate your content. It is most useful for sites with extensive technical documentation where writing quality directly impacts whether agents can extract accurate information.</p>

<hr />

<h2 id="further-reading">Further Reading</h2>

<ul>
  <li>
    <p><a href="https://llmstxt.org/">llms.txt specification</a> — The standard for providing LLM-readable content</p>
  </li>
  <li>
    <p><a href="https://schema.org/">Schema.org documentation</a> — Structured data vocabulary for the web</p>
  </li>
  <li>
    <p><a href="https://www.openapis.org/">OpenAPI specification</a> — Machine-readable API documentation standard</p>
  </li>
  <li>
    <p><a href="https://www.robotstxt.org/">robots.txt specification</a> — The standard for web crawler access control</p>
  </li>
</ul>]]></content><author><name></name></author><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Agents" /><category term="Developer Relations" /><summary type="html"><![CDATA[Agent readability is a set of best practices that make your website parseable, navigable, and citable by AI agents like ChatGPT, Claude, Cursor, and Copilot. A site-wide spec across discovery, structure, and context.]]></summary></entry><entry><title type="html">Building Slack agents can be easy</title><link href="https://timothyjordan.com/blog/2026/03/03/Building-Slack-agents-can-be-easy.html" rel="alternate" type="text/html" title="Building Slack agents can be easy" /><published>2026-03-03T00:00:00+00:00</published><updated>2026-03-03T00:00:00+00:00</updated><id>https://timothyjordan.com/blog/2026/03/03/Building-Slack-agents-can-be-easy</id><content type="html" xml:base="https://timothyjordan.com/blog/2026/03/03/Building-Slack-agents-can-be-easy.html"><![CDATA[<p><em><a href="https://vercel.com/blog/building-slack-agents-can-be-easy">Cross-posted from vercel.com/blog</a></em></p>

<p>Slack is already where teams work. It provides a natural interface for agents, with messages, threads, buttons, and events, so you don’t need to invent a new UI or onboarding flow. Getting from “I want a Slack agent” to a running deployment, though, means coordinating across a lot of systems:</p>

<ul>
  <li>Creating an app in the Slack API console</li>
  <li>Configuring OAuth scopes and event subscriptions</li>
  <li>Writing webhook handlers and signature verification</li>
  <li>Deploying to infrastructure that can handle Slack’s 3-second response window</li>
</ul>

<p>Each piece has its own docs, and they all need to work together.</p>

<p>Coding agents like Claude Code, OpenCode, Cursor, and GitHub Copilot are well suited for exactly this kind of coordination because they can read docs, reason through dependencies, and generate code in seconds. We built the <a href="https://github.com/vercel-labs/slack-agent-skill">Slack agent skill</a> to take advantage of that. It builds on our <a href="https://vercel.com/templates/nitro/slack-agent-template">Slack Agent Template</a>, works with the coding agent of your choice, and takes you from idea to a deployed Slack agent on Vercel in a single session, automating steps when possible and showing you exactly what to click when it can’t.</p>

<h2 id="from-idea-to-deployed-agent-with-the-skill-wizard">From idea to deployed agent with the skill wizard</h2>

<p>Install the skill and run the wizard:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npx skills add vercel-labs/slack-agent-skill
</code></pre></div></div>

<p>Then run the skill in your agent. For example, with Claude Code:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/slack-agent new
</code></pre></div></div>

<p>The wizard starts by asking what kind of agent you want to build. You might say “a support agent that answers questions from our internal docs,” or “a standup bot that collects updates from the team every morning.” Based on your answer, it generates a custom implementation plan tailored to your use case. You review and approve the plan before any code is written.</p>

<p>From there, you move through five stages:</p>

<ul>
  <li><strong>Project setup</strong>: You choose your LLM provider, and the agent scaffolds your project from our <a href="https://github.com/vercel-partner-solutions/slack-agent-template">Slack Agent Template</a>.</li>
  <li><strong>Slack app creation</strong>: The agent customizes your <code class="language-plaintext highlighter-rouge">manifest.json</code> with your app name, description, and bot display settings, then opens Slack’s console and walks you through creating the app and installing it to your workspace. OAuth scopes, event subscriptions, and slash commands come pre-configured from the template.</li>
  <li><strong>Environment configuration</strong>: The agent walks you through setting up your signing secret, bot token, and any API keys your project needs.</li>
  <li><strong>Local testing</strong>: The agent starts your dev server and connects it to Slack so you can message your bot and see it respond in real time before anything touches production.</li>
  <li><strong>Production deployment</strong>: The agent walks you through deploying to Vercel and setting up your environment variables. From this point, every git push triggers a new deployment.</li>
</ul>

<p style="text-align: center;"><img src="/blog/assets/slack-agent-skill/slack-agent-skill-dark.avif" alt="Slack Agent Skill in action" /></p>

<h2 id="what-the-skill-gives-you">What the skill gives you</h2>

<p>The Slack agent skill gives you an agent that can:</p>

<ul>
  <li>Hold multi-turn conversations across messages and threads</li>
  <li>Pause for human approval before taking sensitive actions</li>
  <li>Stream responses to Slack in real time</li>
  <li>Read channels and threads on its own</li>
</ul>

<p>Your agent interacts with Slack and your systems through <strong>tools</strong>, functions it can call to take actions or retrieve information. The template ships with tools for:</p>

<ul>
  <li>Reading channel messages</li>
  <li>Fetching thread context</li>
  <li>Joining channels (with human approval)</li>
  <li>Searching channels by name, topic, or purpose</li>
</ul>

<p>You can also tell your coding agent to add custom tools that connect to your own systems. Want the agent to look up a customer record, create a support ticket, or query a database? Each of those becomes a tool the agent knows when and how to call.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">tool</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">ai</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">z</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">zod</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">lookupCustomer</span> <span class="o">=</span> <span class="nf">tool</span><span class="p">({</span>
  <span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Look up a customer record by email</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">inputSchema</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">object</span><span class="p">({</span>
    <span class="na">email</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">string</span><span class="p">().</span><span class="nf">describe</span><span class="p">(</span><span class="dl">"</span><span class="s2">Customer email address</span><span class="dl">"</span><span class="p">),</span>
  <span class="p">}),</span>
  <span class="na">execute</span><span class="p">:</span> <span class="k">async </span><span class="p">({</span> <span class="nx">email</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">use step</span><span class="dl">"</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">db</span><span class="p">.</span><span class="nx">customers</span><span class="p">.</span><span class="nf">findByEmail</span><span class="p">(</span><span class="nx">email</span><span class="p">);</span>
    <span class="k">return</span> <span class="p">{</span> <span class="na">success</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">customer</span> <span class="p">};</span>
  <span class="p">},</span>
<span class="p">});</span>
</code></pre></div></div>

<p><a href="https://vercel.com/docs/workflow"><strong>Workflow DevKit</strong></a> is what makes the agent durable. A Slack agent often needs to hold a conversation across many messages, or wait hours for someone to approve a request. Workflow DevKit lets the agent suspend mid-conversation, wait for external input, and pick back up exactly where it left off. Tool calls are automatically retried on failure, and responses stream back to Slack in real time.</p>

<p><strong>Human-in-the-loop</strong> is built in. When the agent needs to perform a sensitive action like joining a channel, it posts a message with Approve and Reject buttons and suspends. You’re only billed for <a href="https://vercel.com/docs/functions/usage-and-pricing#active-cpu">active CPU time</a>, so waiting costs nothing, even if approval takes days. This pattern extends to any action requiring approval, from sending messages to modifying data to calling external APIs.</p>

<p><a href="https://vercel.com/ai-gateway"><strong>AI Gateway</strong></a> gives your agent access to hundreds of models from every major provider through a single API key. Switching models is a one-line change, and if a provider goes down, AI Gateway automatically routes to another so your agent stays up.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">gateway</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@ai-sdk/gateway</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">generateText</span><span class="p">({</span>
  <span class="na">model</span><span class="p">:</span> <span class="nf">gateway</span><span class="p">(</span><span class="dl">"</span><span class="s2">anthropic/claude-sonnet-4.6</span><span class="dl">"</span><span class="p">),</span>
  <span class="na">prompt</span><span class="p">:</span> <span class="nx">userMessage</span><span class="p">,</span>
<span class="p">});</span>
</code></pre></div></div>

<h2 id="going-deeper">Going deeper</h2>

<p>Once your agent is live, there are a few ways to extend it and understand it better.</p>

<p>Our <a href="https://vercel.com/academy/slack-agents">Vercel Academy Slack Agents course</a> covers the entire lifecycle, from creating and configuring a Slack app to handling events and interactive messages, building agents with the AI SDK, and deploying to production.</p>

<p>Vercel preview deployments let you test changes before they reach production. For Slack bots, this may require bypassing deployment protection so Slack’s webhook verification can reach your endpoint. Our <a href="https://vercel.com/kb/guide/test-slack-bot-with-vercel-preview-deployment">testing guide</a> explains how to set this up.</p>

<p><a href="https://vercel.com/sandbox">Vercel Sandboxes</a> let your agent execute code in isolated environments, so it can run user-provided scripts like analyzing a spreadsheet, generating a chart, or transforming data without risking your infrastructure.</p>

<h2 id="get-started">Get started</h2>

<p>The whole experience fits in one session with your coding agent.</p>

<ul>
  <li><a href="https://github.com/vercel-labs/slack-agent-skill"><strong>Slack Agent Skill</strong></a></li>
  <li><a href="https://github.com/vercel-partner-solutions/slack-agent-template"><strong>Slack Agent Template</strong></a></li>
  <li><a href="https://vercel.com/academy/slack-agents"><strong>Vercel Academy: Slack Agents</strong></a></li>
</ul>]]></content><author><name></name></author><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Engineering" /><category term="Artificial Intelligence" /><category term="Agents" /><category term="Developer Relations" /><summary type="html"><![CDATA[Walk-through of the Vercel Slack agent skill, which takes a coding agent (Claude Code, OpenCode, Cursor) from idea to a deployed Slack bot in a single session — handling app config, OAuth, webhooks, and infra.]]></summary></entry><entry><title type="html">Hello, Vercel.</title><link href="https://timothyjordan.com/blog/2025/11/03/hello-vercel.html" rel="alternate" type="text/html" title="Hello, Vercel." /><published>2025-11-03T00:00:00+00:00</published><updated>2025-11-03T00:00:00+00:00</updated><id>https://timothyjordan.com/blog/2025/11/03/hello-vercel</id><content type="html" xml:base="https://timothyjordan.com/blog/2025/11/03/hello-vercel.html"><![CDATA[<p style="text-align: center;"><img src="/blog/assets/timothy-at-vercel.jpg" alt="Timothy in the Vercel lobby with G and Tomo" /></p>

<p>I’ve redeployed timothyjordan.com to <a href="https://vercel.com">Vercel</a>, and I’m super excited to lead the Developer Experience team at a company famous for excellent Developer Experience.</p>]]></content><author><name>Timothy Jordan</name></author><category term="Engineering" /><category term="Engineering" /><category term="Developer Relations" /><summary type="html"><![CDATA[I’ve redeployed timothyjordan.com to Vercel, and I’m super excited to lead the Developer Experience team at a company famous for excellent developer experience.]]></summary></entry><entry><title type="html">My exit from Google: we don’t say goodbye, we say good journey.</title><link href="https://timothyjordan.com/blog/2025/10/25/good-journey.html" rel="alternate" type="text/html" title="My exit from Google: we don’t say goodbye, we say good journey." /><published>2025-10-25T00:00:00+00:00</published><updated>2025-10-25T00:00:00+00:00</updated><id>https://timothyjordan.com/blog/2025/10/25/good-journey</id><content type="html" xml:base="https://timothyjordan.com/blog/2025/10/25/good-journey.html"><![CDATA[<p style="text-align: center;"><img src="/blog/assets/timothy-goodbye-google.jpg" alt="Timothy in a Google Lobby" /></p>

<p>Although I wasn’t searching for a new role, one found me, and after a long period of accumulating scope and responsibilities at Google, I’m excited to try something new.</p>

<p>I’m incredibly grateful for the 15.75 years I’ve had to work side-by-side with some of the most amazing engineers and storytellers I’ve ever met… and to do so on a global scale. I first learned about Developer Advocacy when I ran into Mano Marks at Google I/O in 2009. He had just returned from a talk at the UN, where he spoke about combating deforestation by empowering indigenous peoples with mapping platforms. I knew right away that it was my dream job, and I applied to Google.</p>

<p>I started as a Developer Advocate working on the social web. First there was Open Social, then Friend Connect, and Google Buzz. The +1 button was one of our first major launches leading up to Google+, and it ended up being on a shockingly high percentage of page loads on the Internet (the TL used to say, “it’s not just a f#$%ing button”). I changed teams to work on Google Glass and gave the <a href="https://www.youtube.com/watch?v=JpWmGX55a40">first public demo of Glass</a> alongside an introduction to our developer platform. That was probably the only time I’ll ever be the subject of a <a href="https://www.youtube.com/watch?v=5Uz3cwHT0S0">Saturday Night Live parody</a>. After Glass, I helped evolve the DevRel structure at Google a few times. First leading a small team of DAs for “<a href="https://youtu.be/ctiaVxgclsg?si=KJEnxZQ1U9GoG6xV&amp;t=365">ubiquitous computing</a>”, then all of Developer Advocacy and the Google Developer Studio, then a portfolio of DevRel teams. That portfolio of teams evolved over the years, and I had the privilege of starting full DevRel teams for Flutter, Firebase, TensorFlow, and evolving many others, such as Maps, Apps, Search, and Material. Later on, I was able to build Google’s first Internal DevRel team and take on responsibility for the Developer Ecosystem team and the Open Source Programs Office. Throughout all those years, I helped stretch Developer Relations at Google through projects like growing our <a href="https://www.youtube.com/playlist?list=PLOU2XLYxmsII8REpkzsy1bJHj6G1WEVA1">online video presence</a>, founding the annual internal DevRel Conference, coauthoring the Developer Relations Engineer role profile, evolving the Google I/O <a href="https://www.youtube.com/playlist?list=PLOU2XLYxmsIJJVnHWmd1qfr0Caq4VZCu4">online experience</a> and <a href="https://www.youtube.com/watch?v=GjvgtwSOCao">developer keynote</a>, helping reinvent perf, and more recently <a href="https://www.youtube.com/watch?v=g602w47NNJ4&amp;t=219s">helping evolve our craft in the age of AI</a>. Together, we helped grow Google from a handful of platforms supporting consumer products to what it is today.</p>

<p>Here I am today, wondering how I was so fortunate to have worked on so many amazing technologies alongside so many extraordinary humans. Thank you to all my Google colleagues for everything you’ve taught me and all the crazy ideas we turned into the state of the DevRel art.</p>

<p>As for what’s next, well, that will have to be a different post.</p>

<p style="text-align: center;"><img src="/blog/assets/good-journey-meme.gif" alt="He-Man good journey meme" /></p>]]></content><author><name>Timothy Jordan</name></author><category term="Engineering" /><category term="Google" /><category term="Engineering" /><category term="Developer Relations" /><summary type="html"><![CDATA[My farewell letter to 16 years at Google, some accomplishments, and a lot of gratitude.]]></summary></entry></feed>