Book a call
LESSON18mVERIFIED 2026-08-02 · CLAUDE CODE 2.1.220 · CODEX CLI 0.146.0 · GITHUB COPILOT CLI 1.0.77 · CURSOR 2026.07.23-e383d2b · ANTIGRAVITY CLI 1.1.9 · KIMI CODE CLI 0.31.1

Anatomy of a skill: frontmatter, arguments, and who invokes it

A skill is a header, a body, and a trigger. Whether the model can fire it itself — and what happens to the text you type after the name — is where harnesses split.

The file looks the same everywhere. The behaviour doesn't.

What's important is that the majority of skills look very similar across different tools, but they might work differently. For example, every skill starts with a small header usually containing name and description, which is followed by a wall of instructions (like a page). But if you try to use your colleague's skill in another tool it works only half the time, either:

  • Doesn't work at all

  • Works on the tasks it wasn't supposed to

  • After the name you type some text and it disappears

It turns out that what differs are only 3 things:

  • What the header is for

  • Where the text you type after the name goes

  • Who is allowed to use the skill

Understanding these three things enables you to read and create skills in any tool; not knowing them results in a silent "do-nothing" skill.

The header, and the one field that earns its place

In terms of the header it's pretty standardised — almost every skill starts with a small header with a name and description field. The only exception are cursor commands, which don't have a header at all, but it's clear what they are based on their file names.

If there's a name field it's just information; the power lies in the description. That's not documentation — in the tools that support model-initiated invocation of skills, the description is the only thing the model has to base its decision on, so it compares it with the current task and chooses this skill or doesn't.

For example if you put "helper for testing" as a description, the model will be like meh... Okay, 50/50, I can pick it. But if you put sth like "use when the user asks to run or write tests for the Rust workspace" — it has something to work with.

I'll talk about this in more detail further down; for now it's good to keep in mind that this field is not decorative but carries weight.

The other fields are optional settings, allowing you to customise:

  • Which tools it can access

  • On which model it runs

  • A hint at what its arguments are

But the description is also what decides who can invoke a skill — let's talk about it in the next section.

Who pulls the trigger

There are two entry points:

  • You type the name and hit enter, like /deploy

  • The model sees the description during a task and chooses this skill

If you work with a tool that doesn't support both of these, the latter might not be available. But most tools following the SKILL.md standard usually support both and have a toggle to disable the model's entry point. For example, in such a tool you can turn off model-initiated invocation of any skill that does smth like changing sth or sending messages — so /commit, /deploy, /send-slack-message will be run only when you type them.

Simpler tools based on a command file usually support only the first entry point (the user invoking the skill by typing its name); they can't initiate themselves during tasks. For these the description is just menu text — it's good to keep in mind that not every tool supports model-initiated invocation of skills, so make sure you enable it if you want your skill to be able to initiate this way. Half of "my skill never runs" cases are about using tools that couldn't support it.

Arguments are the part that doesn't travel

The last component which differs between tools is the arguments — the text you type after the name. It can be handled in different ways:

  • Inserted into the body of the skill at a certain token, like $ARGUMENTS or {{args}}

  • Appended to the end of the prompt

  • Or even ignored

The latter is especially important: if your skill's body relies on the second argument appearing in a certain position, it might not work as intended if you use it in another tool — so it works fine where you create it but becomes a silent do-nothing somewhere else.

If you want to make a skill that will work in every tool, put all the substantial part of the instructions into the body and the description, and keep using arguments only as a tool-specific feature.

I've listed the token used by particular tools below:

IN YOUR HARNESS

In Claude Code

The "skills" is a system in Claude Code to allow creating custom slash commands. For example, if you create a release-notes folder with a SKILL.md file in it within your project like so: .claude/skills/release-notes/SKILL.md, the /release-notes command will be available in the app. The same works for skills placed under ~/.claude/skills.

You can name the folders anything you want, it'll become a slash command. Here's an example of the SKILL.md file:

YAML
---
name: release-notes
description: Draft release notes from the commits since the last tag. Use when asked to write or update a changelog or release notes.
disable-model-invocation: true
argument-hint: [version]
---

