Chapter 3 of 7 · 9 min read
One skill covering five topics wins the wrong requests and loses the right ones
On this page
PR #503 deletes five directories and adds one. The five are component-conventions (32 lines), state-management (54 lines), test-structure (41 lines), a11y-rules (77 lines, a11y being the usual contraction of accessibility) and design-tokens (28 lines), each written by whoever hit the problem first, over about eight months. The replacement is frontend, 232 lines, with the five old bodies pasted in under five headings and a short table of contents at the top.
The argument in the PR description is not silly. The five files had drifted: component-conventions still describes the styled-components setup the team migrated off in April, test-structure and a11y-rules both explain how to query by role and they explain it differently, and nobody could say who owned any of them. One file has one owner, one review, one place to look. The author has also noticed that three of the five skills fire rarely and assumes a bigger, more visible skill will fire more.
It fires more. Over the six weeks after the merge it fired on 41 of 60 logged frontend-adjacent requests, against 30 for the five skills combined. It was also correct less often in both directions, and the fixture from the previous chapter is what makes that visible.
What a description covering five topics has to say
The description on the merged skill has to reach all five bodies, because a request that needs the design-token rules has no other way in.
---
name: frontend
description: >-
Frontend development standards for the web app: component conventions,
state management, test structure, accessibility requirements, and the
design-token system.
---Nothing is wrong with any clause. The problem is what the entry looks like as a document being matched against a request. When somebody asks "the modal doesn't trap focus when you tab past the close button", the part of this description that matches is three words out of thirty-one, and those three words are competing against wcag-audit, a skill the platform team ships whose entire description is about focus order, roles, labels and keyboard traps. The deleted a11y-rules matched that request with most of its text. The merged skill matches it with a sixth of its text, and the loser of a comparison does not get a second look.
The same breadth that loses narrow requests wins requests that belong to nobody. "Can you change the copy on the empty state to say 'No invoices yet'" is a frontend request in the ordinary English sense and matches "frontend development standards" comfortably, so frontend fires, loads 232 lines, and applies none of them. With five skills, that request matched component-conventions weakly and design-tokens weakly and nothing fired, which was the right outcome.
| Logged request | Five-skill library | Merged library | What was needed |
|---|---|---|---|
| "modal doesn't trap focus past the close button" | a11y-rules | wcag-audit | the team's a11y rules |
| "should this filter state live in the URL or in a store" | state-management | frontend | state-management section |
"add a --surface-raised colour for the new card" | design-tokens | frontend | design-token section |
| "change the empty-state copy to 'No invoices yet'" | nothing | frontend | nothing |
"why is the jest transform failing on .svg" | nothing | frontend | nothing |
| "our snapshot tests are all failing after the React 19 bump" | test-structure | jest-config (platform) | test-structure section |
Across the 60-request fixture, 44 requests genuinely belonged to one of the five topics. The five-skill library selected correctly on 39 of them and fired on 2 requests that belonged to nothing. The merged skill selected correctly on 27, lost 9 to more specific skills owned by other teams, and fired on 11 requests that belonged to nothing. Every false fire — a selection on a request the skill has nothing useful to say about — loads all 232 lines, roughly 3,000 tokens by the same 13-tokens-per-line estimate used earlier, when the median correct request needed one section of about 40 lines.
The author's prediction was correct on its own terms, which is what makes the merge tempting. A broader description does fire more often, and if you only count fires the merge looks like a success — 41 against 30 is a 37% increase in a number somebody will put in a retro. The count is made of the wrong events. Eleven of the 41 are loads that should not have happened, and the nine requests lost to wcag-audit and jest-config are not visible in the count at all, because a skill that loses a comparison leaves no trace in the transcript. Over six weeks the merged library spent roughly 123,000 tokens on frontend loads, against 24,000 for the five skills, and delivered twelve fewer correct selections for it.
Two instructions that never met now load together
Selection is the first cost and the more visible one. The second appears inside the body, and it does not exist at all while the skills are separate.
test-structure was written in November and says to colocate tests beside the component as Foo.test.tsx. component-conventions was written in March by somebody else and says that a component directory contains exactly index.tsx, styles.ts and stories.tsx. Neither author was wrong on the day they wrote it, and the two files never appeared in the same context, so the contradiction had no way to surface. Merged, both sections load on every invocation, and the agent has to reconcile them.
What it produced, on the first request to add tests for a new component, was __tests__/Card.test.tsx — a third arrangement that neither skill describes and that the repo does not use anywhere else. That is a reasonable thing for a model to do when two loaded instructions conflict, and it is very hard to attribute after the fact, because the output does not resemble either rule closely enough to point at one.
Attribution is the deeper loss. With five skills, an output that violates the token rules is evidence about design-tokens: it fired or it did not, and the fixture row tells you which. With one skill, the fixture can only measure whether frontend fired, and frontend fires on almost everything, so the measurement stops discriminating. You can no longer run the experiment of changing one description and watching one number move, because there is one description and one number and it is already near its ceiling. A tight boundary is what makes the skill testable, and the merge deleted the test more thoroughly than it deleted the files.
Deciding where a boundary goes
Two checks are enough to catch the merged case before it ships, and both operate on requests rather than on the body.
The first is to write ten requests the skill must win and ten it must lose, in the vocabulary of the log rather than the vocabulary of the topic. Then look at whether the ten winners share vocabulary. For frontend they do not: the focus-trap requests and the design-token requests and the state-in-URL requests have almost no words in common, and a description that matches all three has to be generic enough to match a fourth thing you did not intend. When the positive set splits into groups with disjoint vocabulary, the number of groups is the number of skills.
The second is to try stating the boundary in one sentence without joining unrelated nouns with "and". "Use when adding or changing a colour, spacing, radius or typography value in the design system" passes. "Frontend development standards: component conventions, state management, test structure, accessibility, and the design-token system" is a list wearing a sentence's clothes, and the list is the tell.
Both checks are cheap enough to run on a proposal rather than on a shipped skill, which matters because the merge is easiest to reverse before the bodies have been edited together. They also tell you where to cut when the answer is more than one skill. Grouping the twenty requests by shared vocabulary is a sorting exercise anyone on the team can do in twenty minutes, and the groups it produces are the descriptions: the words the requests in a group have in common are, almost literally, the first draft of the clause that will match them.
The concern that motivated the merge survives both checks and needs its own answer. Drift between five files is real, and the fix for shared content is a shared file rather than a shared description. The React 19 query conventions that test-structure and a11y-rules both explained belong in one references/queries.md that both skills read on demand, using the deferral mechanism from topic 12.3; ownership is a CODEOWNERS line — an entry in the file that maps repository paths to the people who must review changes under them — not a directory count. Neither of those requires the five descriptions to become one.
Narrow is about the boundary, not the byte count
a11y-rules is 77 lines, the longest of the five, and it is the narrowest in scope. Those two properties are independent, and conflating them produces the opposite mistake, which is a library of thirty fifteen-line skills whose descriptions each carve out a sliver.
That failure is real and worth naming, because it is what happens to a team that reads the argument above and over-applies it. Thirty skills means thirty descriptions resting in context on every request, which topic 12.3 priced; it means boundary arguments in review over whether a change belongs in button-variants or component-conventions; and it means requests that genuinely span two slivers, where the agent selects one, does the half it covers, and stops. Splitting design-tokens into colour-tokens and spacing-tokens buys nothing, because no request arrives that needs one and would be harmed by the other.
The line between the two is whether the split follows a boundary that requests already respect. Requests about focus traps and requests about colour tokens are different requests, phrased differently, arriving from different situations, and the five-way split follows a seam that exists in the traffic. Requests about colour and requests about spacing are the same request with a different noun, and splitting there invents a seam.
One frontend skill | Five topic skills | Thirty sliver skills | |
|---|---|---|---|
| Resting description tokens | ~35 | ~180 | ~900 |
| Correct selections (of 44) | 27 | 39 | 31 |
| False fires (of 16) | 11 | 2 | 3 |
| Tokens loaded on a correct fire | ~3,000 | ~400–1,000 | ~200 |
| Can you attribute a bad output | no | yes | yes |
| Requests needing two skills | 0 | 2 | 9 |
The last row is the one that does not improve monotonically, and it is where the argument for narrowness runs out. Two of the sixty requests needed both test-structure and a11y-rules — testing a component's keyboard behaviour draws on both — and no redrawing of the boundary between those two skills removes that, because the request genuinely sits on the line. At thirty skills the same problem appears nine times.
That is a different failure from the one in this chapter. Here the boundary was arbitrary, drawn around a team's directory rather than around anything in the traffic, and moving it fixes the problem. When two skills are each correctly scoped and a real request needs both, there is no boundary to move, and the next chapter is about what you do then.
Writing a skill — read the current version at https://daviddominguez.dev/learn/en/writing-a-skill/one-skill-covering-five-topics-wins-the-wrong-requests-and-loses-the-r