Skip to content

Agent Skills

Lesson 7 of 10Advanced14 min readGitHub Copilot & AI Engineering · AI AgentsVerified: GitHub Copilot agent skills documentation, September 2026

A skill is a written procedure an agent loads when it is relevant.

That is a narrower thing than the name suggests, and the narrowness is what makes it useful. It is not a plugin, not code, and not a capability the model did not previously have. It is your team’s way of doing something, written down in a form an agent reads at the point it needs it — which turns out to solve a real problem, because the alternative is explaining the same procedure at the start of every session.

A skill is a directory containing a SKILL.md file with frontmatter.

---
name: release-notes
description: Generate release notes from the commit range between two tags. Use when preparing a release or when asked to summarise what changed between versions.
allowed-tools:
- shell(git log)
- shell(git tag)
- shell(git diff --stat)
---
# Release notes
## Determining the range
Find the previous release tag with `git tag --sort=-v:refname | head -n 2`.
The range is `<previous>..<current>`.
## Reading the changes
`git log <range> --format='%s%n%b'` for messages, and
`git diff --stat <range>` for the shape of the change.
## Structure
Group by audience, in this order:
1. **Breaking changes** — anything requiring action from a consumer. If there
are none, omit the section rather than writing "none".
2. **Features** — new capability, described by what it enables.
3. **Fixes** — what was broken, described from the user's side.
4. **Internal** — refactors and dependency updates, one line total.
## Rules
- Describe behaviour, not implementation. "Orders now retry on timeout",
not "added a retry wrapper to OrderClient".
- Every entry links its pull request.
- Do not claim a fix resolves a reported issue unless the commit references
that issue.
- If a commit's purpose is unclear from its message and diff, list it under
Internal rather than inventing a user-facing description.

name and description are required. Both, in that frontmatter block. A skill missing either will not load.

The description is not documentation — it is the routing signal. It is how the agent decides whether this skill is relevant to the current task. A vague description means the skill loads when it should not and, more often, fails to load when it should.

allowed-tools scopes what the skill may use. This is the part that makes a skill more than a document.

The field that determines whether a skill is ever used, and the one people write last.

Say what it does and when to use it. Both halves. The “when” is what makes routing work.

# Weak — no routing signal
description: Release notes helper.
# Better
description: Generate release notes from the commit range between two tags.
Use when preparing a release or when asked to summarise what changed
between versions.

Include the words somebody would actually use. If your team says “cut a release”, the description should contain that phrase. Routing works on the description text, and matching the vocabulary in use beats matching the vocabulary in the documentation.

Be specific about the boundary. “Use when preparing a release” is better than “use for release-related tasks”, because the second matches things this skill cannot do.

One skill, one job. A skill described as “handles releases, changelogs, version bumps and deploys” will load for all four and be mediocre at each. Four skills route better and are individually shorter.

The distinction that decides whether you should write one at all.

Repository instructions are always in context. Everything in them costs context on every task, whether relevant or not.

A skill loads when its description matches. It costs nothing on unrelated tasks.

That gives a clean rule:

The guidance isPut it in
Relevant to every task in the repositoryInstructions or AGENTS.md
A procedure for a specific recurring taskA skill
Two linesInstructions — a skill is overhead
Two pages of stepsA skill
Needed by several repositoriesA shared skill

Conventions go in instructions. “Errors are wrapped in AppError” applies to every change.

Procedures go in skills. “How we prepare a release” applies when preparing a release, which is once a fortnight.

The test: would you want this in front of the agent on an unrelated task? If not, it is a skill.

allowed-tools is the enforcing half of a skill definition, and it deserves the same care as any tool list.

List what the procedure needs. The release-notes skill reads git history. It does not need to write files, push, or run arbitrary shell.

Specific commands, not categories. shell(git log) is a boundary. shell(git) includes git push --force.

Nothing destructive. A skill that runs migrations, deploys, or applies infrastructure changes is a skill that can do those things in any session where it loads — including one where the user was asking about something adjacent. Route those through a reviewed pipeline.

Read-only where possible. An analysis skill that cannot write is one you never have to think about.

The body is prose an agent reads. The qualities that make it work are the qualities that make a runbook work for a new team member.

Concrete commands. git tag --sort=-v:refname | head -n 2 is executable. “Find the previous tag” is a research task.

Explicit ordering. Numbered steps where order matters, and a note where it does not.

Decision points stated. “If there are no breaking changes, omit the section” removes a judgement the agent would otherwise make differently each time.

Examples of good and bad output. One line of each is worth a paragraph of description. Showing “Orders now retry on timeout” against “added a retry wrapper to OrderClient” teaches the distinction faster than explaining it, and it removes the argument about what the rule meant. Two or three such pairs are usually enough for a whole procedure.

Explicit prohibitions. “Do not claim a fix resolves an issue unless the commit references it” is the kind of line that prevents a confident invention.

