Skills: Teach the Agent Your Way of Working
Package your repeated instructions as skills: the SKILL.md format, progressive disclosure, prompt vs router vs script-backed shapes, and how to install them safely.
The one line: a skill is a folder with a
SKILL.mdin it, and its value is progressive disclosure: the name and description sit in context always, the body loads only when the agent needs it.
A skill is a file convention
A skill directory holds SKILL.md plus optional scripts/, references/ and assets/. SKILL.md is YAML frontmatter then markdown; paths inside are relative to the skill root.
pr-issue/
├─ SKILL.md # frontmatter + the router body
├─ pr.md # loaded only when the request is a PR
└─ issue.md # loaded only when the request is an issueThe format started at Anthropic and is now an open standard with roughly 45 clients, Codex, Cursor, Gemini CLI and OpenCode among them. Keep SKILL.md under 500 lines and references one level deep.
Progressive disclosure
Three levels:
- Metadata.
nameplusdescription, about 100 tokens per skill, in context from startup. The model scans this list for relevance. - Body. The rest of
SKILL.md, loaded when the skill is invoked. Target under 5,000 tokens. - Bundled files. A reference table,
pr.md, a script, read only if the body sends the agent there. Script code never enters context, only its stdout.
Every globally installed skill charges you level 1 in every session. Claude Code caps description plus when_to_use at 1,536 characters, Codex caps the whole list at 2 percent of the window. So install narrowly (context budgeting).
Frontmatter, annotated
The open spec allows six fields. Claude Code adds more, and those extras are a hard error on claude.ai uploads, the Skills API and package_skill.py. Other harnesses ignore them.
---
# --- open spec: portable everywhere ---
name: session-report # required; lowercase, must match the folder name
description: >- # required, <=1024 chars; your matching signal
Generate an HTML report of what is in a session's context window.
license: MIT # optional
compatibility: "claude-code" # optional, <=500 chars
metadata: { version: "1.1.0" }# optional string->string map
allowed-tools: "Bash Read" # optional, marked experimental in the spec
# --- Claude Code only: silently ignored by Codex, rejected by the Skills API ---
when_to_use: "when the user asks what is eating their context"
argument-hint: "[session-id]"
arguments: session_id
disable-model-invocation: true # keeps it out of the startup listing entirely
model: opus
effort: high
---Claude Code is looser on name: optional, display label only, and the command you type comes from the directory name.
disable-model-invocation: true drops the skill from the startup metadata, so only you can trigger it, by name. Use it for reports, generators, one-shot commands: anything aimed at you, not the agent. My session-report is set this way. Outside Anthropic's harness the flag does nothing, so on Codex your only lever is not installing the skill.
| Harness | Invoke a skill by name |
|---|---|
| Claude Code | /skill-name, with $ARGUMENTS, $0, $name in the body |
| Codex CLI and IDE | $skill-name, or /skills to browse |
| ChatGPT | @ |
Codex's equivalent flag is allow_implicit_invocation in a per-skill agents/openai.yaml.
Three shapes
Pure prompt. My pr-issue skill holds the templates every PR and issue follows: title as area: summary, then problem, solution, security impact, testing.
Router. That same skill is also a router. SKILL.md carries the shared rules, then branches:
Writing a pull request → [`pr.md`](pr.md)
Writing an issue, ticket, or bug report → [`issue.md`](issue.md)Only the matching file loads, so a long body of guidance costs you one short entry point.
Script-backed. session-report/SKILL.md is mostly instructions for running a TypeScript file that reads a session transcript and writes an HTML report:
node generate-report.ts "$session_id" --openThe model never reads that source, so a thousand lines of TypeScript cost nothing until stdout comes back. Keep scripts non-interactive, with --help and structured output.
The security cost
A skill body can embed a command whose output lands in context: the agent sees the diff, not the git diff. Combined with bundled scripts that is a supply-chain problem: installing a skill installs software that runs with your permissions. Anthropic says to use trusted sources only, audit every bundled file, and treat skills that fetch external URLs as the highest risk. Read the scripts/ directory before you install. Nothing stops one from reading .env and posting it somewhere.
Distribution
The skills CLI is community-built by Vercel Labs, not Anthropic. It reads a GitHub repo of skills and installs the ones you pick:
bunx skills add Mark-Life/agent-skills # or npx / pnpxIt asks three things. Which convention: .agents/skills works almost everywhere, .claude/skills for Claude Code. Which scope: project, versioned with the repo, or global, every session on the machine. And copy versus symlink. Choose symlink: one source of truth in ~/.agents/skills/, linked into every harness directory, so an edit lands everywhere at once. The installer flags risky bundles; mine gets a mid-risk warning because it ships scripts.
My global set: pr-issue, agent-to-human, human-to-agent, quality-code for TypeScript, new-project, product, context-doctor. observability and Matt Pocock's codebase-architecture skills I install per project. A line in my global AGENTS.md pointing at pr-issue means "make a PR" reaches the template without me naming the skill.
What to do
- Move the instruction you retype most often into
~/.agents/skills/<name>/SKILL.md, symlinked to~/.claude/skills/. - Spend your effort on
description: it is all the model reads when deciding to load the skill. - Split anything past a page into a short router body plus linked detail files.
- Set
disable-model-invocation: trueon skills meant for you, not the model. - Read every bundled script before installing someone else's skill.
- Audit the global scope monthly and delete what you never invoke.