Writing skills that get used

Principles for authoring a skill people actually install: narrow scope, a precise description, clear steps, explicit inputs and failure handling.

Contents

Most skills that go unused aren’t broken. They lint clean, they run, and nobody installs them a second time. The gap is usually authoring, not engineering: the skill tries to do too much, the description doesn’t say what it’s for, or the body assumes context the agent doesn’t have. This post covers the principles that separate a skill someone reaches for again from one they forget exists.

Pick one job and do it well

A skill is not a toolkit. The temptation is to fold every related capability into a single SKILL.md: extract text, but also summarize it, also translate it, also flag PII while you’re at it. Resist this. Each capability you add dilutes the description, complicates the steps, and multiplies the ways the skill can fail partway through.

Narrow scope has a second benefit beyond clarity: it makes the skill composable. An agent that needs to extract text and then summarize it can chain two focused skills far more reliably than it can steer one skill that tries to do both and exposes a dozen options for which parts to run. If you find yourself writing “optionally” more than once in your instructions, that’s a sign the skill wants to be two skills.

Ask yourself what a user would say the skill does, in one clause, without an “and.” If you can’t answer that, the scope isn’t narrow enough yet.

Write a description that does the selecting for you

The description is not documentation. It’s the signal an agent uses to decide, before it ever opens the body, whether this skill is the right tool for the task in front of it. Treat every word as load-bearing.

A weak description states the category:

description: Handles PDF files.

A precise one states the operation, the tool, and the boundary:

description: Extracts text and tables from PDF files using pdfplumber, with page-range and layout options. Does not handle scanned or image-only PDFs.

The second version lets an agent rule the skill in or out correctly on the first pass. It also tells a human skimming a list of ten PDF-related skills which one to pick. A description under twenty characters gets flagged by skillmd lint for a reason: something that short cannot carry enough signal to be useful. Aim well past the minimum. Name the mechanism, name the format, and name what’s explicitly out of scope if that boundary matters.

Give steps, not vibes

Once an agent has loaded the body, it needs instructions it can execute, not a paragraph explaining the general idea. Compare:

## Usage
This skill helps convert data between formats as needed.

against:

## Steps
1. Read the input file and detect its format from the extension.
2. Parse it into an intermediate structure using the matching library.
3. Serialize the intermediate structure to the target format.
4. Write the output to the path the caller specified, or alongside the input if none was given.

The second version is a script an agent can follow under pressure, mid-task, without guessing what step three means. Use numbered lists for sequences and short prose only to explain why a step exists when that reasoning isn’t obvious. Headings matter here too: a body with ## Steps or ### Inputs gives an agent something to anchor on and skip to, rather than forcing it to read every sentence to find the part it needs.

State inputs and expected output explicitly

Every skill takes something in and produces something out, even if that’s implicit in your head as you write it. Make it explicit on the page:

### Input
A path to a PDF file, and optionally a page range like "3-7".

### Output
A JSON object with `text` (string) and `tables` (array of row arrays), one entry per page in range.

This does two things. It lets the agent validate what it’s about to pass in before running the skill, and it lets the caller know exactly what shape to expect back without executing anything. Skills that leave this implicit force a round trip of trial and error before anyone trusts the result.

Handle failure on purpose

A skill that only describes the happy path will get used once and abandoned the first time it hits a PDF with no extractable text, a malformed input, or a permission error. Decide up front what the skill should do when things go wrong, and write it down:

### Failure handling
- If the PDF has no extractable text (likely scanned), say so explicitly rather than returning an empty result.
- If a requested page is out of range, skip it and note which pages were skipped.
- If the file can't be opened, stop and report the error rather than guessing.

None of this is exotic. It’s the difference between a skill that fails loudly and clearly and one that fails silently and erodes trust. An agent that gets a clean error message can recover or ask for help. An agent that gets an empty result with no explanation has no way to tell success from failure, and neither does the person reading the output.

Keep it readable

A SKILL.md is read by an agent, but it’s also read by every person deciding whether to trust and install it. Short sentences, active voice, and a body organized under a couple of clear headings serve both audiences at once. Avoid stacking qualifiers and edge cases into the main steps; put them under their own heading so the primary path stays easy to follow.

Run skillmd lint before you publish. It won’t tell you whether your scope is narrow or your description is precise, but it will catch the structural issues, missing headings, a body that’s too thin, a description that’s too short, that quietly drag down the quality score regardless of how good your writing is. Lint is necessary. It isn’t sufficient. The judgment calls above are what turn a skill that merely passes into one that people keep coming back to.