A stated fallback. “If a commit’s purpose is unclear, list it under Internal” gives the procedure a defined behaviour for the case it cannot handle. Without one, the agent invents something plausible.

That last point generalises: every procedure should say what to do when it does not apply. The alternative is not the agent stopping — it is the agent continuing with a guess.

A skill is a directory, not just a file, and the other files in it are underused.

Templates. A skill that produces a document — an incident report, an architecture decision record, a release note — can ship the template alongside it and reference it from the procedure. “Use the structure in template.md” is more reliable than describing the structure in prose, because the template is unambiguous and can be edited by whoever owns the format rather than by whoever owns the skill.

Reference material. A short table of your service names and their owners, a list of the environments and what each is for, the mapping from label to team. Facts the procedure needs that would otherwise be guessed.

Scripts. Where a step is genuinely mechanical, a script in the skill directory is better than prose describing what the script would do — it is deterministic, it is testable, and it removes a step from the part of the process that can vary.

.github/skills/release-notes/
SKILL.md
template.md — the output structure
service-owners.md — who to attribute what to
scripts/range.sh — determine the tag range deterministically

The rule for what belongs in a bundled file rather than in the prose: anything that is data rather than procedure, and anything somebody other than the skill’s author should be able to change. A template edited by the person who owns the document format is better maintained than a template embedded in a procedure they will never open.

The caveat on scripts. A script in a skill directory is code that runs when the skill loads. It needs the same review as any other executable content in the repository, and it needs to be listed in allowed-tools to run at all.

Keep it under a page or two. A long skill dilutes, and its middle section carries less weight than its beginning.

Use headings. They are navigational for a reader working through a procedure.

Put the rules at the end. The procedure first, the constraints after — the same order a runbook uses, and it reads correctly whether the agent processes it linearly or refers back.

Split rather than extend. A skill that has grown to four pages covering three related procedures is three skills. They route better and each is shorter.

Skills are files, so distributing them is a file problem.

Repository-local for anything specific to one codebase. It lives with the code, changes through pull requests, and is reviewed like everything else.

Shared across repositories for genuinely general procedures. Organisation-level configuration where the plan supports it, or packaged as a plugin — a plugin.json with a marketplace.json — for a platform team distributing a standard toolkit.

The rule that makes sharing work: a shared skill must not hard-code repository specifics. A release-notes skill that reads the tag format from the repository is portable. One that assumes v[0-9].[0-9].[0-9] is portable to repositories that happen to use it.

Version what you share. A change to a shared skill changes behaviour everywhere it is installed, with no pull request in those repositories to notice. Same risk class as an unpinned action, same treatment: deliberate versions, and a way to stay behind.

The ones that repay the effort, drawn from what teams actually repeat.

Incident response. The steps for declaring, the template for the timeline, where the runbooks are, who to page. Written once, loaded at three in the morning by whoever is on call — which is the situation in which a written procedure is worth the most.

Release preparation. The tag format, the changelog structure, the checks before cutting, the announcement template.

Adding a new service. The scaffolding, the required configuration, the registrations in the service catalogue, the monitoring that must exist before it ships. A procedure with fifteen steps that somebody does twice a year and forgets between times.

Database migration authoring. Reversibility requirements, locking considerations, the three-step pattern for adding a NOT NULL column, what must never be combined in one migration. High-consequence, easy to get wrong, and mechanical enough to write down.

Dependency upgrades. How to check the changelog, what to run, what to look at in the diff, what requires a human decision rather than an automatic bump.

Debugging a specific subsystem. The queries, the log locations, the metrics that matter, the three things it usually turns out to be. This one is pure institutional knowledge and it otherwise lives in one person’s head.

Onboarding a repository. What this service does, how to run it locally, the first thing to read. Overlaps with AGENTS.md and belongs in a skill when it runs to a full procedure rather than a description.

The pattern across all of them: infrequent, multi-step, consequential, and currently reconstructed from memory each time. Frequent tasks do not need skills — people remember them. It is the fortnightly and quarterly procedures where the written version is worth having, and where the agent loading it matters least compared with the human reading it.

A skill is executable-adjacent content and deserves review.

The tool list is the security-relevant part. Read allowed-tools before the prose. A skill whose tools grew during a refactor is a permission change nobody reviewed as one.

Check the description for over-broad routing. A description matching more than the skill can handle produces confidently wrong output on adjacent tasks.

Test it by using it. Ask for the task the skill is for, and confirm it loads and behaves. A skill nobody has exercised is a guess, and the failure is usually in the description rather than in the procedure — the prose is fine and it never gets read because the routing never fires.

Test the negative case too. Ask for something adjacent that the skill should not handle, and confirm it stays out of the way. Over-broad routing is harder to notice than under-broad routing, because the output looks like an answer rather than like nothing happening.

Put skills under CODEOWNERS. They shape agent behaviour across the repository, and a change to one is a change to a shared standard rather than to one person’s workflow.

