
Skill Writing Best Practices
- 161 installs
- 93 repo stars
- Updated February 1, 2026
- sergiodxa/agent-skills
Author reusable agent skills with clear triggers, progressive disclosure, and verification steps so coding agents behave consistently across projects and teams.
About
Skill-writing best practices for creating high-quality agent skills. Teaches trigger design, SKILL.md structure, progressive disclosure, executable workflows, and review habits so custom skills are reliable, discoverable, and safe for production agent use.
- Trigger phrases and scope boundaries
- Progressive disclosure in SKILL.md
- Actionable steps agents can execute
- Bundled scripts and reference layout
- Review checklist for skill quality
Skill Writing Best Practices by the numbers
- 161 all-time installs (skills.sh)
- +3 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #200 of 782 Skill Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/sergiodxa/agent-skills --skill skill-writing-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 161 |
|---|---|
| repo stars | ★ 93 |
| Last updated | February 1, 2026 |
| Repository | sergiodxa/agent-skills ↗ |
What it does
Author reusable agent skills with clear triggers, progressive disclosure, and verification steps so coding agents behave consistently across projects and teams.
Files
Skill Writing Best Practices
Patterns for creating effective AI agent skills that capture coding conventions and best practices. Contains 6 rules covering structure, content, and writing style.
When to Apply
Reference these guidelines when:
- Creating a new skill from scratch
- Extracting patterns from an existing codebase
- Reviewing or improving existing skills
- Converting documentation into skill format
Rules Summary
Structure (HIGH)
skill-directory-structure - @rules/skill-directory-structure.md
Every skill has a SKILL.md and a rules/ directory.
skills/
└── topic-best-practices/
├── SKILL.md # Main summary with all rules
└── rules/
├── rule-name.md # Detailed individual rules
└── another-rule.mdskill-md-structure - @rules/skill-md-structure.md
SKILL.md has frontmatter, overview, and condensed rule summaries.
---
name: topic-best-practices
description: When to use this skill.
---
# Topic Best Practices
Brief intro. Contains N rules across M categories.
## When to Apply
- Situation 1
- Situation 2
## Rules Summary
### Category (IMPACT)
#### rule-name - @rules/rule-name.md
One sentence. Code example.rule-file-structure - @rules/rule-file-structure.md
Each rule file has frontmatter, explanation, examples, and takeaways.
---
title: Rule Title
impact: HIGH
tags: [relevant, tags]
---
# Rule Title
What to do and why.
## Why
- Benefit 1
- Benefit 2
## Pattern
\`\`\`ruby
# Bad
bad_code
# Good
good_code
\`\`\`
## Rules
1. Takeaway 1
2. Takeaway 2Content (HIGH)
concrete-examples - @rules/concrete-examples.md
Every rule needs code examples. Abstract advice is hard to apply.
# Bad: Too abstract
"Keep your code organized."
# Good: Concrete
"Place concerns in `app/models/model_name/` not `app/models/concerns/`."
\`\`\`ruby
# Shows exactly what to do
app/models/card/closeable.rb
\`\`\`explain-why - @rules/explain-why.md
Don't just show what. Explain why it matters.
## Why
- **Testability**: Sync method can be tested without job infrastructure
- **Flexibility**: Callers choose sync or async based on context
- **Clarity**: The `_later` suffix makes async behavior explicitStyle (MEDIUM)
writing-style - @rules/writing-style.md
Write naturally. Avoid AI-isms and excessive formatting.
# Bad
---
Here is an overview of the key points:
---
# Good
Group related rules by category. Each rule gets a one-sentence
description and a short code example.Philosophy
Good skills are:
1. Concrete - Every rule has code examples 2. Reasoned - Explains why, not just what 3. Scannable - Easy to find relevant rules quickly 4. Honest - Shows when NOT to use a pattern 5. Natural - Written like documentation, not AI output
Use Concrete Examples
Every rule needs code examples. Abstract advice without examples is hard to apply.
Why
- Actionable: Code shows exactly what to do
- Unambiguous: Examples eliminate interpretation guesswork
- Memorable: Concrete patterns stick better than abstract principles
- Verifiable: Readers can compare their code to the example
Bad: Abstract Advice
# Bad: Too vague
"Keep your code organized and maintainable."
"Use appropriate design patterns."
"Structure your files logically."These don't help because they don't show what "organized" or "appropriate" means.
Good: Concrete Patterns
Show directory structures:
# Ruby example
"Place model-specific concerns in `app/models/model_name/`."
\`\`\`
app/models/
├── card.rb
├── card/
│ ├── closeable.rb # Card::Closeable
│ └── searchable.rb # Card::Searchable
└── concerns/ # Only shared concerns
└── mentionable.rb
\`\`\`
# TypeScript example
"Co-locate components with their tests and styles."
\`\`\`
app/components/
├── Button/
│ ├── Button.tsx
│ ├── Button.test.tsx
│ └── index.ts
└── Card/
├── Card.tsx
└── index.ts
\`\`\`The reader knows exactly where to put files.
Show the Transformation
When showing a pattern, include before and after:
# Ruby example
\`\`\`ruby
# Bad: Custom controller action
resources :cards do
post :close
end
# Good: Resource controller
resources :cards do
resource :closure, only: [:create, :destroy]
end
\`\`\`
# TypeScript example
\`\`\`typescript
// Bad: Inline conditional classes
<button className={`btn ${isActive ? 'btn-active' : ''} ${isDisabled ? 'btn-disabled' : ''}`}>
// Good: Using cn() utility
<button className={cn("btn", { "btn-active": isActive, "btn-disabled": isDisabled })}>
\`\`\`The contrast makes the improvement obvious.
Use Real Code
Patterns from real codebases are more convincing:
# Ruby example from Fizzy:
\`\`\`ruby
module Card::Closeable
extend ActiveSupport::Concern
included do
has_one :closure, dependent: :destroy
scope :closed, -> { joins(:closure) }
end
def close
create_closure!(user: Current.user)
end
end
\`\`\`
# TypeScript example from a React codebase:
\`\`\`typescript
function Button({ className, variant, children }: ButtonProps) {
return (
<button
className={cn(
"inline-flex items-center rounded-lg font-medium",
{
"bg-teal-500 text-white": variant === "primary",
"bg-neutral-100 text-neutral-900": variant === "secondary",
},
className
)}
>
{children}
</button>
);
}
\`\`\`Real code shows that the pattern actually works in production.
Match Example Complexity to Rule Complexity
Simple rules get simple examples:
# Ruby: Simple rule, simple example
Use `_later` suffix for async methods.
\`\`\`ruby
def notify
# sync
end
def notify_later
NotifyJob.perform_later(self)
end
\`\`\`
# TypeScript: Simple rule, simple example
Use named exports, not default exports.
\`\`\`typescript
// Bad
export default function formatCurrency() {}
// Good
export function formatCurrency() {}
\`\`\`Complex rules may need longer examples with comments:
# Ruby: Complex rule, annotated example
\`\`\`ruby
module CurrentAttributesJobExtensions
def initialize(...)
super
@account = Current.account # Capture at enqueue time
end
def serialize
super.merge("account" => @account&.to_gid) # Store in job payload
end
def perform_now
Current.with_account(account) { super } # Set before perform
end
end
\`\`\`
# TypeScript: Complex rule, annotated example
\`\`\`typescript
function useDebounce<T>(value: T, delay: number): T {
let [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
// Set up timer to update debounced value
let timer = setTimeout(() => setDebouncedValue(value), delay);
// Clean up timer on value change or unmount
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
\`\`\`Rules
1. Every rule must have at least one code example 2. Show bad/good contrast when applicable 3. Use real code from actual codebases when possible 4. Match example complexity to rule complexity 5. Abstract advice alone is not a rule
Explain Why, Not Just What
Don't just show what to do. Explain why it matters. Rules without reasoning feel arbitrary and are easy to ignore.
Why
- Buy-in: People follow rules they understand
- Judgment: Understanding "why" helps apply rules to edge cases
- Memory: Reasoning makes patterns memorable
- Trust: Explained rules feel like advice, not commands
Bad: Rules Without Reasoning
## Pattern
# Ruby example
Use `after_create_commit` instead of `after_create` for jobs.
\`\`\`ruby
after_create_commit :notify_later
\`\`\`
# TypeScript example
Use `useCallback` for event handlers passed to children.
\`\`\`typescript
const handleClick = useCallback(() => {
doSomething();
}, []);
\`\`\`These tell you what to do but not why. Someone might wonder: "What's wrong with after_create?" or "Why do I need useCallback?"
Good: Rules With Why
## Why
- **Transaction safety**: `after_create` runs inside the transaction; if it fails, the record isn't saved. `after_commit` runs after the transaction succeeds.
- **Job reliability**: Jobs enqueued in `after_create` might run before the transaction commits, causing "record not found" errors.
## Pattern
\`\`\`ruby
# Bad: Job might run before transaction commits
after_create :notify_later
# Good: Job runs after transaction is committed
after_create_commit :notify_later
\`\`\`## Why
- **Referential stability**: Without `useCallback`, the function is recreated every render, causing child components to re-render unnecessarily.
- **Dependency safety**: React hooks that depend on this function won't trigger infinite loops.
## Pattern
\`\`\`typescript
// Bad: New function every render, children re-render
function Parent() {
const handleClick = () => doSomething();
return <Child onClick={handleClick} />;
}
// Good: Stable reference, children don't re-render
function Parent() {
const handleClick = useCallback(() => doSomething(), []);
return <Child onClick={handleClick} />;
}
\`\`\`Now the reader understands the actual problem being solved.
Structure for Why Sections
Use bulleted list with bold benefit names:
## Why
- **Testability**: The sync method can be unit tested without job infrastructure
- **Flexibility**: Callers choose sync or async based on context
- **Clarity**: The `_later` suffix makes async behavior explicitEach bullet should be: 1. A bolded benefit name (one or two words) 2. A concrete explanation (one sentence)
Good Why Statements
Concrete and specific:
- "Jobs enqueued in
after_createmight run before the transaction commits" - "Model methods can be tested without spinning up job infrastructure"
- "Without
useCallback, the function reference changes every render" - "Named exports enable tree-shaking and better IDE autocomplete"
Bad Why Statements
Too vague:
- "It's better practice"
- "It's more maintainable"
- "It follows the principle of X"
Too theoretical:
- "This adheres to SOLID principles"
- "It reduces coupling"
- "It improves separation of concerns"
These aren't wrong, but they don't help someone understand the practical impact.
When to Skip Why
Some patterns are genuinely conventions without deep reasoning:
# Naming convention - just explain it
Ruby: Use `-able` suffix for behavior concerns: `Closeable`, `Searchable`.
TypeScript: Use PascalCase for components: `UserCard`, `OrderList`.But even here, you can add light reasoning:
# Better
Use `-able` suffix for behavior concerns. This communicates that the concern
adds a capability: something that "can be closed" or "can be searched."
Use PascalCase for components. This distinguishes components from regular
functions and matches React's convention for JSX tag detection.Rules
1. Every non-trivial rule needs a "Why" section 2. Use bulleted list with bold benefit names 3. Each bullet = one concrete, specific reason 4. Avoid vague phrases like "more maintainable" 5. Practical impact beats theoretical principles
Rule File Structure
Each rule file in rules/ follows a consistent structure: frontmatter, explanation, examples, and numbered takeaways.
Why
- Consistency: Same structure across all rules makes them easy to read
- Completeness: Structure ensures you cover why, what, and how
- Actionable: Numbered rules at the end give clear takeaways
Frontmatter
Every rule file starts with YAML frontmatter:
---
title: Human-Readable Rule Title
impact: HIGH
tags: [relevant, tags, here]
---Impact levels:
CRITICALorHIGH- Core patterns, always followMEDIUM- Important but with flexibilityLOW- Nice-to-haves, edge cases
Tags help with discovery and grouping.
Content Sections
Title and Introduction
# Rule Title
One paragraph explaining what to do. This is the quick summary someone
reads to understand the rule at a glance.Why Section
Explain the benefits with bullet points:
## Why
- **Benefit One**: Concrete reason this matters
- **Benefit Two**: Another tangible benefit
- **Benefit Three**: Third reason to follow this patternBold the benefit name, then explain. Each point should be a real, specific reason.
Pattern/Example Section
Show the main pattern with code. Lead with the bad pattern, then show good:
## Pattern
\`\`\`ruby
# Bad: Explanation of what's wrong
class BadExample
def scattered_logic
# logic here, there, everywhere
end
end
# Good: Explanation of improvement
class GoodExample
def focused_logic
delegate_to_model
end
end
\`\`\`
\`\`\`typescript
// Bad: Explanation of what's wrong
function BadExample() {
// logic scattered in component
const data = fetch(...);
const processed = data.map(...);
return <div>{processed}</div>;
}
// Good: Explanation of improvement
function GoodExample() {
const { data } = useProcessedData();
return <div>{data}</div>;
}
\`\`\`Additional Sections
Add sections as needed:
## When to Use This
Specific situations where this pattern applies.
## When NOT to Use This
Exceptions and alternatives.
## Real-World Example
Actual code from a codebase showing this pattern.
## Common Mistakes
Pitfalls to avoid.Rules Section
End with numbered takeaways:
## Rules
1. First concrete action to take
2. Second thing to remember
3. Third key pointKeep to 3-6 rules. These should be scannable action items.
Complete Examples
Ruby Example
---
title: Keep Jobs Thin
impact: HIGH
tags: [jobs, architecture]
---
# Keep Jobs Thin
Jobs should be thin wrappers that call model methods. All business logic belongs in the model layer.
## Why
- **Testability**: Model methods can be unit tested without job infrastructure
- **Reusability**: Same logic works sync or async
- **Debuggability**: Logic isn't buried in job classes
## Pattern
\`\`\`ruby
# Bad: Logic in job
class ProcessOrderJob < ApplicationJob
def perform(order)
order.items.each { |i| i.product.decrement!(:stock) }
order.update!(status: :processing)
end
end
# Good: Job delegates to model
class ProcessOrderJob < ApplicationJob
def perform(order)
order.process
end
end
\`\`\`
## Rules
1. Jobs call one method on the received record
2. All business logic lives in models
3. Namespace jobs to mirror model structureTypeScript Example
---
title: Use Named Exports
impact: MEDIUM
tags: [modules, imports, typescript]
---
# Use Named Exports
Use named exports instead of default exports for better tooling support and explicit imports.
## Why
- **Refactoring**: Renaming is easier when the name is explicit at export
- **Autocomplete**: IDEs can suggest imports automatically
- **Tree-shaking**: Bundlers can eliminate unused named exports
## Pattern
\`\`\`typescript
// Bad: Default export
export default function formatCurrency(amount: number) {
return `$${amount.toFixed(2)}`;
}
// Importing - name can be anything, easy to mismatch
import format from "./format";
// Good: Named export
export function formatCurrency(amount: number) {
return `$${amount.toFixed(2)}`;
}
// Importing - name must match, IDE autocompletes
import { formatCurrency } from "./format";
\`\`\`
## Exception
Remix route components use default exports by convention:
\`\`\`typescript
// app/routes/dashboard.tsx
export default function Dashboard() {
return <div>...</div>;
}
\`\`\`
## Rules
1. Use named exports for utilities, hooks, and components
2. Default exports only for framework conventions (routes)
3. One export per file is fine, still use namedRules
1. Start with frontmatter (title, impact, tags) 2. One-paragraph intro explains the rule 3. "Why" section has bulleted benefits 4. Show bad/good code examples 5. End with numbered takeaways (3-6 items) 6. Add extra sections only when needed
Skill Directory Structure
Every skill lives in its own directory with a consistent structure: a main SKILL.md file and a rules/ subdirectory containing individual rule files.
Why
- Discoverability: Consistent structure means agents know where to find things
- Scalability: Individual rule files keep content manageable
- References: SKILL.md can link to detailed rules with
@rules/rule-name.md
Structure
skills/
└── topic-best-practices/
├── SKILL.md # Main summary file
└── rules/
├── rule-one.md # Detailed rule
├── rule-two.md # Another rule
└── rule-three.md # And anotherNaming Conventions
Skill directories: {topic}-best-practices
ruby-on-rails-best-practices/
frontend-react-best-practices/
frontend-testing-best-practices/
skill-writing-best-practices/Rule files: kebab-case.md
model-scoped-concerns.md
thin-controllers.md
current-in-other-contexts.mdWhat Goes Where
SKILL.md contains:
- Frontmatter with name and description
- Overview and "When to Apply" section
- Condensed summaries of all rules with short examples
- Links to full rule files
*rules/.md** contain:
- Detailed explanation of one specific rule
- Full code examples with bad/good patterns
- Edge cases and exceptions
- Numbered takeaways
Rules
1. One skill = one directory with SKILL.md + rules/ 2. Directory names use {topic}-best-practices pattern 3. Rule files use kebab-case.md naming 4. SKILL.md summarizes; rules/ files go deep
SKILL.md Structure
The main SKILL.md file has four parts: frontmatter, overview, rules summary, and optional philosophy section.
Why
- Quick reference: Agents can scan SKILL.md to find relevant rules fast
- Context: Frontmatter helps agents decide when to load this skill
- Depth on demand: Summary links to detailed rules when needed
1. Frontmatter
YAML frontmatter with name and description:
---
name: topic-best-practices
description: Brief description of what this skill covers. Mention when to use it.
---The description should help agents understand when to reference this skill. Include trigger conditions if relevant.
2. Overview
Title, intro, and application guidance:
# Topic Best Practices
Brief intro about what's covered. Mention rule count and categories.
## When to Apply
Reference these guidelines when:
- Doing X
- Working with Y
- Reviewing Z codeKeep the intro to 1-2 sentences. The bullet list helps agents quickly assess relevance.
3. Rules Summary
Group rules by category with impact levels. Each rule gets:
- Header linking to full file
- One-sentence description
- Short code example showing the core pattern
## Rules Summary
### Category Name (IMPACT)
#### rule-name - @rules/rule-name.md
One sentence explaining what to do.
\`\`\`ruby
# Bad
bad_example
# Good
good_example
\`\`\`
#### another-rule - @rules/another-rule.md
Another one-sentence explanation.Impact levels:
- CRITICAL/HIGH - Core patterns, always follow
- MEDIUM - Important but flexible
- LOW - Nice-to-haves
4. Philosophy (Optional)
End with core principles if the skill embodies a specific approach:
## Philosophy
These patterns embody X approach:
1. **Principle One** - Brief explanation
2. **Principle Two** - Brief explanationComplete Example
---
name: example-best-practices
description: Example patterns. Use when working with examples.
---
# Example Best Practices
Patterns for examples. Contains 3 rules in 2 categories.
## When to Apply
- Writing examples
- Reviewing example code
## Rules Summary
### Structure (HIGH)
#### example-structure - @rules/example-structure.md
Examples should be self-contained.
\`\`\`ruby
# Good: Complete example
def complete_example
setup
action
verify
end
\`\`\`
### Style (MEDIUM)
#### example-naming - @rules/example-naming.md
Use descriptive names.
## Philosophy
1. **Clarity** - Examples should be obvious
2. **Brevity** - Show only what mattersRules
1. Frontmatter has name and description 2. Overview includes "When to Apply" bullets 3. Rules are grouped by category with impact levels 4. Each rule gets one sentence + short code example 5. Link to full rules with @rules/rule-name.md
Writing Style
Write naturally like documentation. Avoid AI-isms, excessive formatting, and filler phrases.
Why
- Readability: Natural prose is easier to scan
- Trust: AI-sounding text feels generated, not authored
- Density: Removing filler packs more value per line
- Professionalism: Clean writing reflects clear thinking
Avoid Horizontal Rules
Don't use --- as section separators:
# Bad
## Section One
Content here.
---
## Section Two
More content.
---
# Good
## Section One
Content here.
## Section Two
More content.Headings already create visual separation.
Avoid Filler Phrases
Cut phrases that add no information:
# Bad
"In this section, we will explore the various ways in which..."
"It's important to note that..."
"As mentioned previously..."
"Let's take a look at..."
# Good
Just say the thing directly.Avoid Over-Formatting
Don't bold or bullet everything:
# Bad: Everything is emphasized
**Always** use `after_commit` for jobs because:
- **It ensures** transaction safety
- **It prevents** race conditions
- **It guarantees** data consistency
# Good: Emphasis is meaningful
Use `after_commit` for jobs. This ensures the transaction has committed
before the job runs, preventing race conditions where the job can't find
the record.Reserve bold for terms being defined or key concepts in lists.
Use Direct Language
# Bad: Passive and hedging
"It is recommended that consideration be given to..."
"One approach that could potentially be utilized..."
"It should be noted that in some cases..."
# Good: Direct
"Use X when Y."
"Consider X for Y situations."
"X doesn't apply when Y."Keep Paragraphs Short
Long paragraphs are hard to scan:
# Bad: Wall of text
When implementing the pattern you should consider that there are multiple
approaches and each has tradeoffs. The first approach involves X which has
the benefit of Y but the downside of Z. The second approach...
# Good: Broken up
Consider two approaches:
**First approach**: X. Benefits from Y but has Z downside.
**Second approach**: A. Better for B situations.Code Comments Should Be Minimal
Let code speak for itself:
# Bad: Over-commented
# This method closes the card by creating a closure record
# and then touching the updated_at timestamp
def close
# Create the closure record for this card
create_closure!(user: Current.user)
# Update the timestamp
touch
end
# Good: Comments add context code can't express
def close
create_closure!(user: Current.user)
touch # Triggers cache invalidation
endRules
1. No horizontal rules (---) as separators 2. Cut filler phrases that add no information 3. Reserve formatting (bold, bullets) for emphasis, not decoration 4. Use direct, active language 5. Keep paragraphs short and scannable 6. Code comments explain why, not what