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 agent pays for the name and description always, the body only when it needs the body.
A skill is a file convention
A skill is a directory containing SKILL.md (required) plus optional scripts/, references/ and assets/. SKILL.md is YAML frontmatter followed by markdown, and paths inside it 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 its own specification and 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, around 100 tokens per skill, sitting in context from startup. This is the list the model scans for relevance. - Body. The rest of
SKILL.mdloads when the skill is invoked. Target under 5,000 tokens. - Bundled files.
pr.md, a reference table, a script — read only if the body sends the agent there. Script code never enters context. Only its stdout does.
Every globally installed skill charges you level 1 in every session, forever. Claude Code caps the description plus when_to_use shown in the listing at 1,536 characters; Codex caps the entire skills list at 2 percent of the context window, falling back to 8,000 characters when the window size is unknown. So install narrowly and treat the global scope as expensive real estate (context budgeting).
Frontmatter, annotated
The open spec allows exactly six fields. Claude Code adds many more, and those extras are a hard error on Anthropic's other distribution paths — claude.ai uploads, the Skills API and package_skill.py packaging — while other harnesses just 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 than the spec on name: it is optional there, sets only the display label, and the command you type still comes from the directory name.
disable-model-invocation: true is the one to know. It removes the skill from the startup metadata, so only you can trigger it, by name. Use it for anything aimed at the human rather than the agent — reports, generators, one-shot commands. My session-report skill is set this way. Outside Anthropic's harness the flag does nothing, so on Codex the 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 own equivalent of the flag is allow_implicit_invocation in a per-skill agents/openai.yaml.
Three shapes
Pure prompt. My pr-issue skill holds the templates I want every PR and issue to follow: title as area: summary, then problem, solution, security impact, testing. The standard, written once.
Router. The 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. This is how you keep a big body of guidance cheap: one small entry point, detail behind links.
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 the generator's source, so a thousand lines of TypeScript cost nothing until stdout comes back. Scripts should be non-interactive, support --help and return structured output.
The security cost
A skill body can also embed a command whose output is bundled into context — the agent sees the diff, not the git diff. Convenient, and a real supply-chain problem when combined with bundled scripts: installing a skill is installing software that runs with your permissions. Anthropic's guidance is blunt: use trusted sources only, audit every bundled file, and treat skills that fetch external URLs as 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 (the repo you are standing in, versioned with it) 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, for reference, not prescription: pr-issue, agent-to-human and human-to-agent (how the agent writes for me, and how I write for it), quality-code for TypeScript, new-project, product, context-doctor. observability and Matt Pocock's codebase-architecture skills I install per project rather than globally. A global AGENTS.md line pointing at pr-issue means "make a PR" reaches the template without me naming the skill.
What to do
- Take the instruction you retype most often and move it into
~/.agents/skills/<name>/SKILL.md, symlinked to~/.claude/skills/. - Spend your effort on
description: it is the only thing 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 a skill from anyone else.
- Audit the global scope monthly and delete what you have not invoked.