Read a skill change the way you read a lint rule change. The question is not “is this line correct” but “is this now true for everyone who will load it”. A procedure adjusted to suit one person’s situation becomes the procedure everybody gets.

Four things now overlap, and knowing which to reach for saves rewriting.

MechanismLoadedScopeEnforces?
AGENTS.mdAlwaysThe repositoryNo
InstructionsAlwaysRepository or pathNo
Prompt filesOn explicit invocationOne promptNo
SkillsOn description matchOne procedureallowed-tools does
Custom agentsOn selectionA persona plus a tool listThe tool list does

Skill or prompt file? A prompt file is invoked by name — you ask for it. A skill routes automatically on relevance. If you want it to appear without being asked for, it is a skill. Prompt files are also IDE-oriented and currently in public preview, which is a consideration for anything a team is going to depend on.

Skill or custom agent? An agent is a persona for a whole session — a reviewer, a test writer. A skill is a procedure inside a session. An agent can use skills; the reverse does not apply. If it changes how the session behaves throughout, it is an agent.

Skill or instructions? Covered above: always-relevant guidance goes in instructions, task-specific procedures go in skills.

Skill or documentation? This one deserves a direct answer: write it as a skill and let people read the file. A SKILL.md is Markdown in the repository. It is as readable by a person as any runbook, it is reviewed through pull requests, and it has the advantage of being loaded automatically by the agent rather than sitting in a wiki nobody opens. Teams that write both end up with two versions that disagree.

That last point is the strongest practical argument for skills generally. The procedure was worth writing down regardless. The skill format gives the written version a second consumer, which is often what finally makes somebody write it.

A vague description. The skill never loads, or loads for the wrong things.

Putting a procedure in instructions. Costs context on every unrelated task.

Putting a convention in a skill. It only applies when the skill loads, so most changes miss it.

Broad allowed-tools. Access granted by a routing decision rather than a deliberate one.

A four-page skill. Dilutes; split it.

No stated fallback. The agent invents a behaviour for the case you did not cover.

Hard-coding repository specifics into a shared skill. Portable to one repository.

Never testing it. A skill that has never loaded is untested code.

Skills that deploy or apply infrastructure. Destructive capability triggered by description matching.

A skill is a written procedure, and written procedures rot. The rot is quiet, because nothing fails when one goes stale — the agent follows it confidently into a step that no longer exists.

The commonest decay: a command renamed, a directory moved, a step that a newer tool now does automatically. Each is individually trivial and collectively turns a good skill into a source of plausible wrong instructions.

Tie the skill to what it describes. A skill covering the release process should be listed in CODEOWNERS alongside the release configuration, so a change to one prompts a look at the other.

Run it after any change to the underlying process. If somebody changes the tag format, the release-notes skill needs a five-minute check. This is the same discipline as updating documentation, and it fails for the same reason — nobody is prompted.

Date the parts that are time-sensitive. A line saying which tool version a step assumes lets the next reader tell whether it is current, rather than guessing.

Delete rather than maintain. A skill for a process the team no longer follows should be removed. A stale skill that still loads is worse than no skill, because it produces confident output from an obsolete procedure — and it will keep doing so until somebody notices, which may be a long time.

The check worth scheduling: once a quarter, for each skill, ask whether anybody has used it and whether the steps still work. Most will be fine. The one that is not is the one that would have caused a bad afternoon.

A skill is a runbook with a keyring attached.

The runbook half is the procedure — the steps, the decisions, the examples of good output, the fallback for the case it does not cover. Written for a competent person who has not done this particular task here before.

The keyring half is allowed-tools, and it is granted whenever the routing decision says this runbook is relevant. That is why the keyring should hold the minimum the procedure uses: you are not handing it to a person who will use judgement about which key to try.

  • A skill is a SKILL.md with required name and description frontmatter, plus allowed-tools
  • The description is the routing signal, not documentation — it decides whether the skill ever loads
  • Conventions belong in instructions; procedures belong in skills
  • allowed-tools is enforced and applies whenever the skill loads, so keep it minimal
  • Good procedures give concrete commands, stated decisions, examples, and a fallback for the unhandled case
  • Shared skills must not hard-code repository specifics, and should be versioned
  • Review the tool list before the prose; put skills under CODEOWNERS

Use a disposable repository.

  1. Write a skill for a procedure your team actually repeats. Include name, description and a minimal allowed-tools.

  2. Ask for the task using the words your team uses. Predict: does the skill load?

  3. Rewrite the description to be vague — “helps with releases”. Ask again. Predict: what changes?

  4. Ask for an adjacent task the skill does not cover. Predict: does it load anyway, and what does it produce?

  5. Remove a tool from allowed-tools that the procedure needs. Run it. Predict: which layer stops it?

  6. Add a fallback line for a case the procedure does not handle. Trigger that case before and after. Compare.

  7. Delete the repository.

AI-assisted engineering learning pathEleven lessons on getting value from Copilot and agents without giving up review discipline.