Agent Skills Series (2): `SKILL.md`, progressive disclosure, and a minimal example

📌 About this series

If you already buy that “what AI lacks is your team’s default wiring” (see part 1), the next question is: in what shape do those conventions reach the model, and when do they actually enter the context. This post is only about how to write and structure: what goes in SKILL.md, where long material lives, and why the runtime does not dump every Skill’s full text into the conversation up front. For install, search, publishing, and keeping the team in sync on Skills, see Tooling; for what deserves a Skill and how to narrow triggers, see What deserves a Skill and Fragmentation.


💡 What is an Agent Skill?

An Agent Skill is structured guidance the runtime can discover first and expand on demand. The standard shape is a skill directory: the root must contain SKILL.md (YAML front matter + Markdown body); the same folder may hold references/, scripts/, and other bulky material—you maintain it like docs, but you do not have to paste the whole playbook into every chat by hand.


🪜 Progressive disclosure: why “20 Skills installed” does not “blow up the context”

This is a core design idea in Agent Skills: you can have many capabilities, but discovery stays cheap by default; detail loads only when it is actually needed.

LayerWhat usually loadsRole
Layer 1: metadataYAML in SKILL.md: name, description (plus other short fields your stack allows)Scanned at startup or index time: the model/client can answer “what Skills exist and when each should be considered”
Layer 2: instruction bodyMarkdown in SKILL.md after the front matter: flows, constraints, no-go zones, output formatsLoads when the user’s need matches the scenario description points at—put “how we work” here
Layer 3: attachmentsLong docs under references/, runnable scripts under scripts/, big templates, etc.Read or run only when needed—avoid dumping tens of thousands of words of spec into context every session

Watch these points:

  • description is not a marketing blurb—it is a trigger spec: spell out what user phrasing / task types should turn this Skill on, or it never gets picked—or the description is so broad it fights other Skills for triggers (the fragmentation post goes deeper).
  • Long examples, API tables, copy-paste blocks belong in references/ first; if there is a lot, split into several .md files under references/ and use one or two sentences in SKILL.md to say when to read which file.

📁 What a Skill directory looks like (structure)

The smallest shape is one folder + one file:

vue3-form-generator/
└── SKILL.md

When you need progressive expansion, a common layout is:

vue3-form-generator/
├── SKILL.md              # Required: metadata + main instructions
├── references/
│   ├── form-templates.md      # Optional: long examples, field dictionaries
│   ├── login-form.md          # Multi-file examples
│   └── api-reference.md       # More reference docs
└── scripts/
    └── validate.mjs      # Optional: scripts when the toolchain allows (execution depends on the client)

The YAML front matter at the top of SKILL.md will at least use:

  • name: stable, human-readable skill id.
  • description: when to enable + one line on scope (for layer-1 discovery).

Write the body so a newcomer can follow it on first open—not like a press release. A practical outline: goal → important conventions / forbidden items → scenario-to-approach table → acceptance criteria → (optional) pointers to big examples in references/.


🔍 Real example: “main flow + multiple references/ files” in vue-best-practices

vue-best-practices in the vuejs-ai/skills repo does the “short main Skill, details outsourced” pattern well: SKILL.md at the root states workflow order and must-dos; long rules are split across several Markdown files under references/, matching the diagram above.

Rough layout (20+ topic files under references/; only a few shown):

vue-best-practices/
├── SKILL.md
└── references/
    ├── reactivity.md
    ├── sfc.md
    ├── component-data-flow.md
    ├── composables.md
    ├── component-slots.md
    ├── state-management.md
    └── … (other files omitted)

SKILL.md orchestrates “read these first, open these when needed.” Below is an excerpt of YAML header + required core references + one on-demand section (English as in the repo, for path alignment):

---
name: vue-best-practices
description: MUST be used for Vue.js tasks. Strongly recommends Composition API with `<script setup>` and TypeScript as the standard approach. Covers Vue 3, SSR, Volar, vue-tsc. Load for any Vue, .vue files, Vue Router, Pinia, or Vite with Vue work. ALWAYS use Composition API unless the project explicitly requires Options API.
license: MIT
metadata:
  author: github.com/vuejs-ai
  version: "18.0.0"
