What are agent skills, and how do you actually use them?

An agent skill is a folder containing a SKILL.md file that tells an AI coding agent when and how to do a specific task. The agent sees the skill's short description at all times and loads the full instructions only when it decides the skill applies. Claude Code, OpenCode, and Codex all read the same format, so a skill written once can work in all three.
This page covers what a skill is, how each tool finds and loads it, and how to write and use one end to end.
What an agent skill is
The format is the Agent Skills specification, an open standard Anthropic published in December 2025 that all three tools have adopted. The minimum is a directory with one file:
changelog-entry/
└── SKILL.md
SKILL.md is YAML frontmatter (name, description) followed by a markdown body of instructions. Scripts, reference documents, and templates are optional extras.
The reason skills exist is context. A CLAUDE.md or AGENTS.md file is always in the model's context, whether or not it's relevant. A skill's body is not. The spec calls this progressive disclosure and describes three levels:
- Metadata: every installed skill's
nameanddescription, loaded at startup, roughly 100 tokens each. - Instructions: the full
SKILL.mdbody, loaded when the skill is activated. Recommended under 5,000 tokens and 500 lines. - Resources: files in
scripts/,references/, orassets/, loaded only when the instructions point at them.
Thirty installed skills cost thirty descriptions per turn, not thirty procedures.
Anatomy of a SKILL.md
The skill this page uses as its worked example writes a changelog entry from the current diff.
---
name: changelog-entry
description: Writes a CHANGELOG.md entry in Keep a Changelog format from the current uncommitted or branch changes. Use when the user asks for a changelog entry, release notes for a change, or to "update the changelog".
---
# Changelog entry
Write one entry for the `[Unreleased]` section of `CHANGELOG.md`, following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## Steps
1. Run `git diff HEAD`. If empty, run `git diff main...HEAD` for the branch. If both are empty, say so and stop.
2. Group changes under the standard headings that apply: Added, Changed, Deprecated, Removed, Fixed, Security.
3. One bullet per user-visible change. Describe the effect on the user, not the implementation. Skip refactors, tests, and formatting.
4. If `CHANGELOG.md` has no `[Unreleased]` section, add one below the title.
5. Insert the entry and show the resulting diff. Do not commit.
## Example
Diff: adds a `--json` flag to `export`; fixes a crash when the config file is missing.
## [Unreleased]
### Added
- `export --json` prints results as JSON instead of a table.
### Fixed
- Running with no config file no longer crashes; defaults are used instead.
## Edge cases
- Breaking changes go under Changed or Removed, bullet prefixed with "Breaking:".
- Docs-only diffs get a single bullet under Changed.
Frontmatter fields, per the spec:
| Field | Required | Rules |
|---|---|---|
name | Yes | 1 to 64 characters, lowercase letters, digits, single hyphens. Must match the directory name. |
description | Yes | 1 to 1,024 characters. What the skill does and when to use it. |
license | No | License name or a bundled file. |
compatibility | No | Up to 500 characters of environment requirements. Rarely needed. |
metadata | No | String-to-string map for your own tooling. |
allowed-tools | No | Experimental. Pre-approved tools, space-separated. |
Each tool adds its own extensions: Claude Code as extra frontmatter fields (disable-model-invocation, context, paths, and others), OpenCode as keys under metadata (opencode/autoinvoke), Codex in a separate agents/openai.yaml file. For a portable skill, stay within the six spec fields; each tool's own documentation covers the extensions.
The body has no format rules. Numbered steps, one concrete input and output, and a short list of edge cases work. Explaining why the process exists does not.
How the agent decides to use a skill
Two paths: the model picks the skill, or you name it.
Automatic invocation works the same everywhere. The tool puts every skill's name and description into context, and the model loads one when a request matches. This is why the description matters more than the body. "Helps with changelogs" rarely triggers; "Use when the user asks for a changelog entry, release notes, or to update the changelog" does.
Explicit invocation differs:
| Claude Code | OpenCode | Codex | |
|---|---|---|---|
| Listing shown to the model | Name and description, budgeted at 1% of the context window | <available_skills> block in the skill tool description | Name, description, and path, budgeted at 2% of the context window |
| You invoke with | /changelog-entry | Ask in prose; the model calls skill({ name: "changelog-entry" }). V2 adds slash: true to list it as a command | $changelog-entry, or /skills to pick from a list |
| Turn off auto-invocation | disable-model-invocation: true in frontmatter | metadata.opencode/autoinvoke: false (V2) or a deny permission rule | policy.allow_implicit_invocation: false in agents/openai.yaml |
In all three tools the body is read once when the skill loads, not re-read on later turns, so write it as standing guidance rather than a one-shot script.
Where skills live in Claude Code, OpenCode, and Codex
Each tool separates personal skills (home directory, every project) from project skills (in the repo, shared with whoever clones it).
| Scope | Claude Code | OpenCode | Codex |
|---|---|---|---|
| Project | .claude/skills/<name>/SKILL.md | .opencode/skills/<name>/SKILL.md | .agents/skills/<name>/SKILL.md |
| Personal | ~/.claude/skills/<name>/SKILL.md | ~/.config/opencode/skills/<name>/SKILL.md | ~/.agents/skills/<name>/SKILL.md |
| Also reads | Plugin skills/ dirs, managed settings, nested .claude/skills/ in subdirectories | .claude/skills/ and .agents/skills/ at both scopes | /etc/codex/skills |
Two consequences. OpenCode reads the other two tools' directories, but Claude Code and Codex do not read each other's, so a team on all three commits the skill twice or uses something that installs into each layout. And all three walk up from the working directory to the repo root, so a root-level skill is available from any subdirectory.
A worked example: one skill, end to end
Save the SKILL.md above as a project skill in whichever tools you use. The folder is identical; only the parent directory changes.
mkdir -p .claude/skills/changelog-entry # Claude Code
mkdir -p .opencode/skills/changelog-entry # OpenCode (or rely on .claude/skills/, which it also reads)
mkdir -p .agents/skills/changelog-entry # Codex
Change any file so there is a diff, then start the tool in the repo. The same prose request triggers the skill in all three:
> Add a changelog entry for what I just did
The tool matches the description, loads the body, and the model runs git diff HEAD and edits CHANGELOG.md. To skip the matching step and name the skill directly:
| Interactive | Non-interactive (scripts, CI) | |
|---|---|---|
| Claude Code | /changelog-entry | claude -p "/changelog-entry" |
| OpenCode | Ask in prose; the model calls skill({ name: "changelog-entry" }) | opencode run "add a changelog entry" |
| Codex | $changelog-entry | codex exec "add a changelog entry" |
Either way the result is a diff to CHANGELOG.md along these lines (illustrative; your bullets will differ):
+## [Unreleased]
+
+### Fixed
+- The `export` command no longer fails when the config file is missing.
You now have one skill and up to three copies of it. That is fine for one skill. It stops being fine at ten, when someone edits one copy and not the others.
Skills vs commands, subagents, and MCP servers
| Primitive | What it is | Who triggers it | On disk |
|---|---|---|---|
| Skill | Instructions loaded on demand | The model or you | skills/<name>/SKILL.md |
| Command | A prompt you invoke by name | You | Claude Code: merged into skills. OpenCode: .opencode/commands/<name>.md. Codex: no separate primitive |
| Subagent | A separate agent with its own prompt, tools, and context | The model delegates to it | .claude/agents/<name>.md, .opencode/agents/<name>.md, .codex/agents/<name>.toml |
| MCP server | An external process providing tools or data | The model calls its tools | .mcp.json, opencode.json, .codex/config.toml |
A skill teaches a procedure, a command is a shortcut you type, a subagent is a specialist you hand work to, and an MCP server is a capability the agent didn't have. In Claude Code, commands and skills are now the same thing; in OpenCode they are separate.
Writing skills that get used
- Put the trigger phrases in the description. It is the only thing the model sees before deciding.
- Keep the body short and concrete. Every line stays in context once loaded.
- Include one real input and output.
- Test both paths: ask for the task without naming the skill, then invoke it by name.
Sharing skills with a team
Committing .claude/skills/ works for one tool and one repo. Beyond that: personal skills are invisible to teammates and CI, a two-tool team needs every skill in two directories, and nobody knows which copy is current. Six months in, two developers on the same repo get different results from the same request. The options are submodules, dotfiles, symlinks, copying, or a registry.
Summary
- A skill is a directory with a
SKILL.md:nameanddescriptionin frontmatter, instructions in the body. - The description is always in context; the body loads on use. Write the description for triggering, the body for execution.
- Stay within the six spec fields to keep a skill portable.
- Project skills:
.claude/skills/,.opencode/skills/,.agents/skills/. OpenCode also reads the other two. - Explicit invocation:
/name(Claude Code), theskilltool (OpenCode),$name(Codex). - Commit project skills. Past a handful, or past one tool, plan for keeping copies in sync.
Do this with facets
When a skill needs to be in more than one place, Agent Facets treats it like a dependency. A facet bundles skills (plus agents, commands, and MCP server declarations) under a version, and an adapter writes them into each tool's layout.
facet adapter add claude-code # or: opencode, codex
facet add cowsay
facet add records the version in facets.json and the resolved hash in facets.lock; a teammate or CI job runs facet install and gets identical files. The quickstart takes about five minutes.