Writing your first skill
The last lesson’s rule was that a procedure the agent can already do, but does differently each time, belongs in a skill. This lesson writes one. The procedure is cutting a release of a small package, and the result is a directory with one file that any agent reading the open skill format can load and follow [1]. You build the file in three passes. The first pass gives it a name and the sentence that makes the agent pick it. The second writes the steps and the check that says the result is right. The third adds what to do when a step fails.
The lesson runs three short Python scripts against the fixture, and the course checks their output on every build. The agent comes in at the exercise, where you run the finished skill from a session as you did in the first coding lesson.
The fixture
Section titled “The fixture”The package is a notes command in one file, in the course repository under
site/examples/customizing-agents/first-skill/fixture-package/. Copy that
directory somewhere you can throw away, because the exercise has an agent
change files in it. Inside are notes.py, its tests in test_notes.py, a
CHANGELOG.md with three releases, and release_check.py. The check
reads the version out of notes.py, looks for a matching heading in the
changelog, runs the tests, and prints one line. That line is the
skill’s last step, so run it once now to see what “right” looks like.
Run the release check
Section titled “Run the release check”Run this in the copied package, and compare with the output below.
python3 release_check.pyrelease 0.3.0: ok
Output verified in CI from site/examples/customizing-agents/first-skill/check.py.
The package also holds the finished skill at
.claude/skills/release/SKILL.md. Read it at the end, and write your own
first. Delete it from your copy if you want the exercise to test your
version and not the one that ships with the fixture.
Pass one: the directory and the description
Section titled “Pass one: the directory and the description”A skill is a directory with a file named SKILL.md in it, and the
directory’s name is the skill’s name [2]. Claude Code
reads project skills from .claude/skills/<name>/SKILL.md and personal
ones from ~/.claude/skills/<name>/SKILL.md, and a project skill is
committed so the team’s agents share it [3]. Make the
directory in your copy of the package:
mkdir -p .claude/skills/releaseThe file starts with YAML front matter between two --- lines. The
specification requires two fields. name is at most 64 characters of
lowercase letters, digits and single hyphens, with no hyphen at the start
or the end, and it must equal the directory name. description is at
most 1024 characters and says what the skill does and when to use it
[2]. Every other field is optional.
The description does more work than its size suggests. At the start of a
session Claude Code loads the name and description of every skill it can
see. The body is loaded when a task matches the
description, or when you type /release yourself. The loaded text
remains in the conversation on later turns, until compaction trims it
[3]. So
the description is the trigger, and it should name the requests that
should load it [4]. Write this as the
whole file for now:
---name: releasedescription: Cut a release of the notes package. Use when asked to release, to bump the version, or to tag a new version.---The course keeps a small lint script next to the fixture that checks the
required fields against the rules above. It reads two skills: the release
skill in the fixture package, and bad-skill/, whose front matter is
name: Release-Notes and an empty description.
What does the lint report?
Section titled “What does the lint report?”A lint script checks two SKILL.md files against the Agent Skills specification: name at most 64 lowercase letters, digits and single hyphens and equal to the directory name, and a non-empty description of at most 1024 characters. The first skill is a release skill in a directory named release. The second is in a directory named bad-skill, with name Release-Notes and an empty description.
Predict the four lines the lint prints, one for the release skill and one
per broken rule for bad-skill.
python3 skill_lint.pyrelease: ok, description is 108 characters bad-skill: name has uppercase letters bad-skill: name does not match the directory 'bad-skill' bad-skill: description is missing or empty
Output verified in CI from site/examples/customizing-agents/first-skill/skill_lint.py.
Check the name against each rule in turn, then the description. How many rules does Release-Notes in a directory called bad-skill break?
The description above is 108 characters, far under the limit, and it should stay short. Every skill’s description is in context on every turn of every session, whatever the task. Claude Code shortens a long description in the listing it shows the model [3], so the end of a long one may never be read, and a description that long holds steps that belong in the body.
Pass two: when, the steps, the check
Section titled “Pass two: when, the steps, the check”The body is Markdown, and the specification doesn’t restrict it beyond a recommendation to keep the whole file under 500 lines and move detailed reference material to files next to it [2]. What the body has to do is let an agent that has none of your context run the procedure without asking. That takes an opening line on when to use it, the steps in order with the exact commands and file names, and the check that says the result is right.
Add this below the front matter:
# Release the notes package
Use this when the user asks for a release of this package. A release is aversion bump, a changelog entry, passing tests, and a tag. Ask for the newversion if the user did not give one. Bump the last number for a fix, themiddle one for a feature.
## Steps
1. Run `python3 -m unittest -q`. The last line must be `OK`.2. In `notes.py`, change the `__version__` line to the new version.3. In `CHANGELOG.md`, add a `## <version>` heading under the title, above the previous release, with one bullet per user-visible change since that release. Read `git log` for the list. Don't invent changes.4. Run `python3 release_check.py`. It must print `release <version>: ok`.5. Commit with the message `release: <version>` and tag it `v<version>` with `git tag v<version>`.
## Done when
`python3 release_check.py` prints `release <version>: ok`, the commit ison the branch and `git tag --list 'v*'` shows the new tag. Don't push.Report the version, the tag and the changelog entry you wrote.The commands and file names in that body are exact. The step says
python3 -m unittest -q, because an agent on another machine that reads
“run the tests” picks the runner it guesses. The last step is a check the
agent can run, with the exact line it must print, so “done” is a line the
agent reads. The “Done when” section repeats that check and adds
the limit, “Don’t push”, because a release skill that pushes is a
release skill that can’t be undone with git reset.
Order the release steps
Section titled “Order the release steps”A release skill runs the tests before it changes anything, then edits the version and the changelog, then runs a release check that prints ok or a FAIL line naming a problem, and commits and tags only once the check prints ok.
- Run the tests, and stop if they fail
- Change the version line and write its changelog entry
- Run the release check and read its line
- Fix what a FAIL line names and run the check again
- Commit and tag
Which step must come before any file changes, which step needs the changes in place to have anything to check, which one needs the check's line, and which one needs the check to print ok?
Pass three: when a step fails
Section titled “Pass three: when a step fails”The body so far assumes every step works. The first time an agent runs
it, one won’t. The tests fail because of a change someone left half
done, or the check prints a FAIL line because the agent bumped the
version and forgot the changelog. A skill that says nothing about that
leaves the agent to decide, and the decisions agents make at that moment
include “edit the check until it passes”.
The fixture shows what the check prints after such a half-done release. The script copies the package, changes the version to 0.4.0 as step 2 says, runs the check, then adds the changelog heading from step 3 and runs it again.
What does the check print at each point?
Section titled “What does the check print at each point?”The fixture package's release check reads the version from notes.py, looks for a matching '## version' heading in CHANGELOG.md and runs the tests, then prints 'release <version>: ok' or one 'release <version>: FAIL: <problem>' line per problem. A script bumps the version to 0.4.0, runs the check, adds the changelog heading and runs it again.
Predict the two lines, one after the version bump and one after the changelog entry.
python3 failing_step.pyafter step 2: release 0.4.0: FAIL: CHANGELOG.md has no '## 0.4.0' heading after step 3: release 0.4.0: ok
Output verified in CI from site/examples/customizing-agents/first-skill/failing_step.py.
After the bump, which of the three things the check looks at is out of step with the others? What does the check print when nothing is?
The check names the missing step, so the skill’s job is to tell the agent to read the line and fix what it names. Change steps 1 and 4 to:
1. Run `python3 -m unittest -q`. The last line must be `OK`. If it is not, stop and report the failure. Never release with a failing test....4. Run `python3 release_check.py`. It must print `release <version>: ok`. If it prints a `FAIL` line, fix what the line names and run it again. Don't edit `release_check.py`.Each failure line has the same three parts. What the agent sees, what it
does about it, and the one thing it must not do. The third part is the
one authors skip, and it is the one that stops the agent from making the
check pass by changing the check. Your SKILL.md now matches the
finished one in the fixture, line for line.
Which lines let a colleague's agent run it?
Section titled “Which lines let a colleague's agent run it?”A team writes a release skill for a coding agent. The question asks which lines in it let a colleague's agent, with none of the author's context, run the release without asking.
Which three lines earn their place in the release skill?
For each line, ask: does an agent with none of the author's context need it to run the procedure without asking?
Which description is the right trigger?
Section titled “Which description is the right trigger?”A skill's name and description are what the agent sees before deciding whether to load the body, and the description should name the requests that should load the skill and no others.
Three descriptions for the same release skill. Which one loads the skill for the right requests and leaves it unloaded for the rest?
Which requests should load a release skill, and which of these descriptions names those and only those?
Where does the step list go?
Section titled “Where does the step list go?”A skill in the open skill format has a front matter description that the agent reads on every turn to decide whether to load the body, and a body with the procedure that is loaded only once a task matches.
A colleague writes a release skill and asks where the numbered steps belong. What do you tell them?
Which part of the file does the agent read before it has decided to use the skill, and which part after?
Does the line pass the spec?
Section titled “Does the line pass the spec?”A skill sits in a directory named release-notes, with a SKILL.md file inside. The Agent Skills specification asks for a name of lowercase letters, digits and single hyphens that equals the directory name, and a description that isn't empty.
Check the case, the characters, the directory name and whether the description is empty.
The step that runs the tests
Section titled “The step that runs the tests”A release skill's first step runs the package's tests.
Which two lines belong in that step?
What does the agent see when the step fails, what does it do, and what must it not do?
How the skill ends
Section titled “How the skill ends”A release skill for a small package is being written, and its last lines are still open.
Which ending lets the agent know that the release is right?
Which ending gives the agent a line to compare against?
The skill never loads
Section titled “The skill never loads”A team's code review skill has the description 'Code review helper.' The agent reads the name and description on every turn, and it loads the body only when a task matches the description.
Colleagues ask the agent to “review my pull request”, and the agent doesn’t load the review skill. What do you change?
What does the agent read before it decides to load the body?
Exercise
In a fresh copy of the fixture package, with your SKILL.md in place
and the one that ships with the fixture deleted, start your coding agent
in the package directory and ask it to cut release 0.4.0. Run the skill
by typing /release 0.4.0 if your agent supports that, or ask in
plain words and watch whether the description fires. The skill changes
files: it edits notes.py and CHANGELOG.md and makes a commit and a
tag. Run it only in the copy, and never in the course repository.
If the copy isn’t a git repository, run git init and commit the
package first, so steps 3 and 5 have something to work with.
A good result: the agent ran the tests first, python3 release_check.py
prints release 0.4.0: ok, git tag --list 'v*' shows v0.4.0, and
the agent didn’t push and didn’t edit release_check.py. Where did the
agent hesitate or ask you something? That question is a line missing
from the skill, and the next lesson is about finding those lines.
Stretch: Change one bullet in the skill's changelog step so the entry has a fixed first line, for example the date, and run the skill again in a fresh session on a fresh copy. Did the agent follow the new line without being told about it?
Recap
- A skill is a directory named after the skill with a
SKILL.mdin it. Its front matter needsnameanddescription, andnamemust equal the directory name [2]. - The description is the trigger. It is in context on every turn, and the body is loaded when a task matches it or you invoke the skill, and remains on later turns until compaction [3]. Name the requests that should load it and no others.
- The body says when to use the skill, gives the steps in order with exact commands and file names, and ends with a check the agent can run, with the line that means the result is right.
- Each step that can fail says what the agent sees, what it does about it, and what it must not do. The agent then knows what to do at the first surprise.
- A skill is done when someone else’s agent runs it from a fresh session without asking. Running it that way is the next lesson.
You can now
- Packages a repeatable procedure as a skill another person's agent can use
References
Section titled “References”- Addy Osmani, Ivar Soares Urdalen, Leo Simons. Agent skills: skills versus tools, the spec, progressive disclosure. Agent Engineer Course. Course.
AEC-17 - Anthropic. Agent Skills specification. agentskills.io. Reference.
Agent Skills spec - Anthropic. Extend Claude with skills. Claude Code documentation. Reference.
Claude Code skills - Anthropic. Introduction to agent skills. Claude Academy. Course.
Academy introduction-to-agent-skills