---

# Vue Best Practices Workflow

Use this skill as an instruction set. Follow the workflow in order unless the user explicitly asks for a different order.

## 1) Confirm architecture before coding (required)

### 1.1 Must-read core references (required)

- Before implementing any Vue task, make sure to read and apply these core references:
  - `references/reactivity.md`
  - `references/sfc.md`
  - `references/component-data-flow.md`
  - `references/composables.md`
- Keep these references in active working context for the entire task, not only when a specific issue appears.

## 3) Consider optional features only when requirements call for them

- Slots: parent needs to control child content/layout -> [component-slots](references/component-slots.md)
- Fallthrough attributes: wrapper/base components must forward attrs/events safely -> [component-fallthrough-attrs](references/component-fallthrough-attrs.md)
# … (performance, async components, etc. link to their own reference files)

This shows: references/ can hold many fragments, but SKILL.md makes load order explicit with workflow + required list + on-demand links—you do not need every .md in full context by default; expand by task phase.


✅ Simplest single-file example (make it work first)

This matches the pattern from part 1—e.g. “HTTP must go through the team wrapper; components must use our shell”—in short, write the conventions down so the model does not invent the flow. Below is a single-file sketch you can copy; as it grows, move long bits under references/ as above.

---
name: vue3-project-standards
description: Standards for Vue 3 admin (back-office) development. Invoke only when building Vue 3 admin dashboards or internal systems.
---

# Vue 3 admin project standards

## Background
These rules apply to admin (back-office) projects on Vue 3 + Element Plus. For public-facing ToC products, Element Plus is not recommended by default.

## UI components
- Tables: always `<ProTable>`
- Dialogs: always `<DialogV2>`
- Forms: always `<FormRenderer>`

## HTTP
- Must use the team’s `request.js` wrapper
- Do not call axios/fetch directly
- Handle errors in the response interceptor; do not sprinkle try/catch in business code

## Example
(Full login form implementation can live in `references/login-form.md`; this section is illustrative only.)

With this Skill, for “write a login form,” the model is nudged toward your wrappers instead of whatever looks generic from training data.


⚖️ Side by side: without Skill vs with Skill

ScenarioNo SkillWith Skill
Ask AI for a login formRaw <form> + ad hoc requests<FormRenderer> + request.js
Ask AI to call an APIaxios / fetch however it likesOne wrapper + error-handling rules
Param style / API prefixDrifts toward “common on the internet”Defaults to your project rules

🛠️ Walkthrough: “form generator” as the standard pattern

Below is a cleaned-up SKILL.md body: field names, conventions, and forbidden items. For a full Vue SFC, prefer references/standard-login.vue.md and one line in the main file: “read this before filling the form.” To avoid nested-fence rendering issues, the template is split into three chunks below—in real use, merge into one main file or one reference file.

Part 1: front matter through standard form structure

---
name: vue3-form-generator
description: Vue 3 form generator rules. Use when the user asks for a login form, registration form, or any form with input fields.
---

# Vue 3 form generator standards

## Goal
All form code must follow this spec.

## Stack
- Vue 3 Composition API
- UI: Element Plus (required)
- HTTP: `request.js` (no axios/fetch)
- Form container: `<FormRenderer>`

## Component map
| Need | Component |
|------|-----------|
| Short text | `<el-input>` |
| Password | `<el-input type="password">` |
| Select | `<el-select>` |
| Date | `<el-date-picker>` |
| Toggle | `<el-switch>` |

## Validation
1. Required fields must have `rules`
2. Phone: `/^1[3-9]\d{9}$/`
3. Password: at least 8 characters, with both letters and digits

## Code templates

### Standard form structure
(Lots of `<template>` / `<script setup>` code can live in references or continue below.)

Part 2: Vue sample (strongly recommend ending up in references/)

<template>
  <FormRenderer
    :schema="formSchema"
    v-model="formData"
    @submit="handleSubmit"
  />
</template>

<script setup>
import { reactive } from 'vue';
import { FormRenderer } from '@/components';
import { request } from '@/utils/request';

const formData = reactive({
  username: '',
  password: '',
  phone: ''
});

