Agent readiness means that an AI agent can find a site's interfaces, read its pages without the visual layout and learn from standard documents what it may and may not do. On 17 October 2026 we measured kondevs.com with five public scanners, fixed what they found and recorded what we refused to fake. This is what they measure, what we built and what it means for the Visibilio Website Builder.
If an AI agent cannot discover your APIs, read your content without parsing a visual layout and understand what it may and may not do, your site is not agent-ready. It is agent-adjacent. The difference matters because agents are becoming non-human actors in enterprise workflows, and the integration boundary is where policy either holds or fails silently.
The bottleneck is rarely the model. In McKinsey's State of AI survey for 2025, 62 percent of respondents said their organizations were at least experimenting with AI agents (23 percent scaling, 39 percent experimenting), yet in no business function did more than 10 percent report scaling them. Deloitte's Tech Trends 2026 cites a 2025 survey in which 48 percent of organizations named the searchability of data and 47 percent its reusability as challenges to AI automation. Microsoft's Cloud Adoption Framework guidance on agents (April 2026) asks that every agent be observable, governed and secure, and names the Model Context Protocol and Agent-to-Agent as the protocols to standardize on.
kondevs.com is the reference implementation of the Visibilio Website Builder: what we learn here becomes a proposal for every site the builder generates. On 17 October 2026 we ran the public agent-readiness scanners against it, read their source or methodology, fixed what they found and recorded what we chose not to fix. This article measures the technical preconditions for LLM visibility. Whether AI assistants actually find and cite a company is a separate measurement, covered in our article on LLM visibility. What an agent can do on the site is in How we made kondevs.com usable by AI agents.
Agent readiness has no single standard yet. Each scanner encodes its own view of what an agent needs.
| Scanner | What it checks | Result on 17 October |
|---|---|---|
| Is It Agent Ready | Discovery documents (robots.txt, sitemap, Link headers, API catalog, OAuth metadata, MCP server card, A2A agent card, agent skills, WebMCP, ARD), markdown negotiation, bot rules, commerce protocols | 93, Level 5; the one failing check, DNS-based agent discovery, waits for a DNSSEC record at the domain registrar |
| Is Agentic | Whether agent user agents can reach the site, content without JavaScript, llms.txt, auth.md, page sizes against a context budget | 100/100 |
| Mintlify Score (the open-source AFDocs checks) | llms.txt quality and coverage, markdown at .md URLs, directives that point agents to llms.txt, page size, parity between HTML and markdown | 82 in the morning, 98 after the first set of changes |
| Ora | 132 checks in nine goals, from discovery and trust to integration, authentication and payments; many concern listings outside the site | 90 at 14:51 UTC, 93 at 16:17 UTC; the changes in sections 3 to 7 went live after that scan and wait for its next one |
| PageSpeed Insights, with the browser and security tools | PageSpeed's Agentic Browsing category beside performance, accessibility and SEO; WebPageTest, MDN HTTP Observatory, SSL Labs, SecurityHeaders and Internet.nl for speed, headers and TLS | PageSpeed 99 mobile and 100 desktop, 100 for Accessibility, Best Practices and SEO, Agentic Browsing 4 of 4 applicable audits; Observatory 145/100, 12 of 12 tests; SSL Labs A+ on all four addresses |
Two lessons came before any code. Read how a check works before fixing it: Mintlify publishes its checks as the AFDocs project and Ora documents each check's method, and more than once the fix we would have guessed was not the one the check rewards. And a report can be stale: two scanners showed results computed before the deployment we wanted measured.
A markdown twin at every address
An agent that reads a web page pays in tokens for every menu, footer and script. Since July 2026 our pages have answered markdown when a client sends Accept: text/markdown. The documentation checks look for something simpler, an .md address. Both now work:
curl -s https://www.kondevs.com/about.md
curl -s -H "Accept: text/markdown" https://www.kondevs.com/about/
The .md address is not a second copy. When no file exists at that path, the site's middleware requests the page itself with Accept: text/markdown and returns that answer, with a canonical Link header back to the HTML page and noindex, so search engines keep indexing the page and not its twin. /about/index.md, /index.md and /de/index.md resolve too; real files such as /auth.md keep priority; an .md address with no page behind it answers a 404 in markdown. The home page's markdown is 6,865 bytes, and the server's x-markdown-tokens header estimates it at 1,714 tokens, so an agent can decide before it reads.
Telling agents where to look
Every page now points agents to llms.txt in both representations. In the HTML, the first element of the body is a short note with the addresses of llms.txt, llms-full.txt and the page's own .md twin; it is visually hidden, aria-hidden and out of the tab order, so screen-reader and keyboard users never meet it. In the markdown, the answer opens with the page's frontmatter (title, description, canonical, language, dates) and one quote line:
> For the complete index of this site, see [llms.txt](https://www.kondevs.com/llms.txt); the whole site in one file: [llms-full.txt](https://www.kondevs.com/llms-full.txt).
Every page also carries <link rel="alternate" type="text/markdown" href="/about.md"> in its head, and the HTTP response the same link. The not-found page carries none: an advertisement that points to nothing is worse than none.
llms.txt is a markdown file at the root of a site that tells language models what the site is and where its important pages are. Three findings changed ours.
Links to markdown. Every page link now uses the .md address: all 30 same-site links lead to markdown, 25 of them .md addresses and five text indexes. The JSON and XML documents (OpenAPI, the API catalog, the feeds, the sitemap) cannot be markdown, so they stay in llms.txt as plain URLs.
Coverage. Mintlify compares the pages linked from llms.txt, and from the text files it links, with the sitemap's pages that it counts as documentation: the English ones, without the German twins and without paths it treats as company pages, such as /about/ and /pricing/. Of the 60 sitemap entries, that is 33 pages. 23 were covered; the missing ten were six author pages and four articles that llms-full.txt mentioned only as bare URLs. Each author and article there now has a markdown link, and coverage is 33 of 33.
Sections. Ora rewards per-section indexes, so an agent working in one area does not read the whole site. llms.txt links four, each generated from the site's own data on every request: /services/llms.txt, /content-hub/llms.txt, /developers/llms.txt and /de/llms.txt for the German pages.
MCP in two revisions
The site runs a read-only Model Context Protocol server at POST /mcp with four tools: search_articles, get_article, list_services and get_company_info. The 2026-07-28 revision makes the protocol stateless: there is no initialize handshake, each request carries the protocol version, the client's capabilities and its self-declared name and version (clientInfo) in _meta, and the server checks mirrored HTTP headers against the body. clientInfo describes the client software; it is not an authenticated identity. Clients of the 2025-06-18 revision still exist, so one endpoint serves both: a request whose _meta names the new version takes the new path, and anything else is answered as before.
POST /mcp
MCP-Protocol-Version: 2026-07-28
Mcp-Method: server/discover
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientInfo":{"name":"example-agent","version":"1.0.0"},
"io.modelcontextprotocol/clientCapabilities":{}}}}
The answer lists both supported versions, the capabilities and the instructions, with ttlMs and cacheScope for caching. A request whose Mcp-Method header disagrees with its body is refused with error -32020; an unknown version with -32022 and the list of supported ones. Every tool is annotated read-only, and an article read through get_article carries a notice that its text is published content, to be treated as data and never as instructions. The notice is a label, not a defence against prompt injection: the agent still has to act on it. The MCP server card names the server, its version and its address (remotes) at the top level, as the draft server-card extension reads them. An A2A agent at POST /api/a2a answers questions about the company and the Content Hub.
One table, every description
The HTTP endpoints are described once, in one table in the code. The API index, the OpenAPI 3.1 description and the endpoint table on the developer portal are generated from it, and the build fails when the table and the OpenAPI operations disagree. The OpenAPI file documents 69 responses, 52 of them errors, each with an example built from the same function that produces the real error body; each operation's security requirement comes from the table (the public index has security: []). A shared source does not rule out a bug in the generator, so HTTP tests against the running site check that the behaviour matches the description.
The API catalog follows RFC 9727. Ora found no item links in its first entry, so the catalog now opens with itself and lists the two APIs, served as application/linkset+json with the RFC 9727 profile:
{
"anchor": "https://www.kondevs.com/.well-known/api-catalog",
"item": [
{ "href": "https://www.kondevs.com/api/content/", "title": "KONDEVS Publishing API (Content API)" },
{ "href": "https://www.kondevs.com/mcp", "title": "KONDEVS MCP server (read-only Content Hub)" }
]
}
For an enterprise, this is the agent-facing form of an integration inventory: one place that says which interfaces exist and where each is described.
Reading is open: pages, markdown, feeds, MCP and A2A need no key. Publishing is not: the Content API accepts a key that KONDEVS issues to a vetted partner, VISIBILIO among them, after a person has reviewed the request. No agent can grant itself that access.
A request without a valid key gets an answer an agent can act on:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="kondevs", resource_metadata="https://www.kondevs.com/.well-known/oauth-protected-resource"
RateLimit-Policy: "catalog";q=60;w=60
RateLimit: "catalog";r=59;t=60
resource_metadata points to the protected-resource metadata (RFC 9728), which names the authorization server (RFC 8414). A token that was sent but is invalid adds error="invalid_token"; a valid token without the needed scope gets 403 with error="insufficient_scope" (RFC 6750).
auth.md follows the eight steps of the auth.md profile (discover, pick a method, register, claim, exchange, use the token, errors, revocation) and says at each step what KONDEVS offers and what it does not. There is one method, OAuth client credentials with an issued key; anonymous registration, email-based registration and identity assertions from an outside provider are not offered; there is no claim ceremony, because the human review is the human step; there is no revocation endpoint, because a token lives one hour and a key is revoked by rotation. The agent_auth block in the authorization-server metadata matches: it names the registration endpoint (identity_endpoint) and an empty identity_types_supported. A scanner rewards a full list. An honest empty one is the right architecture decision.
The same rule decided the Personal Agent Protocol document. Its draft lets a company declare APIs and sign-in flows, but every declared API must accept the protocol's own session tokens. Ours do not, so /.well-known/poppy.json declares the website only:
{ "protocol_version": "0.1", "organization": { "name": "KONDEVS", "domain": "www.kondevs.com" }, "web": {} }
Ora suggested cursor-based pagination for the catalog. Changing the existing contract would break the partners that use it, so the cursor is an addition: GET /api/content/catalog accepts ?cursor= and answers next_cursor, null on the last page, and without a cursor the answer is what it was. A token the catalog did not issue is a 400, never a silent restart from the top.
Every rate-limited answer carries the IETF RateLimit header fields and their X-RateLimit-* counterparts, and a 429 adds Retry-After. The catalog used to check the key first and count the request second, so its 401 carried no rate-limit headers. It now counts first: the 401 tells an agent how much room it has, and repeated attempts with a wrong key are counted against the same policy.
That counter has limits worth stating. It keeps recent request times per client in KV, and KV is eventually consistent with no atomic increment, so concurrent requests, especially from different Cloudflare locations, can read the same count and all pass. It slows a brute-force attempt to roughly the policy's pace; it is not a hard ceiling. A strict limit needs an atomic counter, such as a Durable Object or the platform's own rate-limiting feature. We did not add a limit to the public index or the health endpoint: they touch no storage, so an anonymous caller cannot make them spend storage operations, only the processing of a request.
The organization is typed Organization and LocalBusiness, with its Sofia address, service area and two contact points; the previous type, ProfessionalService, is deprecated by schema.org. The home page lists its three services as Service nodes in an ItemList, each with the same @id as the node on its own service page. Technical articles are TechArticle, and articles written with VISIBILIO name VISIBILIO as the author and KONDEVS as the publisher, the same byline a reader sees and an agent receives through MCP.
Ora also checks whether an agent can find pricing. KONDEVS prices every engagement for its own scope, and inventing a price list to satisfy a scanner was not an option. The new pricing page says how each way of working is priced (embedded specialists by the day, end-to-end delivery as a fixed quote for a defined scope) and lists the steps from a first conversation to a quote. Its structured data is an OfferCatalog in which the only price is the one that is true: the first conversation, at 0 EUR.
Ora found lastmod on only 26 percent of our sitemap entries: the articles had them, the pages did not, because the easy value, the deployment date, changes on every deployment and teaches search engines to ignore the field. The sitemaps protocol asks for the date the page last changed.
Our approach: for a page that is code, the build takes the date of the last commit that touched the page's own source files (its template, its copy, its data), and an uncommitted change counts as changed on the day of the build. An article carries its own last change; the home page, the Content Hub and each category listing carry the later of their code's date and their newest article. All 60 entries now have a date, and none is the deployment date. Git is a proxy with known gaps: a commit can change only layout, and a change in a shared component or a removed article can change a page without touching its listed sources. The dates are as good as the source lists behind them, so those lists are checked by a test.
For natural-language indexers built on NLWeb, robots.txt now names a schema map, a short XML file that lists the site's four full-text feeds (RSS and JSON Feed, English and German).
Ora alone runs 132 checks. Some reward signals a site can send without doing the work behind them; we declined those, and some real ones whose cost is not justified yet.
| Suggestion | Why we did not do it |
|---|---|
Answer 401 on public endpoints so a probe finds a WWW-Authenticate header | The endpoints are public by design; a fake 401 would break every client that reads them |
| Serve markdown to AI crawlers that asked for HTML | The answer would depend on the user agent rather than on what the client asked for; explicit negotiation (Accept or .md) treats every client alike and avoids anything that could look like cloaking |
| Declare anonymous or email-based agent registration, a claim ceremony or a revocation endpoint | We do not run them; declaring them would send agents into flows that fail |
| Declare our APIs in the Personal Agent Protocol | They do not accept the protocol's session tokens |
| Replace page-and-limit pagination with cursors | Partners depend on the current contract; the cursor is an addition instead |
A ?mode=agent view, an NLWeb /ask endpoint, batch endpoints, a sandbox | Little value for a site of this size in October 2026; the markdown twins and MCP already serve agents |
| A price list | Engagements are priced per scope; the only true price on the site is the free first conversation |
Some remaining findings are not code: an entry in the MCP registries, a Wikidata item, and the DNSSEC record at the registrar. Those are on the company's list, not the site's.
In the afternoon, after many full test runs and every scanner crawling the sitemap, the sitemap and the German Content Hub started answering 500.
The load exposed an architectural dependency on a capped operation. The site runs on Cloudflare Workers with Workers KV for its articles, on the Workers Free plan: 100,000 KV reads but only 1,000 list operations a day. Three code paths listed keys on every request: the sitemap and the author pages listed the article index, and every German page and feed fell back to listing it because the German archive key did not exist yet (there is no German article). Ordinary traffic had never reached the cap. A day of scanners, link checkers and our own test runs did, and Cloudflare answered that the account had reached the free usage limit for that operation for the day.
The fix was one commit. The sitemap and the author pages read the published archive, one key read each, instead of listing the index, and a missing archive whose list fails reads as empty and is logged instead of failing the request. With the quota still exhausted, every affected address answered 200 on the preview, and production followed the same evening. The lessons apply to any integration platform:
- Keep list operations off public request paths. A list is a maintenance operation; reading a precomputed key is a request operation.
- Measuring a system is load. Scanners, link checkers and test suites on preview deployments share the account's quota.
- Know your platform's caps before a spike finds them. Workers Paid includes a million list operations a month and bills further ones; the free plan's 1,000 writes a day matter too, because a rate limiter that writes a counter on every call spends them.
| Measure | Before | After |
|---|---|---|
| Mintlify Score | 82 | 98 |
| Ora | 90 (14:51 UTC) | 93 (16:17 UTC, before the changes in sections 3 to 7) |
| Documentation pages of the sitemap covered by llms.txt | 23 of 33 | 33 of 33 |
| Same-site llms.txt links that lead to markdown | page links led to HTML | 30 of 30 |
Pages with a markdown twin at .md | none | every page |
Sitemap entries with a lastmod | 26 percent | 60 of 60 |
Is Agentic (100/100) and Is It Agent Ready (93, Level 5, with DNSSEC pending) were at those values at the end of the day. The scores use different criteria and are not comparable with each other.
Every tool above measures whether an agent can reach the site, read it cheaply and call it safely. None measures what a buyer sees: whether ChatGPT, Claude, Perplexity, Copilot or an AI Overview names KONDEVS when someone asks about enterprise integration, and whether it describes the company correctly. This article reports no such answers.
The work above supplies the inputs. Markdown an assistant can read without the layout, an llms.txt that covers every page, structured data that matches the visible page, full-text feeds and a consistent byline make a site easier to find, quote and attribute. Whether that turns into citations is measured by asking the assistants the questions buyers ask, at regular intervals, and recording which sources they name; our method and first results are in the article on LLM visibility.
kondevs.com is the builder's reference site, so each lesson is a proposal for the base template every generated site starts from. Together they are also a checklist for any site:
- Markdown twins by default: every page at its
.mdaddress and byAccept: text/markdown, announced withrel="alternate", with the llms.txt directive at the top of the HTML and the markdown. - llms.txt from the site's data: an index of markdown links plus one per section, generated on request and checked against the sitemap.
- Discovery documents from one table: OpenAPI, the API catalog with
itemlinks and the developer docs generated together, an example for every error, and tests that the running site behaves as described. - Refusals that explain: 401 with
WWW-Authenticateandresource_metadata, 403 withinsufficient_scope, and RateLimit headers on every limited answer, the 401 included. - Limits that say what they are: a KV counter is a brake, not a ceiling; where a strict limit matters, use an atomic counter.
- No list operation on a public request, and a hosting plan sized to the traffic, scanners included.
- Sitemap dates that track real changes: never the deployment date, with the method's gaps covered by tests.
- Honest declarations: empty lists where a capability is not run, a website-only Personal Agent Protocol document unless sessions exist, and a pricing page that states how work is priced when there is no price list.
- Measure like the outside world, carefully: read each scanner's method, rescan after deploying, and remember that every test run spends the site's quota.
A corporate website is a small integration boundary, but once agents call it, it raises the questions an ERP, a B2B gateway or an internal API platform raises: who is calling, what may it do, how much may it do, and can an operator see what it did. The last question needs more than annotations and notices; it needs request logs, identifiers that follow a call across systems, monitoring and alerts. Every declared capability is a contract, and none should be declared that the team cannot back up at 02:00 on a Tuesday. Agent readiness is an integration architecture question, and the answer is the one it has always been: if it is not governable, it is not production-ready.