Write release notes for version $ARGUMENTS, grouped by type, from the commits since the previous tag.

A skill will be available in two ways by default, both of them enabled:

  • Manually — you just type /release-notes 2.4.0

  • Automatically — the app "sees" this skill in the description, and runs it automatically if it recognises that it's needed

You can opt out from either of these behaviours:

  • If you set disable-model-invocation: true, the app will be unable to run this skill on its own, so it'll only be accessible by typing /release-notes 2.4.0

  • If you set user-invocable: false, the skill won't appear in the list of slash commands after typing /, but it'll still be accessible for the app

You can pass arguments in three ways:

  • Using $ARGUMENTS — it contains the entire argument string

  • Using slots starting from 0, like $0, $1, etc (so in the example above, 2.4.0 would be accessible via $0)

  • Creating an arguments: list in frontmatter and using it like $version

If you don't use the $ARGUMENTS variable in the body, the input will be automatically appended as ARGUMENTS: <value> instead of being discarded.

Also, the skills system is a natural evolution of custom commands, so if you had the .claude/commands/release-notes.md file, it'd still create /release-notes, just like with skills. Custom commands were "swallowed" by skills, so these are just alternative ways of expressing the same thing — the difference is that only skills support these frontmatter options.

In Codex CLI

The skills are a set of things you can use with the Codex CLI. They're defined by creating a folder with a SKILL.md in one of those places:

  • .agents/skills/<name> (repo level)

  • ~/.agents/skills/ (per user)

  • ~/.codex/skills/ (also per user, and also accessible from the CLI)

In a skill, the header should be relatively short. Here's an example:

YAML
---
name: release-notes
description: Draft release notes from the commits since the last tag. Explain exactly when this should and should not trigger.
metadata:
  short-description: Draft release notes
---

Write release notes grouped by type, from the commits since the previous tag.

This is what the agent will actually do upon invocation, but it's important to know that when it comes to resolving a skill for a given task, the only thing that matters are the name and description; the short description is a regular optional addition.

To invoke a skill, you can either run /skills in the Codex CLI (or $ if you want to use a specific one), or have the agent load it automatically when it recognises that it's appropriate given its description.

The automatic part of this behaviour can be turned off by setting allow_implicit_invocation: false in the neighbouring agents/openai.yaml file.

The only downside of skills compared to the custom prompts (which are also a thing in Codex, but are defined under ~/.codex/prompts/ and are never loaded automatically) is that for skills there's no argument placeholder documented. You can create one though with either $ARGUMENTS or $1 if you're using custom prompts.

These didn't merge into a single thing in Codex the way they did in Claude Code; they're two tools for two jobs. So, long story short, choose depending on your needs: go for a skill if you'd like it to be shareable and auto-invocable, and for a custom prompt if you need an argument.

In GitHub Copilot CLI

Handling skills in GitHub Copilot CLI is based on the copilot skill subcommands:

  • add

  • list

  • remove

Each skill is a SKILL.md file in the following locations:

  • .github/skills/

  • .claude/skills/

  • .agents/skills/

For personal skills, these locations are looked up under ~/.copilot/skills/ and ~/.agents/skills/.

Here's an example of a minimal skill:

YAML
---
name: release-notes
description: Draft release notes from the commits since the last tag. Use when asked to write or update a changelog.
---

Write release notes grouped by type, from the commits since the previous tag.

After the frontmatter with the name and the description, it's all about what to instruct the AI in the body of the file. It's crucial to include both the name and the description; the skill can be invoked in two ways:

  • Via the slash (/) by the name: /release-notes

  • Automatically if the description fits your request

Setting disable-model-invocation: true prevents the automatic invocation. In GitHub Copilot CLI, there's no documented argument-substitution token — everything that comes after the name is just additional text, with the argument-hint being a suggestion of what you should write.

