Skills format
A skill is reusable know-how that an agent loads when it needs it: a folder with a SKILL.md file and optional text resources, in the open Agent Skills format. This page is for anyone who writes, imports or reviews skills. Since it is plain prompt text plus one gateway tool, it works the same with every model provider.
On this page
- What a skill package is
- SKILL.md frontmatter
- Complete example
- Package limits
- Built-in starter skills
- Lifecycle and review
- Scanning, import and export
- Skills API routes
- Assignment to agents
- How skills reach the model
- Limits and cleaning of loaded text
What a skill package is#
A package is a folder whose only required file is SKILL.md: a YAML frontmatter block followed by markdown instructions. It may include supporting files such as templates, examples, reference documents or scripts, always as text.
Each version stores its files in the organization's Drive, at /Shared/Skills/<name>/v<n>/, and the database keeps the manifest: path, size and sha256 of each file. Versions are immutable; restoring an old version creates a new version with the same content.
client-email/
├── SKILL.md
└── examples/
└── follow-up-example.md
SKILL.md frontmatter#
SKILL.md must be valid UTF-8, start with a --- line and close the block with another one. The instructions after the frontmatter cannot be empty.
The parser understands a small subset of YAML, on purpose:
- Scalars, quoted strings and
>or|blocks. - Inline or block lists.
- A single level of nested map, only for
metadata. - It rejects anchors, aliases, tags, inline maps and tabs in indentation.
| Field | Required | Rule or limit |
|---|---|---|
name | Yes | Pattern ^[a-z0-9-]{1,64}$. Unique per organization and immutable once created. |
description | Yes | Up to 1024 characters. It is what the agent reads to decide whether to use the skill: say what it does and when to use it. |
license | No | Free text. |
compatibility | No | Free text; preserved on export. |
allowed-tools | No | Up to 20 entries. Informational: it never grants tools. |
metadata | No | Key-to-text map, up to 30 entries (for example category). |
Note
Other top-level scalar keys are preserved on import and export (up to 20 keys and 300 bytes per value), so a round trip loses nothing. The in-app editor does not show compatibility or those extra keys: saving from the editor drops them.
Complete example#
This is the SKILL.md of the English client-email skill, exactly as the backend ships it. The name uses lowercase and hyphens, the description says what it does and when to use it, and metadata carries the category and the built-in marker.
---
name: client-email
description: "Write a clear email to a client: subject, brief context, a concrete request or answer and the next step. Use it for replies, follow-ups, friendly collections and announcements."
metadata:
category: communication
builtin: "true"
---
# Client email
## When to use it
To write replies, follow-ups or notices to clients.
## Steps
1. Identify the goal in one sentence: inform, ask, confirm, collect or apologize.
2. Specific subject (at most 8 words) with the topic and, if relevant, the date or reference.
3. First paragraph: the answer or request. Then the minimum context.
4. Close with a single next step, who does it and by when.
5. Adjust the tone: warm and professional; in complaints acknowledge the problem first; in collections be firm and kind.
## Quality criteria
- Under 150 words unless more is needed.
- No figure, date or commitment that is not in the task data.
- Check names, amounts and attachments mentioned.
## Limits
Leave the email as a draft; sending it is decided by a person or by the organization policy.
Package limits#
These caps are validated on create, edit and import. A violation returns a stable error code, for example file_too_large, too_many_files, extension_not_allowed or zip_bomb.
| Limit | Value |
|---|---|
| Uploaded file on import | 4 MB |
| Total size of a version | 2 MB |
| Size of one file | 512 KB |
Files per skill (including SKILL.md) | 40 |
| Zip entries inspected | 300 |
| Paths | Up to 4 levels (the file name counts) and 160 characters. Each segment starts with a letter or digit and uses A-Z, a-z, 0-9, ., _ or -, up to 64 characters. |
| Allowed extensions | md, txt, csv, json, yaml, yml, py, js, sh (UTF-8 text only, no NUL bytes) |
| Compression ratio of a zip entry | Up to 100 to 1 (checked on entries of 64 KB or more) |
Built-in starter skills#
The backend ships a catalog of starter skills embedded in the binary, in Spanish and English, in the same format as an import. There are 55 per language today, including bank-reconciliation, b2b-proposal, service-contract-review, month-end-close, client-email and project-plan.
The first time an organization opens the library, they are created as active in its language. If the organization already has any skill, even a deleted one, none are created, and deleted starters do not come back. Role templates declare default_skills (names only) and agents of that role receive them without needing an assignment.
backend/internal/skills/builtin/
├── es/<name>/SKILL.md
└── en/<name>/SKILL.md
Lifecycle and review#
A skill is in one of four statuses: draft, in_review, active or disabled. Members (skills:write permission) create drafts, edit, import and restore versions. Administrators (skills:review permission) approve, reject, disable and delete.
- An import always enters
in_review. - If a member edits an active skill, the new version lands in
in_reviewand agents keep using the approved one. If an administrator edits it, the edit counts as the review and it becomes active. - A review names the version the reviewer read: if it is no longer current, it fails with
stale_review. File hashes are checked again before activation. - A rejection stores a note and returns the skill to
draft, or keeps it active with the previous approved version when there is one. - Deletion is soft and removes assignments. Creating another skill with the same name brings it back with a new version.
create (member) → draft ──submit──▶ in_review ──approve (admin)──▶ active
import (member) → in_review (always)
reject → draft, or stays active with the previous approved version
active ──disable (admin)──▶ disabled · delete (admin, soft)
Scanning, import and export#
Every saved version is scanned and the warnings are stored for the reviewer. The scan informs; it does not decide: the reviewer sees everything before approving.
injection: a file's injection score exceeds the threshold.script: the package ships executable files as text (py,js,sh).allowed_tools: the frontmatter declaresallowed-tools.import_note: the import adjusted something, for example it skipped an empty file.
- Import a
.zipor a singleSKILL.mdwithPOST /api/v1/skills/import(multipart fieldfile). - Validation is strict: it rejects absolute paths or paths with
.., backslashes, drive letters, symlinks and special files, encrypted entries, duplicates that differ only in case, and zip bombs. It strips a single wrapper folder and ignores__MACOSX,.DS_StoreandThumbs.db. - An administrator reviews the version and approves or rejects it.
- To take it out,
GET /api/v1/skills/{id}/exportreturns a deterministic zip in the standard layout (<name>/SKILL.mdplus resources, no executable bits), or onlySKILL.mdwith?format=md.
Note
Import from a URL deliberately does not exist: it would need an SSRF-safe downloader.
Skills API routes#
Every route lives under /api/v1, behind authentication and per-organization isolation. A review body carries the decision (approve or reject), the reviewed version and a note.
POST /api/v1/skills/{id}/review
Content-Type: application/json
{ "decision": "approve", "version": 3, "note": "Checked tone and limits" }
| Route (under /api/v1) | Permission | What it does |
|---|---|---|
GET /skills | skills:read | Lists the organization's skills. |
POST /skills | skills:write | Creates a draft from the editor. |
POST /skills/import | skills:write | Imports a .zip or a SKILL.md; it enters in_review. |
PUT /skills/{id} | skills:write | Saves a new version. |
POST /skills/{id}/versions/{n}/restore | skills:write | Creates a new version with the content of version n. |
POST /skills/{id}/submit | skills:write | Moves from draft to in_review. |
POST /skills/{id}/review | skills:review | Approves or rejects a specific version. |
POST /skills/{id}/disable | skills:review | Stops serving the skill to agents. |
GET /skills/{id}/export | skills:read | Exports the zip or, with ?format=md, only SKILL.md. |
Assignment to agents#
An assignment links a skill to an agent through PUT and DELETE on /api/v1/agents/{id}/skills/{skillId}, with the agents:write permission. There are also role defaults: the names in a role template's default_skills reach every agent of that role without an explicit assignment.
An agent only receives a skill if it is assigned (or is a default of its role), is neither deleted nor disabled, and has an approved version. While an edit waits for review, the approved version is served. In any other case the load is denied with skill_not_available.
How skills reach the model#
Disclosure is progressive, in two steps. On every run, the backend adds to the request only the name and description of the skills the agent can use, with caps: at most 12 skills, 240 characters per description and 3000 bytes in total. The runtime renders them in the prompt with an explicit rule: a skill guides how to work and never grants tools, connections, permissions, autonomy or approvals.
If a skill applies, the agent asks for it with a tool request. The Tool Gateway serves it as a native read tool, subject to the kill switch, pause and audit, and answers with the approved version's body without frontmatter plus the file list. With file it returns a single resource.
{ "tool": "skill", "action": "load", "args": { "name": "client-email", "file": "examples/follow-up-example.md" } }
Limits and cleaning of loaded text#
Once loaded, the skill enters the task context with several safeguards:
- The text travels in its own channel (
loaded_skills) as trusted organization configuration, inside dedicated delimiters, because a person approved that exact version. - It is still cleaned (invisible characters, credentials, forged delimiters) and scored for injection.
- It is bounded in the backend (24,000 characters per answer, 4 loads per task) and again in the runtime (3 skills, 12,000 characters each, 20,000 in total).
- Scripts are inert text: running them requires the code tool, with human approval.