---
title: "Agents & automation"
description: "Which surfaces of this site are a contract a machine can rely on, which are not, and what an agent is expected to do when the answer is \"that does not exist\"."
url: "https://opsinjs.pensievelabs.org/agents"
source: "https://opsinjs.pensievelabs.org/agents.md"
section: "Agents & automation"
kind: "guide"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["llm", "ai agent", "machine readable", "copilot", "code generation"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="guide" />

## Overview [#overview]

A large share of the code written against a design system is now written by a
model, and models fail against design systems in a very specific way: asked for
a component that does not exist, they invent a plausible one. The invented
component has a plausible API, plausible props, and no relationship to anything
real. In a to-do app that is an annoying hour. In an app that shows somebody
their blood-pressure reading it is a defect with a clinical shape.

opsinjs is built on the assumption that this will happen, and tries to make the
failure impossible rather than unlikely. The strategy has three parts:

1. **Every guessable URL answers.** A component that is only specified still
   resolves, and the answer says "planned, not implemented" in a form a
   machine can read. A 404 is what an agent fills with an invention; a definite
   negative is not.
2. **Every page has a machine twin.** Add `.md` to any docs URL and you get the
   processed markdown of that page. There is no chrome and no scraping, and the
   documentation components are left as self-describing tags whose attributes
   are the content.
3. **Everything measurable is generated.** Prop tables, token tables, contrast
   figures and the catalogue come from source, so what a machine reads is the
   same thing CI checks.

This page is the map. If you are configuring a tool, jump to
[MCP server](./mcp-server.mdx). If you are writing a prompt, jump to
[Rules for agents](./rules-for-agents.mdx).

## What is a contract and what is not [#what-is-a-contract-and-what-is-not]

The distinction matters more than the list. A contract is something that will
not change shape without a version and a changelog entry. Everything else is
free to move.

| Surface                                                                                    | Status             | What it is                                                                                    |
| ------------------------------------------------------------------------------------------ | ------------------ | --------------------------------------------------------------------------------------------- |
| `/**.md`                                                                                   | Contract           | Processed markdown twin of any docs page                                                      |
| `/llms.txt`                                                                                | Contract           | Curated index with absolute URLs and section headers                                          |
| `/llms-full.txt`                                                                           | Contract           | The whole corpus, size-capped                                                                 |
| `/llms-components.txt`, `/llms-health.txt`, `/llms-foundations.txt`, `/llms-reference.txt` | Contract           | Shards, for context windows that cannot take the whole thing. Each carries its sections whole |
| `/r/registry.json`                                                                         | Contract           | The shadcn-spec catalog. Required by the MCP server                                           |
| `/r/index.json`                                                                            | Contract           | Name, type, status, category, links and aliases per item                                      |
| `/r/<name>.json`                                                                           | Contract           | One registry item at the default base and style                                               |
| `/r/docs.json`                                                                             | Contract           | The offline bundle, versioned                                                                 |
| The HTML of any page                                                                       | **Not** a contract | Chrome, markup and class names change freely                                                  |
| Anchor ids                                                                                 | **Not** a contract | Headings are edited; link to a page, not to a fragment                                        |
| `/view/**`                                                                                 | **Not** a contract | Chrome-less preview host, and `robots.txt` disallows it                                       |

If you are scraping the HTML, stop; the markdown twin exists precisely so that
you do not have to, and it is stable in a way the HTML is not.

## Pick a surface [#pick-a-surface]

<Steps>
  ### For a coding agent inside an editor [#for-a-coding-agent-inside-an-editor]

  Install the [agent skill](./agent-skill.mdx), or configure the
  [MCP server](./mcp-server.mdx), or both. The skill carries the rules; the MCP
  server carries the inventory. They solve different halves of the same problem
  and are meant to be used together.

  ### For a retrieval pipeline [#for-a-retrieval-pipeline]

  Start at [`/llms.txt`](/llms.txt) for the index and fetch the `.md` twins of the
  pages it names. See [llms.txt and the shards](./llms-txt.mdx) and
  [Raw markdown API](./raw-markdown-api.mdx).

  ### For a build script or a validator [#for-a-build-script-or-a-validator]

  Use the [machine-readable schemas](./machine-readable-schemas.mdx): the
  catalogue, the token set, the status vocabulary and the glossary, each as JSON
  at a stable path.

  ### For an agent with no network [#for-an-agent-with-no-network]

  Fetch [the offline bundle](./offline-docs-bundle.mdx) once and carry it.
</Steps>

## The one rule that matters most [#the-one-rule-that-matters-most]

Every catalogue row is implemented and installable, and every one has been
audited against WCAG 2.2 AA by the authors who built it, in a static source pass
and a rendered pass, with the findings fixed in the same change. None has had an
independent accessibility review, and none has had a clinical review, so nothing
here is for a production health surface until a clinician signs it. That is why
an agent's first job is
to read the status a surface reports before it writes anything. The surface is
arranged so that it can, without guessing: every id resolves, and the answer
carries the distinction in a field.

For a `shipped` id the correct behaviour is to install it from the registry and
to say that the API may change in any release, that it has been audited against
WCAG 2.2 AA by the authors but has had no independent accessibility review and no
clinical review, and that it is therefore not for a production health surface
until a clinician signs it. The record is
[ADR 0025](../project/decisions/0025-the-audit-is-author-run.mdx). Installing copies the source into the
project, because there is no published opsinjs package to import. For a
`planned` id the correct behaviour is to describe the specification, refuse to
emit an import, and stop. No id sits there today, so that
rule currently guards the mechanism rather than a live entry. Inventing a name
that does not exist, or refusing one that ships, are the two failures this whole
surface exists to prevent. Both are stated in full, as testable rules, in
[Rules for agents](./rules-for-agents.mdx), and they are what the
[evals](./evals.mdx) measure.

<Callout>
  The discriminator is `implemented` on `/r/index.json`, `meta.opsinjs.implemented`
  on a registry item, and the `x-opsinjs-implemented-count` response header on
  every machine surface. Three redundant channels, because an agent that misses
  one will usually catch another.
</Callout>

## Verify it worked [#verify-it-worked]

<Steps>
  ### The twin returns markdown [#the-twin-returns-markdown]

  ```bash
  curl -s https://opsinjs.pensievelabs.org/components/range-bar.md | head -20
  ```

  You should get frontmatter and prose, not HTML. Documentation components arrive
  as `<PascalCase … />` tags and that is the format working; an `import` statement
  at the top means you have raw MDX from somewhere else.

  ### The index is reachable and absolute [#the-index-is-reachable-and-absolute]

  `/llms.txt` should contain fully qualified URLs. A relative URL in an index is
  useless to a client that fetched it from somewhere else.
</Steps>

## Troubleshooting [#troubleshooting]

**The `.md` twin returns HTML.** You requested a path that is not a docs page.
The rewrite applies to `/:path*.md` and to `/index.md`; other routes have no
twin.

**The MCP server sees no components.** That is a real fault. All sixty built
items list source files, and the per-item payloads at `/r/<name>.json` carry
that source inline, so every id the server lists resolves to real files and
there is no metadata-only entry to mistake for a fault. An inventory that is
empty across the board means something
is broken: check that `components.json` carries the `@opsinjs` entry in the
`{name}` template form and that the fetch is not being blocked.

**An agent invented a prop.** Report it as an eval failure rather than as a
documentation bug. An implemented component's API reference is generated from
the source the registry serves, so an invented prop is a generation failure and
not a gap in the page. See [Evals](./evals.mdx).

**`/llms-full.txt` is truncated.** It is size-capped on purpose, and "use the
shards" is only half an answer, because the four shards cover eight of the
sixteen sections and no more: Components, Screens, Health, Accessibility,
Content & language, Foundations, Theming & tokens and Reference. Start here,
Recipes, Patterns, Handbook, Agents, Registry & distribution and Project have no
shard, nor do the package pages, which sit outside the sixteen groups. That is
roughly a third of the corpus, and a shard is not its recovery route and never
was. Two things are. Any capped file that drops a page ends in a `## Truncated`
block naming every page it dropped, with the `.md` URL to fetch each one, so
what is missing is enumerated rather than implied. And `/llms.txt` is not
capped. It lists every page in the corpus, names the sections that have no
shard, and every URL in it answers to a `.md` suffix. Fetching one twin is a
single request and is usually cheaper than a shard you then have to search.

## Next [#next]

* [Rules for agents](./rules-for-agents.mdx) has the hard rules, written to be
  pasted into a system prompt.
* [MCP server](./mcp-server.mdx) is five minutes to a working setup.
* [Writing docs for agents](./writing-docs-for-agents.mdx) is the page to read
  if you are contributing pages rather than consuming them.
