Docs

Content negotiation

Serve one resource as HTML for humans and Markdown for agents.

Open Markdown

One URL, one resource, more than one representation. A browser asks for text/html and gets a page; an agent asks for text/markdown and gets the same content without the layout, scripts, and styling it would only have to strip.

This is ordinary HTTP. The resource advertises what else it can be, and the client picks.

GET /docs/framework/routing

HTTP/2 200
Content-Type: text/html
Link: </docs/framework/routing.md>; rel="alternate"; type="text/markdown"
Vary: Accept

Why not a separate agent endpoint

The alternative is a parallel surface: a site, an API, an llms.txt, a scraping adapter, and an agent-specific endpoint for every piece of public information. Each one is a thing to build, secure, and keep in sync.

Multiple representations of one resource is the design the web already has. rel="alternate" is RFC 8288 web linking, and content negotiation is as old as HTTP. The agent needs no prior knowledge of your conventions beyond the standard.

Proa is unusually well placed for this, because a component is a typed Rust value rather than a DOM description. The Markdown representation is not a conversion of the HTML, it is the same component rendered through MdRenderSync.

What Proa emits today

PieceStatus
Typed page → Markdown from one componentShips. MdRenderSync / MdRender / md!
Per-page Markdown artifactShips. DocPage::markdown_path, emitted for every page
text/markdown responsesShips. DocsResponse::markdown()
Link header infrastructureShips. DISCOVERY_LINKS
rel="service-desc" pointing at OpenAPIShips
/.well-known/proa-site.json, /llms-full.txtShips
rel="alternate" per pageShips. Every HTML docs page links its Markdown path with Vary: Accept
Accept: text/markdown negotiationShips. Docs pages return Markdown when Accept lists text/markdown with nonzero quality (q=0 is respected)

Both halves are standard-shaped today: the service description and the negotiated Markdown representation.

Two layers, not one

.well-known and rel="alternate" answer different questions, and an agent needs both:

QuestionMechanism
What can I read here?Link: rel="alternate" on the resource
What can I do here?rel="service-desc", OpenAPI, MCP

Reading is a representation problem. Acting is a service-description problem. Do not collapse them into one endpoint.

Caching, before you ship it

An HTTP cache that honors Vary: Accept keeps representations separate. Raw Accept values can create many variants because clients send different preference lists. Cloudflare does not honor arbitrary Vary fields by default; enable its Vary support or explicitly include the selected representation in the cache key before caching negotiated routes.

Choose a representation policy:

See Page caching.

Good to know: Markdown is a good representation for today's models, not the final destination. The same Link mechanism carries application/ld+json for entity data or an OpenAPI document for actions. The page advertises its interfaces; the agent picks the one that fits the job.

Next steps

Search

Type at least 2 characters