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

Published

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:

  1. Metadata: every installed skill's name and description, loaded at startup, roughly 100 tokens each.
  2. Instructions: the full SKILL.md body, loaded when the skill is activated. Recommended under 5,000 tokens and 500 lines.
  3. Resources: files in scripts/, references/, or assets/, 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:

FieldRequiredRules
nameYes1 to 64 characters, lowercase letters, digits, single hyphens. Must match the directory name.
descriptionYes1 to 1,024 characters. What the skill does and when to use it.
licenseNoLicense name or a bundled file.
compatibilityNoUp to 500 characters of environment requirements. Rarely needed.
metadataNoString-to-string map for your own tooling.
allowed-toolsNoExperimental. 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 CodeOpenCodeCodex
Listing shown to the modelName and description, budgeted at 1% of the context window<available_skills> block in the skill tool descriptionName, description, and path, budgeted at 2% of the context window
You invoke with/changelog-entryAsk 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-invocationdisable-model-invocation: true in frontmattermetadata.opencode/autoinvoke: false (V2) or a deny permission rulepolicy.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).

ScopeClaude CodeOpenCodeCodex
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 readsPlugin 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:

InteractiveNon-interactive (scripts, CI)
Claude Code/changelog-entryclaude -p "/changelog-entry"
OpenCodeAsk in prose; the model calls skill({ name: "changelog-entry" })opencode run "add a changelog entry"
Codex$changelog-entrycodex 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

PrimitiveWhat it isWho triggers itOn disk
SkillInstructions loaded on demandThe model or youskills/<name>/SKILL.md
CommandA prompt you invoke by nameYouClaude Code: merged into skills. OpenCode: .opencode/commands/<name>.md. Codex: no separate primitive
SubagentA separate agent with its own prompt, tools, and contextThe model delegates to it.claude/agents/<name>.md, .opencode/agents/<name>.md, .codex/agents/<name>.toml
MCP serverAn external process providing tools or dataThe 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

  1. Put the trigger phrases in the description. It is the only thing the model sees before deciding.
  2. Keep the body short and concrete. Every line stays in context once loaded.
  3. Include one real input and output.
  4. 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: name and description in 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), the skill tool (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.