Skip to content

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 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.

Example · run it

Run this in the copied package, and compare with the output below.

Terminal window
python3 release_check.py
Output
release 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:

Terminal window
mkdir -p .claude/skills/release

The 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: release
description: 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.

Checkpoint · predict

Predict the four lines the lint prints, one for the release skill and one per broken rule for bad-skill.

Terminal window
python3 skill_lint.py

Output verified in CI from site/examples/customizing-agents/first-skill/skill_lint.py.

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.

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 a
version bump, a changelog entry, passing tests, and a tag. Ask for the new
version if the user did not give one. Bump the last number for a fix, the
middle 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 is
on 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.

Checkpoint · order
  1. Run the tests, and stop if they fail
  2. Change the version line and write its changelog entry
  3. Run the release check and read its line
  4. Fix what a FAIL line names and run the check again
  5. Commit and tag

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.

Checkpoint · predict

Predict the two lines, one after the version bump and one after the changelog entry.

Terminal window
python3 failing_step.py

Output verified in CI from site/examples/customizing-agents/first-skill/failing_step.py.

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.

Checkpoint · multi-choice

Which lines let a colleague's agent run it?

Section titled “Which lines let a colleague's agent run it?”

Which three lines earn their place in the release skill?

Select exactly 3.

Checkpoint · choice

Three descriptions for the same release skill. Which one loads the skill for the right requests and leaves it unloaded for the rest?

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

  1. A skill is a directory named after the skill with a SKILL.md in it. Its front matter needs name and description, and name must equal the directory name [2].
  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.
  3. 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.
  4. 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.
  5. 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

  1. Addy Osmani, Ivar Soares Urdalen, Leo Simons. Agent skills: skills versus tools, the spec, progressive disclosure. Agent Engineer Course. Course. AEC-17
  2. Anthropic. Agent Skills specification. agentskills.io. Reference. Agent Skills spec
  3. Anthropic. Extend Claude with skills. Claude Code documentation. Reference. Claude Code skills
  4. Anthropic. Introduction to agent skills. Claude Academy. Course. Academy introduction-to-agent-skills