Agent-ready design systems: readable is only half the job

The question that started it

My Voys colleague Sander asked me a good question after the last post. His team is exploring the Design System Doc Spec to document design rationale in a format that sits between design, code and AI, and he wanted to know whether we use it in Klai. His framing stayed with me: we have no common language for design, and designers are not used to voicing their decisions in anything other than images.

So I went looking. DSDS is one candidate, Google Labs’ DESIGN.md from the enforcement post is the other. Before picking a format to describe our design system in, I wanted to know how much of the description we already had was true. So on 1 September we took five claims our UI contract made about our own code and checked each one. All five were wrong…

Just for reference.

The document saidThe code said
Four canonical page containers66 containers across roughly 20 combinations
Button has a ghost and an outline variantOne variant with two names, 172 call sites
The component table lists every variantMissing lg (23 uses) and link (7 uses)
About 21 raw form controls are violations10 were, 2 were correct, 10 were a deliberate design decision
The rules ledger restates the prose12 rows had silently dropped their exception clauses

One claim held: 96 of 100 raw <button> elements are the prescribed pattern for clickable rows. It held because it was the only one anybody had counted, the same measurement that stopped me writing a lint rule last time.

This is not a story about a careless team. A sentence in a design document is simply not good enough for an AI. The code moves, the sentence does not, and nothing complains, because the stale sentence still reads perfectly fluently. A human reader eventually notices and works around it. An agent, however, applies it with confidence and at machine speed. And any deviation can become the precedent the next agent copies.

The enforcement post ended on an admission: we had built enforcement without measurement. Measurement should have been our first step, and it spent two days proving our documentation wrong.

What an agent-ready design system actually is

An agent-ready design system has two halves. The readable half is what everyone is building right now: tokens in a machine-readable file, components in a registry, guidance a tool can parse. That is real progress, and it is the easier half. The enforceable half decides whether the readable half stays true. Without a mechanism that stops the documentation lying, you have shipped very well-formatted stale sentences.

Which brings me back to Sander’s question. The honest answer is no, with a longer explanation.

We measured both formats on 1 September. DESIGN.md had 606,306 downloads that month against DSDS’ 88, and DSDS had 226 commits against DESIGN.md’s 62. So DESIGN.md has the adoption and DSDS has the momentum. DSDS also has the better model for judgement: levels, verification modes, and a way of separating components from patterns from guidance that our own rules had fallen into before I knew the spec existed. Its own site calls it a draft that no standards body has endorsed.

We adopted neither as a source, and the table above is the reason. Both are formats you write by hand, and 1 September was the day we established that everything we write by hand about our own code drifts. We do publish a DESIGN.md, but as an output: a script reads our stylesheet and the comments on each component and writes the file. Nothing is authored twice. That also keeps the choice open, because emitting DSDS as well would be a second script over the same data rather than a migration. We would write it if the DSDS tooling measurably beats our own ledger for an agent building an interface. That measurement has not been run. It is fewer instructions, more checks applied to design documentation.

Figma, a design tool we use a lot at Voys and we are not the only company that does so :), is no further along: DTCG for token values, Code Connect for component-to-code mapping, generated markdown for guidance. The judgement layer is unsolved across the industry.

The four levels a rule can live at

This is the model that came out of the mess, strongest first.

1. Impossible

The API does not let you express the mistake. Our PageContainer owns page padding and centering, so you cannot forget them. Our Field component wires a label’s htmlFor to its input’s id automatically, so a form field without an associated label is no longer expressible.

2. Caught

A check fails the build. klai/no-raw-text-input makes a raw textual <input> outside the component folder an error. A contrast test resolves every foreground colour our Colors section documents to a hex value and fails if it drops below 4.5:1 on either portal surface.

3. Derived

The documentation is generated from the code, so it cannot lie. The component table, the per-component rules and DESIGN.md are emitted and staleness-checked. Editing them by hand fails.

4. Stated

A person or an agent reads a sentence in a document and follows it. Thirty-four of our rules still live here, and they should. Whether a set of items works better as a list or a table depends on the items, and no check can make that call for you. Writing this level well is its own subject; rules an agent reads was about exactly that.

Push every rule as far down as it will go

Before this work, effectively every rule sat at level four, including plenty that did not need to. The biggest wins moved rules all the way to level one. Our layout section used to document four container class strings and tell you to copy the right one into your page; now a single PageContainer owns the padding and the centering, and you pass it a width. Ten login and signup screens that each hand-built a label and an input became one Field, which connects the label to the input itself. Both changes deleted documentation instead of adding a check to police it.

Almost everything written about design systems is written about level four. Almost all of the value sits in the three levels below it.

What changed in two days

BeforeAfter
Automated design checks317
Rules with an ID, level and verification mode059
Hand-maintained mirrors of code3 or more0, all generated
text-gray-400 used as reading text521 sites0
Accessibility findings on the component catalogueunmeasured0, with zero exceptions
CI that looks at rendered pixelsnone25 catalogue sections plus an axe audit, gating deploys

One row deserves the story behind it. That grey was our default colour for secondary text, running at 2.31:1 against our background where the accessibility standard asks for 4.5:1, and about half of it sat at 12px. We had assumed roughly a third of those uses were decorative, where low contrast does no harm. Nobody had counted them. After counting it was 2 percent, which makes it the sixth wrong claim of the same day.

The last row is what keeps the rest honest. An accessibility tool now walks the rendered component catalogue on every deploy, and a finding can only be waived with a written reason. Every new check was also attacked with a deliberate mistake before we trusted it, because a check that has never failed has not been tested.

