TnsAI
CapabilitiesSkills

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.md

Supporting 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)
---
FieldTypeDefaultMeaning
namestringparent dirStable identifier; case-insensitive on lookup.
descriptionstringrequiredAlways in the system prompt; the resolver scores against it.
when-to-usestringemptyOptional context for the resolver — trigger phrases, example requests.
allowed-toolslist of stringsemptyTools auto-granted while this skill is active. Empty = no constraint.
argument-hintstringemptyFree-form hint shown in catalog listings.
argumentslist of stringsemptyPositional argument names for argument-hint.
disable-model-invocationbooleanfalseIf true, the LLM may not activate this skill — only the user (or Agent.invokeSkill(...)) can.
user-invocablebooleantrueIf false, hidden from the user-facing menu and rejected on /skill-name. Programmatic activation still works.
pathslist of glob patternsemptyv2 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:

CanonicalAliases
when-to-usewhen_to_use
allowed-toolsallowed_tools
argument-hintargument_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

PlaceholderResolves to
$ARGUMENTSAll 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 Skill with blank name (after defaulting from the directory name) — every skill must have a name.
  • A Skill with blank description — 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-use for trigger phrases. The keyword resolver weights when-to-use matches 2× and name matches 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.md for 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

On this page