const formSchema = [
  {
    prop: 'username',
    label: 'Username',
    component: 'input',
    rules: [{ required: true, message: 'Please enter username' }]
  },
  {
    prop: 'password',
    label: 'Password',
    component: 'password',
    rules: [
      { required: true, message: 'Please enter password' },
      { min: 8, message: 'Password must be at least 8 characters' }
    ]
  },
  {
    prop: 'phone',
    label: 'Phone',
    component: 'input',
    rules: [
      { required: true, message: 'Please enter phone' },
      { pattern: /^1[3-9]\d{9}$/, message: 'Invalid phone number' }
    ]
  }
];

const handleSubmit = async () => {
  await request.post('/api/form/submit', formData);
};
</script>

Part 3: forbidden items (right after the example)

## Forbidden
- Do not build business forms with raw `<form>` + `<input>`
- Do not use `axios` or `fetch` directly
- Do not hard-code API path prefixes (use shared config / wrappers)

📋 Three blocks every good Skill needs

BlockWhat it doesExample
Hard constraintsWhat must not happen; what must happenNo axios; must use request.js
Lookup tablePin requirements to concrete building blocksTable UI → ProTable
Copy-paste templateWorking code or a path to the referenceStandard FormRenderer schema

☑️ Should you write a checklist?

Short answer: yes—it helps.

The first three blocks teach how; a checklist asks whether you are done. It writes down defaults that live in your head and prevents two classes of misses:

  • Format looks right, but a forbidden library slipped in (e.g. fetch again).
  • It looks complete, but important facts are missing (time range, audience, etc.).

Split checklists at two levels:

  • A general checklist at the end of SKILL.md, run whenever this Skill is used.
  • A checklist at the end of each scenario template in references/, for scenario-specific extras (e.g. a daily report must have three clear sections: yesterday, today, blockers).

Example:

## Acceptance checklist
- [ ] Task type identified and mapped to a single template (or explain why the generic template is used)
- [ ] Context filled in where needed (audience, time range, goal)
- [ ] Hard constraints and forbidden items respected (stack, format, tone)
- [ ] Output structure matches the template (field count, field formats)
- [ ] Uncertain bits called out explicitly (no guessing, no inventing)

You do not need a dozen lines—five or fewer, each easy to judge pass/fail, is enough.


✏️ Six rules for writing Skills

Drawing on Agent Skills best practices (Claude CN) and the points above, use these six when you write SKILL.md:

  1. description should say when it fires, not sell the Skill
    Prefer “what + when + phrases users actually say.” Narrow and precise beats vague and universal, or multiple Skills will fight for the same triggers.
  2. Keep decisions and constraints in the body; long text in references/
    Keep the main file short; split out big API lists, templates, glossaries, and add one line on when to open which file.
  3. Smallest working example first, advanced usage later
    Let someone succeed in one pass at the top; add depth and edge cases after, not in one overwhelming dump.
  4. Scripts for deterministic ops; judgment calls stay in prose
    Use scripts for formatting, batch transforms, etc.; business judgment and flow live explicitly in Markdown.
  5. State limits clearly—do not expect the model to guess
    Spell out supported scope, prerequisites, and what to do when information is missing, or the model will invent.
  6. End every template with a checklist
    Global checklist in the main file; per-scenario checklists in each template. Few items, each clearly pass/fail.

If you want one quick accuracy win, do rule 1 first: tighten description so the Skill triggers when it should and stays off when it should not.


➡️ Next: publishing and distribution

Writing the Skill is only step one. Teams ask: where do files live? Who publishes? How do others install? How do we avoid a pile of duplicate Skills? Tooling covers skill-base and skb in detail. When the library grows, how to narrow description and keep a single source of truth is in What deserves a Skill and Fragmentation.

skill-base: Website · GitHub — a private hosting platform built for distributing Agent Skills across a team.


🧭 Series map (sibling posts)

PartPostWhat it covers
1Pain points and motivationWhy Skills
3Toolingskill-base / skb distribution and install
4What deserves a SkillChoosing topics and a bar for shipping
5Admin query page case studyA project-level Skill refactor example
6Operating playbookTriggers, conflicts, and version governance
ExtraFragmentationMulti-Skill triggers and governance

📚 Further reading