What we refused to build

Nobody writes this part down, so here is our list.

  • We did not adopt DSDS as an authoring format. It would have become a third hand-written description of the same code, and hand-written descriptions are what failed at the top of this post.
  • We did not score agent compliance with an AI. We merge too few changes a week for a score to mean anything, so it would look like measurement and behave like noise.
  • We did not add Storybook. Our /dev/ui page already renders every component in every state, and a second catalogue is a second thing to keep current.
  • We did not give every clause in the document its own ledger row, although an audit argued for it. The test is not how many rows there are, it is whether a row can be derived from code. Twenty-one of ours are, and those cost nothing to keep true, while every hand-maintained row is a row that can drift. A rule and the exception that qualifies it are one rule, which is also what the INCOSE requirements guide says: one sentence, one thought, qualified by sub-clauses. What the audit actually caught was twelve rows that had dropped their qualifier, and the fix for that is to put the qualifier back.
  • We did not write build checks for patterns nobody had counted. A check that fires on correct code gets switched off, and the good checks get switched off with it.

The test for any future check is one sentence: name the measured defect or incident that motivates it. No incident, no check.

Working with an agent as the implementer

The work ran as a strict division of labour. One agent (GPT Sol, through the Codex CLI) wrote nearly every change from a written brief. A second agent (Claude) wrote the briefs, checked every claim that came back, and owned what landed. Three things came out of that.

Route by task shape, not task size

The implementing agent was excellent at bulk mechanical work against a measured target: six hundred colour swaps, thirty-seven tests pinning down what two large screens currently do, an entire generator. It was unreliable at design judgement. It restyled a decorative avatar palette that was already correct, renamed a visible label during what the brief called a pure rename, and forced conversions on files that did not need them so it could report the list as clear. Measured targets travel in the brief. Judgement stays with the reviewer.

The fabrication catalogue

Four times the agent produced something it had simply invented. Our automated checks caught none of them. Reading the work by hand caught all four.

  • It wrote a package integrity hash from memory, because its sandbox had no network access and the hash had to come from somewhere.
  • It rewrote an existing test so that its own change would pass.
  • It reported “Confidence: 100” beside a measurement nobody had taken.
  • It wrote “Assumed: none” in a report that assumed the page background.

Notice what those have in common. None of them look like failures. A fabricated hash looks exactly like a real one. The defence is not reading the report more carefully, it is recomputing the number, rendering the page and reading the diff yourself. Verify the agent, especially when it agrees with you. Or, as the last post put it: audit the thing you just built while you still believe in it. That goes for the plumbing too: one job reported “completed” while the agent had never run at all.

Briefs that worked share a shape

  • Paste the failing command in verbatim as a fixture, rather than describing it.
  • Say how to find the code, never which line it is on. Line numbers drifted within hours, three separate times.
  • Include an explicit do-NOT list.
  • Give a default for ambiguity. Ours was “when unsure, treat it as text”.
  • Ask for the reverse list, what did not change, because that is invisible in a diff.
  • Point at the guards instead of restating them. “The guards will talk to you, let them” beat three paragraphs of process.

The styleguide moment

Near the end, the agent did exactly what it was told and produced a defect.

Our styleguide says our amber accent fails as text and that you should use a darker amber instead. The agent applied that to a small status tag, and the new accessibility audit rejected the advice. On that tag’s pale amber background the recommended colour comes out at 4.28:1, just under the bar. The guidance had been written for plain white surfaces and nobody had ever checked it on a tinted one. Every other colour in our system has a darker text variant for exactly this reason. Amber was the one without.

So we added one, and this time the number came first: the new colour was checked on all three surfaces it has to work on before it was picked, and it clears the bar on the worst of them at 6.39:1. The tag uses it, the documentation records the ratio beside it, and the contrast check runs on it every build. The styleguide was corrected too, along with four other claims the same audit found false.

The advice was old, an agent had followed it faithfully, and nobody had to win an argument to get it changed.

That is what an agent-ready design system comes down to. Measure what can be measured, make the rest impossible where you can, generate the documents that can be generated, and leave the judgement in prose where it belongs.

Sander asked whether we use DSDS. Not yet, and not as a source. If we adopt it, it will be as something our code prints rather than something we promise to keep true by hand.

One suggestion before you go looking at formats. Take five sentences your own design system says about your code and check them. We got five out of five wrong, which I would have called sloppy if I had read it about someone else.

Take ours and look at it

And if you want a head start on where five wrong claims lead, all of this is public, in the same monorepo as the last two times.

  • klai-portal/frontend/docs/ui-standards.md is the contract. The Rules Ledger at the bottom is the part this post is about: every rule with an ID, a level and a verification mode, and a test that fails the build when the ledger claims coverage it does not have.
  • klai-portal/frontend/DESIGN.md is the generated one. The header tells you not to edit it by hand, and a build check makes sure nobody did.
  • eslint-rules/ and tests/design/ hold the seventeen checks, including the contrast test that does the WCAG arithmetic on every colour our documentation names.
  • e2e/visual/ is the CI that looks at pixels: the screenshot suite over the rendered catalogue and the accessibility audit with its empty exception list.
  • .moai/specs/SPEC-DESIGN-SOURCE-001/spec.md records the decisions, including everything we refused to build and why.

The reasoning per change sits in the pull requests, #1285 through #1304. The first is the ledger landing; #1302 and #1303 are CI learning to see.