Writing a skill

The SKILL.md format Agent Handler accepts, the limits it enforces, and how to write a description agents actually match.

A skill is a SKILL.md file. Everything else is optional. Write the instructions you would give a capable new hire who has your tools but not your context, then upload the file.

This page covers the format Agent Handler validates on upload and the authoring choices that decide whether an agent finds your skill and follows it.

The smallest valid skill

SKILL.md
1---
2name: Expense report review
3description: Review a submitted expense report against the T&E policy and either approve it or send it back with the specific line items that need receipts. Use when someone asks to review, approve, or check an expense report.
4---
5
6# Expense report review
7
8## Steps
9
101. Pull the report with `expensify__get_report`. Note the submitter and total.
112. Check every line item over $75 for an attached receipt.
123. Check the report against the T&E limits in `references/te-policy.md`.
134. Approve if every line passes. Otherwise reject with the failing line items named individually.
14
15## Rules
16
17- Never approve a report you submitted yourself. Hand those to the submitter's manager.
18- A missing receipt is a send-back, not a judgment call.
19- Quote the exact policy line when you reject something, so the submitter knows what to fix.

Upload that file on its own. You do not need to zip a single-file skill.

Frontmatter

SKILL.md starts with YAML frontmatter between --- lines, then the Markdown body.

KeyRequiredWhat it does
nameYesDisplay name. Must be a non-empty string. Also the default source of the skill’s slug
descriptionYesMust be a non-empty string. Shown on the card and returned by list_skills, and the main thing that decides whether an agent picks the skill
referenced_connectorsNoList of connector slugs the skill expects, for example ["expensify", "slack"]. Surfaces the connector logos on the card

Any other keys you include are preserved on the version record but Agent Handler does not act on them.

referenced_connectors is a declaration, not a grant and not a precondition. Slugs are not checked against your connected connectors, so a typo shows up as a missing logo rather than an error, and naming a connector never gives the agent access to it.

Write the description for the match, not for the reader

list_skills returns each skill’s name and description, and its keyword filter matches on those two fields only. The body of your skill is never searched. So a description that reads well but omits the words people actually use makes the skill invisible.

Say what the skill does, then say when to use it, in the vocabulary of the person asking.

1# Weak: accurate, unmatchable
2description: Handles the review workflow for submitted reports.
3
4# Strong: names the object, the action, and the trigger phrases
5description: Review a submitted expense report against the T&E policy and either approve it or send it back with the line items that need receipts. Use when someone asks to review, approve, or check an expense report, or mentions a reimbursement that is stuck.

Two skills whose descriptions overlap heavily will both match, and the agent has to guess. If you find yourself writing near-identical descriptions, that is usually one skill with a branch in the body, not two skills.

Bundling reference files

Put anything long, rarely needed, or reference-shaped in a separate file and point to it from the body. Agents read SKILL.md first and receive the list of bundled files with it; they fetch a bundled file only when they need it.

expense-report-review.zip
├── SKILL.md # entry point: steps, rules, pointers
├── references/
│ ├── te-policy.md # the full policy text
│ └── escalation-matrix.md # who approves what over which amount
└── examples/
└── rejection-message.md # the tone to use when sending one back

Zip the bundle with SKILL.md at the root, not nested inside a folder. Reference bundled files by their relative path in the body (references/te-policy.md), because that path is exactly what the agent passes back to fetch the file.

The split to aim for: SKILL.md holds the procedure and the decisions, and bundled files hold the material the procedure consults. A SKILL.md that inlines a 4,000-word policy costs every agent that context on every retrieval, whether it needs the policy or not.

Limits

Agent Handler validates the bundle on upload and rejects it whole if anything fails. Nothing in a bundle is ever executed.

LimitValue
Upload size, compressed10 MB
Bundle size, uncompressed10 MB
Any single file2 MB
File count100
SKILL.md length20,000 characters

SKILL.md must be UTF-8 and must sit at the root of the bundle. Symlinks are rejected. Paths that try to escape the bundle (absolute paths, .. segments, Windows drive prefixes) are rejected. An archive whose uncompressed size exceeds its compressed size by more than 100x is rejected.

The 20,000 character cap on SKILL.md is a design constraint as much as a safety one. If your entry point is pushing it, the material that pushed it belongs in a bundled file.

What gets rejected, and what to fix

ErrorCause
Bundle must contain a SKILL.md at its rootSKILL.md is nested in a folder, or named differently. Re-zip from inside the skill directory
SKILL.md must start with YAML frontmatter delimited by '---'Missing or malformed --- fences. The opening fence must be the first line
SKILL.md frontmatter is not valid YAMLUsually an unquoted colon inside a description. Quote the value
SKILL.md frontmatter is missing a non-empty 'name'name or description is absent, blank, or not a string
frontmatter 'referenced_connectors' must be a list of slugsWritten as a comma-separated string instead of a YAML list
The 'merge-' slug prefix is reservedYour skill’s name slugifies to something starting with merge-. Rename the skill, since the slug follows the name
Symlinks are not allowed in a skill bundleThe zip preserved a symlink. Copy the real file in instead

You can check a bundle before committing to it: the upload flow validates and previews the parsed name, slug, description, and file list before anything is saved. See Publishing skills.

Patterns worth copying

  • Lead with when to use it. The first thing in the body should tell an agent whether it is in the right place. An agent that retrieved the wrong skill should be able to tell immediately.
  • Number the steps. Ordered steps get followed in order. A prose paragraph describing the same sequence gets summarized.
  • Name real tools. Writing expensify__get_report rather than “the Expensify API” removes a guess. If the tool name changes, the skill is wrong in an obvious way rather than a subtle one.
  • Write the rules as rules. “Never approve your own report” belongs in its own line under a Rules heading, not buried in step 4.
  • State the stop conditions. Say what the agent should do when the data is missing or ambiguous. Absent that, it will pick something.

What to avoid

  • Do not put credentials, tokens, or keys in a skill. A skill is readable by every member it is published to. Credentials belong in Application Credentials.
  • Do not use a skill to grant access. Naming a connector does not connect it. If an agent lacks a tool, fix the Tool Pack.
  • Do not write one skill for six unrelated jobs. Retrieval is all or nothing, so a combined skill spends context on five procedures the agent did not need.
  • Do not restate the tool schema. Agent Handler already gives the agent each tool’s parameters. Spend the space on judgment the schema cannot carry.
  • Do not describe your org chart in prose. Escalation paths and approval thresholds are tables, and usually belong in a bundled reference file.

Next: Publishing skills