Zum Hauptinhalt springen

How v10r serves AI agents as first-class consumers: a machine-readable docs layer, an agent-facing URL map, self-correcting MCP errors, and a loopable convention checker.

v10r's consumption model is agent-mediated — the PRD's thesis is that an AI agent reads these tested patterns and adapts them to a new project. So agent experience here is not a feature bolted onto a human product; it is the delivery mechanism for the product itself. This document describes the four AX surfaces, the contracts they keep, and the constraints that shaped them.


The four surfaces

Surface Consumer Mechanism
.md docs layer any HTTP agent (Claude Code, Cursor, and OpenCode send Accept: text/markdown today) every published /docs/** page serves raw markdown at its URL + .md; the clean URL honors Accept: text/markdown with a redirect
/llms.txt agents navigating the docs (llmstxt.org shape) curated URL map of the whole published corpus, plus an in-band "Instructions for LLM agents" block
Hosted MCP (POST /api/mcp/public) MCP clients without repo access six read-only pattern tools, every error carrying a ## Next actions recovery block
Stdio MCP (mcp/server.ts) coding agents with the repo cloned the same six tools in an ephemeral, network-isolated container

Repo-level agent instructions live in AGENTS.md (the cross-tool convention) and CLAUDE.md (Claude Code specifically); both point here rather than duplicating this content.

Live proof: /showcases/ax exercises every surface with real requests from the browser — the negotiation redirect, /llms.txt, a ## Next actions error, the protocol-version 400, and an interactive validate_snippet loop.

Nothing is hand-maintained

Every artifact derives from an existing single source of truth, because a parallel corpus that needs manual sync is the documented failure mode of this whole genre (Astro removed its llms.txt over exactly that):

Artifact Derived from
.md responses getManifest() / getRawMarkdown() (src/lib/server/docs/manifest.ts) — the raw glob is consulted only after a manifest hit, so a blocked or draft doc is structurally unreachable (src/lib/server/docs/markdown-urls.ts)
/llms.txt buildLlmsTxt(getManifest()) — it cannot drift from the docs, and its test round-trips every emitted URL through the .md resolver
validate_snippet rules mcp/snippet-rules.json (shared by both MCP runtimes) + the --color-* names extracted from src/app.css at load time
Agent pages (/docs/programming/*.md) the agents registry body — never the raw files, whose frontmatter carries tool grants the HTML page has never published

The negotiation contract

Cache safety decided the shape, not convenience. The two cacheable artifacts each live at exactly one URL with no Vary; only the negotiated redirect varies by request header, and it is uncacheable:

Request Response Cache-Control Vary
GET /docs/x.md 200 markdown public, max-age=0, s-maxage=3600
GET /docs/x with Accept: text/markdown 303 → /docs/x.md no-store Accept
GET /docs/x (browser) 200 HTML + Link: </docs/x.md>; rel="alternate" as before

A 308 keyed off a request header without Vary would let a shared cache serve the redirect to browsers — that is why the negotiated response is 303 and no-store. Wildcards never negotiate (curl's default Accept gets HTML), and sec-fetch-dest: document never negotiates regardless of the header. The hook (docsMarkdown, stage 4 in src/hooks.server.ts) sits above loadStyle and i18n so no Set-Cookie can void s-maxage — pinned by an ordering assertion in handle-chain.gate.test.ts.

Discovery is layered so an agent finds the map from any entry point: every /docs response carries Link: </llms.txt>; rel="llms-txt", doc HTML heads carry <link rel="alternate" type="text/markdown">, robots.txt names /llms.txt, and AGENTS.md lists all four surfaces.

Self-correcting MCP errors

Every registry-produced tool error ends with a fixed-heading text block:

## Next actions
1. `search_patterns` {"query":"pattern"} — lists every pattern in the library.

The convention (each rule is a test assertion in diag.test.ts / next-actions.gate.test.ts):

  • the heading is the exact literal ## Next actions — one grep-able anchor on both surfaces;
  • 1–3 numbered entries: backticked tool name, compact JSON args, one clause of why;
  • argument values are always literals the registry wrote, never caller text — the block adds no echo surface;
  • suggested tools exist on the same trust surface (a public error never names an admin tool);
  • it is text, not a structured field — both surfaces are text-only because structuredContent makes Claude Code hide the text body entirely.

The hosted errorResult(body, diag, next) requires the actions at compile time, mirroring the reasoning that made diag required: a new error branch that strands the caller must be a compile error, not a review miss. The transport's two bare literals (unknown-tool, tool-threw) deliberately carry neither diag nor the block — that absence is the telemetry discriminator, and it now signals in both channels.

validate_snippet

The sixth pattern tool closes the loop svelte-autofixer pioneered: submit a snippet, get deterministic line-numbered findings with fixes and doc links, resubmit until clean. It mechanizes the conventions that were previously enforceable only by review: Svelte 5 runes (no export let, no $:, no stores), component-first, design tokens (including the opacity-modifier trap), Valibot over Zod.

Contracts worth knowing:

  • Findings are a success, not an error. An isError would make agent loops treat the normal case as failure, and it would pollute the mcp.call_log capability-gaps meter. Only malformed arguments are errors.
  • Oversize snippets are refused, never truncated (20 000 chars) — a partially validated snippet reads as clean.
  • The report never echoes snippet content — findings are rule at line:column only, and the argument is named snippet precisely because telemetry's extractQuery does not read it. Three independent barriers keep caller code out of the call log, pinned by observer.snippet.test.ts.
  • The stdio server is a one-directional superset: it adds a Bun.Transpiler syntax pre-check (syntax-error) the hosted surface never emits, and it degrades visibly (a report footer) if src/app.css is unreadable.

Known limitations and anti-goals

  • Cross-Origin-Resource-Policy: same-site is stamped on every response, so a browser-context cross-origin fetch of /llms.txt or a .md page is blocked. CLI and server-side fetches — the actual agent path — are unaffected. Deliberate: weakening the header for everyone to serve one edge case is the wrong trade.
  • No llms-full.txt. The size-ladder pattern serves frameworks with millions of consumers; here it would be a second corpus to maintain.
  • No SEO motivation. Google states Search ignores llms.txt; two 100k+-domain studies found zero citation effect. These surfaces exist for agents already fetching v10r, not for crawlers.
  • English only, locale-invariant. The docs corpus is English by design; .md URLs are unprefixed and locale-prefixed variants 308 to them.
← Back to Blueprint

Geht dieses Pattern noch besser? Sag uns, wie.

Feedback geben