# Writing your first skill

> Build a release skill for a small package step by step, in the open skill format: the description that triggers it, the steps in order with exact commands, the check that says the result is right, and what to do when a step fails.

Page: https://schubergphilis.github.io/ai-training/customizing-agents/first-skill/
License: CC BY-SA 4.0, https://creativecommons.org/licenses/by-sa/4.0/

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 (Agent skills: skills versus tools, the spec, progressive disclosure, Agent Engineer Course). 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

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 the release check

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

```sh
python3 release_check.py
```

Output:

```text
release 0.3.0: ok
```

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

A skill is a directory with a file named `SKILL.md` in it, and the
directory's name is the skill's name (Agent Skills specification, agentskills.io). 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 (Extend Claude with skills, Claude Code documentation). Make the
directory in your copy of the package:

```sh
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
(Agent Skills specification, agentskills.io). 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
(Extend Claude with skills, Claude Code documentation). So
the description is the trigger, and it should name the requests that
should load it (Introduction to agent skills, Claude Academy). Write this as the
whole file for now:

```markdown
---
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: What does the lint report?

Predict the four lines the lint prints, one for the release skill and one
per broken rule for `bad-skill`. A skill that passes gets the line
`<directory>: ok, description is <length> characters`. A skill that fails
gets one `<directory>: <problem>` line per broken rule, in this order:
`name is missing`, `name is longer than 64 characters`,
`name has uppercase letters`,
`name has a character other than a-z, 0-9 and single hyphens`,
`name does not match the directory '<directory>'`,
`description is missing or empty` and
`description is longer than 1024 characters`. The character check runs on
the name in lowercase, so only the uppercase line reports a capital letter.

```sh
python3 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 (Extend Claude with skills, Claude Code documentation), 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

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 (Agent Skills specification, agentskills.io).
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:

```markdown
# 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 the release steps

- Change the version line and write its changelog entry
- Commit and tag
- Fix what a FAIL line names and run the check again
- Run the release check and read its line
- Run the tests, and stop if they fail

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

#### Checkpoint: What does the check print at each point?

Predict the two lines, one after the version bump and one after the
changelog entry. Each line starts with `after step 2:` or `after step 3:`,
followed by what the check prints. The problems it can name are
`version '<version>' is not three numbers`,
`CHANGELOG.md has no '## <version>' heading` and `the tests fail`.

```sh
python3 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:

```markdown
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: Which lines let a colleague's agent run it?

Which three lines earn their place in the release skill?

Select exactly 3.

- The exact test command, `python3 -m unittest -q`, and the line it must end with.
- A note that the author prefers releasing on Fridays.
- A check the agent runs at the end, with the exact line that means the release is right.
- The full history of why the team moved from one release tool to another.
- What to do when the check prints a failure, and what not to edit.

#### Checkpoint: Which description is the right trigger?

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

- "Helps with the notes package, its version, its changelog, and its releases."
- "Cut a release of the notes package. Use when asked to release, bump the version, or tag."
- "Follow the release steps below carefully, in order, and report when the work is done."

#### Pitfall: The description that matches everything

A team writes a skill for updating the changelog and describes it as "Use
when working on the notes package". For a week every session in the
package loads it, whatever the task, and the agent starts adding a
changelog bullet to a typo fix in the README because the loaded body
says to. The body was fine. The description matched every request
instead of the one it was for. Write the description as the list of
requests that should load the skill, and read it once as a stranger to
check that a request for something else wouldn't match.

## 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 skill loads. 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 lesson on testing a skill 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 (Agent Skills specification, agentskills.io).
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 (Extend Claude with skills, Claude Code documentation).
   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. The lesson on testing a skill runs it that way.

## References

- Agent skills: skills versus tools, the spec, progressive disclosure, Agent Engineer Course, https://lsimons.github.io/agent-engineer-course/17-agent-skills/
- Agent Skills specification, agentskills.io, https://agentskills.io/specification
- Extend Claude with skills, Claude Code documentation, https://code.claude.com/docs/en/skills
- Introduction to agent skills, Claude Academy, https://academy.claude.com/courses/introduction-to-agent-skills