Thanks to the fact that both Claude Code and Codex can read from the .claude/skills/ and .agents/skills/ directories too, skills created in this standard way are usually compatible with all of these tools. Just keep in mind that if your skill relies on the argument substitution (which isn't supported by GitHub Copilot CLI), it won't work.

In Cursor

Cursor's take on this thing is that we call them commands. They are located in .cursor/commands/<name>.md files (in the repo) or ~/.cursor/commands/ for user-level ones, and have no frontmatter or heading — only the prompt itself (the file name is used as a command name). This is how a simple single-line prompt for release notes might look:

MARKDOWN
Draft release notes for the current version, grouped by type, from the commits since the previous tag.

And then you can run it by typing / in the agent's input and choosing it from the menu. It can't trigger itself and there's no way to pass any arguments to it, so it's just a pre-defined prompt that you can re-use whenever you need.

The other two entry points — a model-triggerable description or a self-applicable instruction — are part of other Cursor features: .cursor/rules/<name>.mdc and .cursor/skills/<name>/SKILL.md (where you also need to have the familiar name and description in the frontmatter, as well as a paths glob to define where it applies). So a command is the most primitive form of such a thing in terms of structure — go for it if all you need is a prompt with a name that you can re-run.

In Gemini CLI

It is a TOML file per command, for every user at ~/.gemini/commands/<name>.toml and for projects under <project>/.gemini/commands/, like this:

TOML
description = "Draft release notes from the commits since the last tag"
prompt = "Write release notes for version {{args}}, grouped by type, from the commits since the previous tag."
  • The fields are:

    • prompt — required

    • description — optional, shown in /help next to the command

  • You can use the {{args}} placeholder for arguments, which will be replaced with the text you type after the command. For example if you type /release-notes 2.4.0 it will become 2.4.0

  • If you don't use the placeholder, the text you type will be appended at the end of the prompt after a blank line

  • You can nest folders under commands and separate them with colons. For example /git:commit

  • There's a single entry point for the user to run commands — it's by typing, the model cannot do it, so the description field is only useful for /help

  • Just make sure you know which product you're talking about. In June 2026 Google retired the consumer Gemini CLI tiers, so all consumers are being redirected to Antigravity CLI. It uses Markdown skills with name and description in the frontmatter of each file under ~/.gemini/antigravity-cli/ — a different format compared to what's above, which is still applicable for OSS and enterprise Gemini CLI

In Kimi Code CLI

In terms of where to find them and what they look like, you can find them in the following locations (for the project and for your own):

  • .kimi-code/skills / .agents/skills

  • ~/.kimi-code/skills / ~/.agents/skills

In terms of the definition, it's either a directory containing SKILL.md or a single .md file whose name is the skill name.

YAML
---
name: release-notes
description: Draft release notes from the commits since the last tag.
whenToUse: When asked to write or update a changelog or release notes.
disableModelInvocation: false
---

Write release notes for $ARGUMENTS, grouped by type, from the commits since the previous tag.

An example of a release-notes skill which is used for drafting notes from commits after the last tag based on the frontmatter and a body instruction. It's the most robust in terms of what is described above, as it supports both ways of invocation and all the available settings. In order to use it you need to type /skill:release-notes <argument> (the prefix part is mandatory).

The model invokes it automatically unless one of the following is set:

  • disableModelInvocation

  • type is flow

If you were to set any of these, the usage of this skill will be limited only to manual invocation.

For this skill we have 3 ways to pass an argument:

  • $ARGUMENTS — the entire argument as a string

  • $0/$1 (or more if needed) — using position, and taking into account that you can use quotes in the argument, so for example /skill:commit "fix login" patch will make the skill assign the value of fix login to $0

  • $<name> — a parameter which needs to be defined in an arguments: list

For skills it's important to know that they can call other skills, but this is limited to 3 levels of nesting; if you were to create a chain longer than that, Kimi will stop it.

Reading an unfamiliar skill

So, now when you see a skill in a tool you haven't used before, just ask yourself these three questions:

  • What does the description say — that's both what the model has to base its decision on (so it's important to understand if it doesn't work as intended) and the first thing to look for if you think sth is wrong with this skill

  • Is it open to the model or only to the user — that's how you know if it's supposed to be used this way if you feel like "it never worked"

  • Does it have arguments, and if yes — how are they passed

If you know these three things, the body of a skill is just a procedure waiting for one of two invokers to call it.

j / k to move between lessons