A method for building agent skills that are discoverable, maintainable, and production-grade.
This is how we build every skill. It's a synthesis of what actually works after building 1900+ of them — what to include, what to leave out, and how to structure the whole thing so it scales.
More at intentsolutions.io | claudecodeplugins.io
Every skill is a directory with one required file and optional supporting directories:
skill-name/
├── SKILL.md # The skill itself — frontmatter + instructions
├── scripts/ # Automation code that does real work
├── references/ # Heavy documentation, loaded on demand
│ └── implementation.md # Detailed implementation patterns
├── templates/ # Boilerplate for generation
└── assets/ # Static resources (configs, examples)
The only file that matters at startup is SKILL.md. Everything else loads when needed.
Every SKILL.md starts with YAML frontmatter between --- delimiters.
---
name: processing-invoices
description: |
Parse invoice PDFs, extract line items, and generate reconciliation
reports. Use when processing vendor invoices or during monthly closes.
Trigger with 'process invoices', 'reconciliation', 'invoice PDF'.
allowed-tools: Read, Glob, Grep, Bash(python:*)
version: 1.0.0
author: Your Name <you@example.com>
license: MIT
compatible-with: claude-code, codex, openclaw
---| Field | Type | Notes |
|---|---|---|
name |
string | Kebab-case, 1-64 chars, matches directory name |
description |
string | What + When + Trigger. Third person. Include "Use when..." and "Trigger with '...'" |
allowed-tools |
CSV string | Comma-separated tool list. Always scope Bash: Bash(git:*), Bash(npm:*) |
version |
string | Semver: 1.0.0 |
author |
string | Name <email> format |
license |
string | e.g., MIT |
name — Kebab-case only. Must match the directory name. We prefer gerund naming: processing-invoices, analyzing-logs.
description — The most important field. It determines whether and when the skill activates.
- Third person always. "Generates reports..." not "I generate..." or "You can generate..."
- Must include what it does, when to use it (
Use when...), and trigger phrases (Trigger with '...') - Use action verbs (analyze, detect, generate, configure, deploy)
- Keep it tight — every description loads into the system prompt at startup
allowed-tools — Must be a CSV string, not a YAML array. Never use bare Bash — always scope it:
# Good
allowed-tools: Read, Write, Edit, Bash(npm:*), Glob
# Bad — security risk
allowed-tools: Read, Write, BashValid tools: Read, Write, Edit, Bash, Glob, Grep, WebFetch, WebSearch, Task, TodoWrite, NotebookEdit, AskUserQuestion, Skill
| Field | What It Does | Example |
|---|---|---|
model |
Model override | sonnet, opus, haiku (use short names, not full IDs) |
context: fork |
Run skill in forked subagent context | — |
agent |
Subagent type | Explore, Plan |
argument-hint |
Autocomplete hint | "<file-path>" |
user-invocable: false |
Hide skill from / menu |
— |
hooks |
Skill-scoped lifecycle hooks | { pre-tool-call: ... } |
compatibility |
Environment requirements | "Node.js >= 18" |
compatible-with |
Platform compatibility | claude-code, cursor, codex |
tags |
Discovery tags | [devops, ci, deployment] |
disable-model-invocation |
Only activate via explicit /name |
— |
disable-model-invocation: true+user-invocable: false= unreachable skill- Unscoped
Bashis a security risk. Always scope:Bash(git:*),Bash(npm:*) - Hardcoded model IDs break on deprecation. Use
sonnet,opus,haiku allowed-toolsmust be CSV string — YAML arrays cause parse errors
This is the core architectural idea. Skills load in three levels:
Level 1 — Metadata (~100 tokens). Just name and description. Loads for every installed skill at startup. This is your discovery surface. Keep descriptions efficient.
Level 2 — SKILL.md body (≤150 lines, <500 lines max). Loads when the skill activates. Your instructions, examples, edge cases. Keep it concise — the agent is already capable.
Level 3 — Bundled resources (unlimited). references/, scripts/, templates/. Loaded only when explicitly needed during execution. Put heavy content here.
| Component | Target | Hard Max |
|---|---|---|
| SKILL.md body | ≤150 lines | 500 lines |
| Word count | ≤3,500 words | 5,000 words |
| Purpose statement | 1-2 sentences | ≤400 chars |
If your SKILL.md exceeds 150 lines, extract detailed content to references/implementation.md and link with:
See [implementation details](${CLAUDE_SKILL_DIR}/references/implementation.md) for advanced patterns.The goal: a skill that's cheap to discover, fast to activate, and deep when it needs to be.
Every SKILL.md body must include these seven sections in order:
# Skill Title
Purpose statement (1-2 sentences, imperative voice).
## Overview
What this skill does and key capabilities.
## Prerequisites
- Required tools, APIs, credentials
- Environment setup needed
- Knowledge assumptions
## Instructions
1. First step with concrete action
2. Second step with specific details
3. Third step with validation
## Output
- What artifacts this skill produces
- Expected format and location
## Error Handling
| Error | Cause | Resolution |
|-------|-------|------------|
| Auth failure | Expired credentials | Re-authenticate |
| Config conflict | Incompatible settings | Review parameters |
## Examples
**Basic usage**: Description of simple case.
**Advanced scenario**: Description of complex case.
## Resources
- Links to documentation
- Related skills
- External references| Section | Min Content | Notes |
|---|---|---|
## Overview |
1+ sentence | What + capabilities |
## Prerequisites |
Bullet list | Tools, APIs, knowledge |
## Instructions |
40+ chars | Must use numbered steps (1. 2. 3.) or ### Step N: headings |
## Output |
20+ chars | Artifacts produced |
## Error Handling |
20+ chars | Table format preferred |
## Examples |
20+ chars | Concrete input/output pairs |
## Resources |
20+ chars | Links, related skills |
The first paragraph after # Title (or the first paragraph in ## Overview if no text follows the title directly) is the purpose statement:
- Must be 1-2 sentences (not 3+)
- Must be ≤400 characters
- Use imperative voice: "Configure X for Y" not "This skill helps you configure X"
- No first person ("I can...") or second person ("You should...")
- Numbered steps — required. Bullets alone trigger a warning
- Concrete examples over abstract rules
- Show input AND expected output
- Include edge cases that actually occur
- If the body exceeds 150 lines, split heavy content into
references/
Skills can accept dynamic input:
| Syntax | Resolves To |
|---|---|
$ARGUMENTS |
Everything after /skill-name |
$ARGUMENTS[0] |
First positional argument |
$1, $2 etc |
Positional argument shorthand |
${CLAUDE_SKILL_DIR} |
Skill's directory path (portable references) |
${CLAUDE_SESSION_ID} |
Current session identifier |
!`command` |
Runs command at activation, injects output |
Always use ${CLAUDE_SKILL_DIR} for file references — never absolute paths or {baseDir}.
All bash code blocks with dangerous commands (rm, curl, pip, npm, docker, kubectl) must include error handling:
set -euo pipefail
curl -s "https://api.example.com/data" | jq '.results'All numbers ≥200 in code blocks must have an inline comment that includes the number itself:
curl -s localhost:9090/api/v1/query # port 9090 - Prometheus
sleep 300 # 300s = 5 min timeout
if [ "$status" -eq 429 ]; then # 429 Too Many Requests
echo "Rate limited"
fi| Don't | Why | Do Instead |
|---|---|---|
| Absolute paths | Not portable | ${CLAUDE_SKILL_DIR}/ |
| Unscoped Bash | Security risk | Bash(git:*) |
| Over-verbose instructions | Wastes tokens | Be concise, ≤150 lines |
| Vague descriptions | Poor discovery | Specific keywords + what/when/trigger |
| Monolithic SKILL.md | Slow loading | Split into references/ |
| Hardcoded model IDs | Break on deprecation | Use sonnet, opus, haiku |
| First/second person | Breaks discovery | Third person always |
| Deeply nested references | Loading failures | Keep one level deep |
| Time-sensitive info | Goes stale | Generic references |
Stub scripts with pass/TODO |
Dead code warnings | Delete or implement |
Placeholder text (REPLACE_ME) |
Validator warnings | Use real example values |
| Generic boilerplate | Content quality flags | Service-specific content |
If you use references/README.md or assets/README.md with checkbox file lists, every listed file must exist:
# References
- [x] implementation.md ← must exist in references/
- [x] api_patterns.md ← must exist in references/Pick the pattern that fits your skill:
| Pattern | When to Use |
|---|---|
| Sequential | Fixed steps in order — file conversion, builds |
| Conditional | Branch on input type — multi-purpose tools |
| Wizard | Interactive gathering with questions — complex setup |
| Plan-Validate-Execute | High-stakes with verification — deployments, migrations |
| Search-Analyze-Report | Codebase exploration — audits, reviews |
| Feedback Loop | Iterate until quality threshold — content, optimization |
Skills are validated with a 100-point grading system:
| Grade | Score | Meaning |
|---|---|---|
| A | 90-100 | Production ready, all sections present and substantial |
| B | 80-89 | Good quality, minor improvements possible |
| C | 70-79 | Functional but missing sections or thin content |
| D | 60-69 | Needs significant work |
| F | <60 | Missing critical elements |
Compliance = percentage of skills with zero warnings. Target: ≥90%.
Run validation:
python3 scripts/validate-skills-schema.py --skills-only --verbose- Create the directory:
mkdir -p ~/.claude/skills/my-skill/ - Copy the template (see
skill-template.mdin this gist) - Fill in frontmatter — focus on the
descriptionfield - Write all 7 required sections with real, service-specific content
- Keep SKILL.md ≤150 lines; extract heavy content to
references/implementation.md - Test it — invoke with
/my-skilland iterate - Validate:
python3 scripts/validate-skills-schema.py --skills-only
Intent Solutions — Jeremy Longshore
Love it!