SKILL.md format
A SKILL.md file is a YAML frontmatter block followed by a markdown body. The framework's parser (SkillMdParser) accepts the same shape Claude Code's parser does, so a skill authored once works in both environments.
File layout
<root>/<skill-name>/SKILL.md — the directory name is the default skill name when the frontmatter is silent on name. FileSystemSkillStore scans <root>/ and treats every immediate child directory containing a SKILL.md as a registered skill.
.tnsai/skills/
├── deploy/
│ ├── SKILL.md
│ ├── reference.md (optional, lazy-referenced by body)
│ └── scripts/precheck.sh (optional, executed by skill body)
├── lint/
│ └── SKILL.md
└── customer-escalation/
└── SKILL.mdSupporting files (reference.md, scripts, …) are NOT loaded eagerly — the body declares them by relative path and consumers fetch on demand.
Frontmatter reference
---
name: deploy # falls back to parent directory name
description: Production deploy procedure with rollback support
when-to-use: |
When the user asks to ship a build to prod or staging.
Trigger phrases: "deploy", "ship", "release", "rollout".
allowed-tools:
- bash
- kubectl
- github.create_pr
argument-hint: "<environment>"
arguments:
- environment
disable-model-invocation: false # default false
user-invocable: true # default true
paths:
- "infra/**" # v2 (file-context auto-activation)
---| Field | Type | Default | Meaning |
|---|---|---|---|
name | string | parent dir | Stable identifier; case-insensitive on lookup. |
description | string | required | Always in the system prompt; the resolver scores against it. |
when-to-use | string | empty | Optional context for the resolver — trigger phrases, example requests. |
allowed-tools | list of strings | empty | Tools auto-granted while this skill is active. Empty = no constraint. |
argument-hint | string | empty | Free-form hint shown in catalog listings. |
arguments | list of strings | empty | Positional argument names for argument-hint. |
disable-model-invocation | boolean | false | If true, the LLM may not activate this skill — only the user (or Agent.invokeSkill(...)) can. |
user-invocable | boolean | true | If false, hidden from the user-facing menu and rejected on /skill-name. Programmatic activation still works. |
paths | list of glob patterns | empty | v2 only: auto-activate when the agent is operating on matching files. |
Field aliases
The parser accepts both the kebab-case (agentskills.io standard) and snake_case (Claude Code docs) forms for fields where the docs are split:
| Canonical | Aliases |
|---|---|
when-to-use | when_to_use |
allowed-tools | allowed_tools |
argument-hint | argument_hint |
List shorthand
A scalar in place of a single-element list is accepted for allowed-tools, paths, and arguments:
allowed-tools: bash # equivalent to: ["bash"]CRLF tolerance
Files written on Windows / via git autocrlf=true parse identically — the parser normalises line endings before extracting the frontmatter.
Body
Anything between the closing --- delimiter and the end of the file is the skill body. The body is plain markdown — the framework does not parse it.
The body is rendered through SkillSubstitution before it lands in the system prompt, so placeholders are resolved at activation time (not at parse time).
Substitution
| Placeholder | Resolves to |
|---|---|
$ARGUMENTS | All positional arguments space-joined. |
$0, $1, … | The Nth positional argument (0-indexed). Out-of-range = empty string. |
${VAR} | Named env value supplied by the caller. Unknown name = empty string. |
$$ | Literal dollar sign — escapes the placeholder at that position. |
Substitution is single-pass and left-to-right — placeholder values are not re-substituted. A literal $ARGUMENTS passed as $0 stays literal in the output.
---
name: deploy
description: Deploy a build to a target environment
arguments:
- environment
---
# Deploy to $0
1. Verify CI is green for the build tagged `${BUILD_TAG}`.
2. `kubectl apply -f manifests/$0/`
3. Smoke-test https://$0.example.com/healthz.Invoked as /deploy staging with env={BUILD_TAG: "v1.4.2"}, the body renders as:
# Deploy to staging
1. Verify CI is green for the build tagged `v1.4.2`.
2. `kubectl apply -f manifests/staging/`
3. Smoke-test https://staging.example.com/healthz.Validation rules
The parser rejects:
- Files without an opening
---delimiter on the first line. - Files with an opening delimiter but no closing one.
- Frontmatter that is not valid YAML.
- A
Skillwith blankname(after defaulting from the directory name) — every skill must have a name. - A
Skillwith blankdescription— the resolver depends on it.
A malformed SKILL.md is logged and skipped by FileSystemSkillStore; sibling skills continue to load. One broken skill on disk does not poison the store.
Authoring tips
- Lead the description with the problem the skill solves, not the solution. The resolver scores by token overlap with the user's message — "deploy build" lands closer to "ship build" than "ship a release". Name the user's intent in your own words.
- Use
when-to-usefor trigger phrases. The keyword resolver weightswhen-to-usematches 2× andnamematches 3×; trigger phrases here move the skill ahead of competitors. - Keep the body procedural, not narrative. Numbered steps + concrete commands. The body is what the LLM follows once activated; flowery prose dilutes the signal.
- Reference supporting files by relative path. The body can say "see
reference.mdfor examples" — the consumer (LLM or human) fetches on demand instead of bloating the activation snapshot.
See also
- Skills overview — when to reach for skills vs tools / RAG / capabilities
- Registration — wiring a store + resolver into
AgentBuilder