DEV Community

Jonas Gauffin
Jonas Gauffin

Posted on

Skills, not docs

Second in a series on using @relax.js/core with a coding agent. The first piece made the argument; this one is about the first thing you do in a project.

Install the rules, not the manual

npx @relax.js/core init-agents
Enter fullscreen mode Exit fullscreen mode

That writes seven files into .claude/skills/, one per area: the core model, then templates, forms, routing, services, testing and setup. Claude Code loads a skill when its description matches what the agent is doing. Other tools without skill support can be pointed at node_modules/@relax.js/core/skills/relaxjs/SKILL.md from their instruction file; the core skill links to the rest.

Each copy is stamped with the package version it came from. Run the command again after an upgrade and it lists the copies that are behind, and leaves them alone unless you pass --force. A skill is a snapshot, and a snapshot that describes an older library is worse than none, because the agent trusts it.

The border

The interesting decision was not to write the skills. It was deciding what does not go in them.

The library has a docs/ folder like any other. An agent could read it, and sometimes it should. But documentation is written for someone who already knows they need this API. It answers "how does this work, what is available".

A skill loads before the agent knows it has a problem. Its job is to overwrite a wrong default and route to the right doc. The skills/README.md in the package states two tests for where a sentence belongs:

Would an agent that never read this produce code that compiles, type-checks and does nothing? Skill. Would it merely not know a name? Docs.

Would the sentence need editing when the implementation changes? Docs. Only when the design changes? Skill.

The two must not overlap. A skill that accumulates examples is turning into a doc, and should hand off to one instead. The only thing a skill repeats from a doc is its filename.

What that looks like

Here is the whole "Do not" section of the core skill:

## Do not

- Reach for a state store, computed properties or a reactive wrapper. Update the DOM where the
  change happens.
- Add a component base class, a render loop or a diffing layer.
- Use `CustomEvent`, or `enum` where a `declare type` string union works.
- Duplicate native HTML. Use `<dialog>`, `<details>`, `<input type="date">` and friends before
  writing a component.
- Swallow errors. An empty `catch` is a bug.
Enter fullscreen mode Exit fullscreen mode

Every line is a habit. None of them is a fact about an API. An agent that never reads this will write a store, a base class and a CustomEvent, and all three will compile.

Compare the forms skill, which opens with the one thing agents get wrong most:

## FormValidator owns the submit event

Its constructor attaches the listener. Do not add your own, and construct one even when you have
no validation rules, because taking over submit is what it is for. Supplying `submitCallback`
suppresses the native submit, so the page never navigates away.
Enter fullscreen mode Exit fullscreen mode

Then a diagnosis, because skills are also loaded when something is already broken:

A form that still navigates away on submit means no `FormValidator` was constructed for it.
Enter fullscreen mode Exit fullscreen mode

And at the bottom, the hand-off:

## Detail

- `@relax.js/core/docs/forms/form-page.md` for the end-to-end shape of an edit page. Start here
- `@relax.js/core/docs/forms/validation.md` for rules, the error summary and every option
Enter fullscreen mode Exit fullscreen mode

init-agents rewrites those @relax.js/core/docs/ references to the real path of the installed package, so the agent can follow them without knowing where node_modules is.

Why the border holds up

I tried the other shape first: one big instruction file with everything in it. It rotted in two ways.

First, every sentence that described how something worked went stale when that thing changed, and there was no signal which sentences. Splitting on "does this change when the implementation changes, or only when the design changes" is exactly the signal: the docs get updated with the code, the skills get updated with the design, and the design changes rarely.

Second, a long file is context spent. A skill that is loaded on every UI task and carries an example of every option costs the same as the code the agent is supposed to be writing. Short skills that route to long docs let the agent spend its context on the problem, and pull the reference in only for the part it is actually touching.

The other consumer

I said in the first piece that greppability matters because agents navigate by search. The skills lean on that. When a skill says "see docs/forms/form-page.md", the agent opens the file. When it says "FormValidator.FindForm(this)", the agent greps it and lands in the source. A skill never describes a mechanism the agent cannot then find by name.

That is also the test I used when writing one. Pick any identifier in the skill and search the package for it. If the search lands on the thing being described, the sentence belongs. If it only lands back in the skill, the sentence is prose about a convention, and conventions are what agents guess at.

Next: the model itself, and why it fits on one page.

Top comments (0)